Welcome, guest! Login / Register - Why register?
Psst.. new poll here.
Psst.. new forums here.
Microsoft is blocking us again (TY IP Reputation!) so just use oauth login instead. :)

Paste

Pasted as PHP by Xpom ( 15 years ago )
<?
// автор Богатов Василий
// http://fasmer.ru/a.php

/*

 задание реализовано с использованием парадигмы Storage/Document/View
  Storage - несколько хранилищ в чистом виде с методами store и restore
  Document - прикладные объекты в глобальном контексте с помощью ApplicationObject/ApplicationCollection
  View - ввиду специфики задачи реализован в виде конвертеров

 реализовано одним файлом
 прикладной код - в конце файла
 не отдокументировано в стиле phpdoc, в наличии простые комментари по коду
 fault tolerance практически нулевой
 beautifier не использовался
 есть некоторая чехарда в нотациях именования


 STORAGE
 -------

  3 Хранилища - БД, Файл и Поток - по-разному реализуют методы store и restore

 DOCUMENT
 --------

  цепочки наследования для прикладной части
   коллекция персон <- прикладная коллекция <- типизированная коллекция <- коллекция <- итератор
   персона <- прикладной объект

 VIEW
 ----
  
  2 метаКонвертера - содержат конвертеры, различаются характером
  - exporter - из объекта в форматированные данные
  - importer - наоборот

  для каждого метаКонвертера по 2 конвертера - преобразование прикладных объектов и их коллекций в формат, пригодный для Хранилищ
   - преобразование прикладного объекта/коллекции в mysql-insert/массив mysql-insert
   - преобразование прикладного объекта в CSV-строку
   - преобразование CSV-строку в данные для прикладного объекта
   - преобразование результат запроса к БД в данные для прикладного объекта
  методы Конвертера
   - doit - для прикладных объектов
   - doit2 - для прикладных коллекций

  1 Маппер - преобразование тривиальных типов данных для нужд конвертеров
   в Маппере 4 метода конвертации между форматами
    app -> db, 
    app -> csv, 
    db -> app, 
    csv -> app

*/




// конфиг
class CONFIG
{
 const server = "u201087.mysql.masterhost.ru";
 const db  = "u201087";
 const login  = "u201087";
 const password = "sallus5nedn";
 const filename = "a.csv";
 const template = "<b>%s</b><br>\n";

 const logging = true;
 const logdiv = "\r\n";
}

// лог
class LOG
{
 static public function trace($msg)
 {
  if(CONFIG::logging)
   echo($msg.CONFIG::logdiv);
 }
}

// Хранилище
interface iStorage
{
 public function init();       // инициализация
 public function store($obj);     // положить в хранилище
 public function restore($condition = null);  // вынуть из хранилища
}

// Хранилище MYSQL
class Mysql implements iStorage
{
 private $conn;

 public function init()
 {
  // инициализация согласно конфигу
  $this->conn = mysql_connect(CONFIG::server, CONFIG::login, CONFIG::password);
  if(!$this->conn)
   throw new Exception("cant connect MYSQL");
  mysql_select_db(CONFIG::db, $this->conn);
  // тут же - создаем нашу таблицу
  $this->store("drop table f_person");
  $this->store("create table f_person (id INT NOT NULL AUTO_INCREMENT, PRIMARY KEY(id), fio VARCHAR(50), email VARCHAR(50), dtborn DATE, dtreg INT, status INT)");
  LOG::trace("MYSQL inited");
 }
 public function store($data)
 {
  // если пришел массив запросов
  if(is_array($data))
  {
   // выполняем в БД каждый запрос
   for($i=0,$m=count($data); $i<$m; $i++)
    $this->store($data[$i]);
   LOG::trace("MYSQL store number: ".count($data));
  }
  // если пришла строка запроса
  if(is_string($data))
  {
   // выполняем запрос
   mysql_query($data, $this->conn);
   LOG::trace("MYSQL store: ".$data);
  }
 }
 public function restore($condition = null)
 {
  // выполняем запрос, возвращающий данные
  $result  = mysql_query($condition, $this->conn);

  // данные складываем в результирующий блок
  $data  = array();
  while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) 
   $data[] = $row;
  LOG::trace("MYSQL restore: ".$condition);
  return $data;
 }
}

// Хранилище файловое
class File implements iStorage
{
 private $filename;
 
 public function init()
 {
  // инициализация согласно конфигу
  $this->filename = CONFIG::filename;
  LOG::trace("FILE inited");
 }
 public function store($data) {}
 public function restore($condition = null)
 {
  // выбираем файл целиком
  $content   = file_get_contents&#40;$this->filename&#41;;
  if(!$content)
   throw new Exception("cant read ".$this->filename);
  else
  {
   LOG::trace("FILE restore: ".$content);
   return $content;
  }
 }
}

// Хранилище Поток (сюда мы только отправляем данные)
class Stream implements iStorage
{
 private $template;

 public function init()
 {
  // инициализация согласно конфигу
  $this->template = CONFIG::template;
  LOG::trace("STREAM inited");
 }
 public function store($data)
 {
  LOG::trace("STREAM store: ".$data);
  // печатаем в поток
  echo sprintf($this->template, $data);
 }
 public function restore($condition = null) {}
}

// интерфейс Конвертер - преобразует прикладной объект/коллекцию в данные для отправки на хранение
interface converter
{
 public function doit($params = array()); // преобразует прикладной объект
 public function doit2($params = array()); // преобразует коллекцию прикладных объектов
}

// преобразует прикладной объект/коллекцию в mysql-insert/массив mysql-insert
class export_MYSQL_insert implements converter
{
 public function doit($params = array())
 {
  $obj  = $params["data"];
  $map  = $params["map"];
  $fields  = array();
  $values  = array();
  for($i=0,$m=count($map["fields"]); $i<$m; $i++)
  {
   $field   = $map["fields"][$i];
   $fields[]  = $field["dbname"];
   $objfieldname = $field["appname"];
   $values[]  = Mapper::convert($obj->$objfieldname, $field, Mapper::APP, Mapper::DB);;
  }
  $insert = sprintf("insert into %s (%s) values (%s)", $map["table"], implode(",", $fields), implode(",", $values));
  LOG::trace("MYSQL export insert: ".$insert);
  return $insert;
 }

 public function doit2($params = array())
 {
  $map   = $params["map"];
  $collection  = $params["data"];
  $inserts  = array();
  while($collection->valid()) 
  {
   $item  = $collection->current();
   $insert  = $this->doit(array("map"=>$map, "data"=>$item)); // экспортируем (сериализуем) Персону в полный MYSQL-insert
   $inserts[] = $insert;
   $collection->next();
  }
  LOG::trace("MYSQL export insert number: ".count($inserts));
  return $inserts;
 }
}

// преобразует прикладной объект в CSV-строку
class export_CSV implements converter
{
 public function doit($params = array())
 {
  $buf   = array();
  $map   = $params["map"];
  $obj   = $params["data"];
  $fields   = $map["fields"];
  for($i=0,$m=count($fields); $i<$m; $i++)
  {
   $field  = $fields[$i];
   if($field["cvsexport"])
   {
    $fieldname = $field["appname"];
    $value  = $obj->$fieldname;
    $csvvalue = Mapper::convert($value, $field, Mapper::APP, Mapper::CSV);
    $buf[]  = $csvvalue;
   }
  }
  $csv = implode("; ", $buf);
  LOG::trace("CSV export: ".$csv);
  return $csv;
 }
 public function doit2($params = array()) {}
}

// преобразует CSV-строку в данные для прикладного объекта
class import_CSV implements converter
{
 public function doit($params = array())
 {
  $map   = $params["map"];
  $data   = $params["data"];
  $csvdata  = explode("\r\n", $data);
  $csvfieldnames = explode("; ", $csvdata[0]);
  $dataitem  = explode("; ", $csvdata[1]);
  for($j=0,$mm=count($dataitem); $j<$mm; $j++)
  {
   $field = Mapper::getFieldMap("csvname", $csvfieldnames[$j], $map);
   $dummy[$field["appname"]] = Mapper::convert($dataitem[$j], $field, Mapper::CSV, Mapper::APP);
  }
  LOG::trace("CSV import: ".$csvdata[1]);
  return $dummy;
 }
 public function doit2($params = array())
 {
  $dummies  = array();
  $map   = $params["map"];
  $data   = $params["data"];
  $csvdata  = explode("\r\n", $data);
  $csvfielddata = array_shift($csvdata);
  $csvfieldnames = explode("; ", $csvfielddata);
  for($i=0,$m=count($csvdata); $i<$m; $i++)
  {
   $singledata = implode("\r\n", array($csvfielddata, $csvdata[$i]));
   $dummy  = $this->doit(array("map"=>$map, "data"=>$singledata));
   $dummies[] = $dummy;
  }
  LOG::trace("CSV import number: ".count($dummies));
  return $dummies;
 }
}

// преобразует результат запроса в данные для прикладного объекта
class import_MYSQL_select implements converter
{
 public function doit($params = array())
 {
  $map   = $params["map"];
  $data   = $params["data"];
  foreach($data as $k => $v)
  {
   $field = Mapper::getFieldMap("dbname", $k, $map);
   $dummy[$field["appname"]] = Mapper::convert($v, $field, Mapper::DB, Mapper::APP);
  }
  LOG::trace("MYSQL import: ".str_replace(array("\r\n", "\n", "\r"), "", print_r($dummy, true)));
  return $dummy;
 }

 public function doit2($params = array())
 {
  $dummies  = array();
  $map   = $params["map"];
  $data   = $params["data"];
  for($i=0,$m=count($data); $i<$m; $i++)
  {
   $singledata = $data[$i];
   $dummy  = $this->doit(array("map"=>$map, "data"=>$singledata));
   $dummies[] = $dummy;
  }
  LOG::trace("MYSQL import number: ".count($dummies));
  return $dummies;
 }
}

// Коллекция - итератор с дополнительным методами add и count
abstract class collection implements Iterator 
{
    private $position = 0;
    private $array = array();
    private $count = 0;

    public function __construct() 
    {
        $this->rewind();
    }

    function rewind() 
    {
        $this->position = 0;
    }

    function current() 
    {
        return $this->array[$this->position];
    }

    function key() 
    {
        return $this->position;
    }

    function next() 
    {
        ++$this->position;
    }

    function valid() 
    {
        return isset($this->array[$this->position]);
    }

    function add($obj)
    {
     $this->array[] = $obj;
     $this->count++;
    }

    function count()
    {
     return $this->count;
    }

    function get($indx)
    {
        return $this->array[$indx];
    }
}

// типизированная коллекция - в нее могут быть добавлены объекты лишь одного указанного класса
abstract class ClassifiedCollection extends collection
{
 public $className;

    public function __construct($className)
    {
     $this->className = $className;
        $this->rewind();
    }

    function add($obj)
    {
     if($obj instanceof $this->className)
      parent::add($obj);
       else
        throw new Exception("is not appropriate obj for this collection! class must be ".$this->className);
    }
}

// прикладная коллекция - ее объекты можгу быть проинициализированы подготовленными данными
abstract class ApplicationCollection extends ClassifiedCollection
{
    public function __construct($className)
    {
     parent::__construct($className);
    }

 public function init($dummies = array())
 {
  foreach($dummies as $k => $dummy)
  {
   $item = new $this->className();
   $item->init($dummy);
   $this->add($item);
  }
 }
}

// прикладная коллекция Персон
class PersonCollection extends ApplicationCollection
{
    public function __construct()
    {
     parent::__construct("Person");
    }
}

// СуперКласс - умеет "квази-множественное наследование"
abstract class SuperObject 
{
 public function __construct() 
 {
  if (func_num_args() > 0)
  {
   $arr = func_get_args();
 
   for($i=0,$m=count($arr); $i<$m; $i++)
   {
    $class_name = $arr[$i];
    $this->extendClass($class_name);
   }
  }
 }
 
 public function extendClass($class_name, $alias = null)
 {
  $alias     = $alias == null ? $class_name : $alias;
  $this->$alias   = new $class_name;
 }
}

// хелпер Экспортер
class exporter extends SuperObject
{
 public function __construct() 
 {
  parent::__construct();
  $this->extendClass("export_MYSQL_insert", "iMYSQL");
  $this->extendClass("export_CSV", "CSV");
 }
}

// хелпер Импортер
class importer extends SuperObject
{
 public function __construct() 
 {
  parent::__construct();
  $this->extendClass("import_MYSQL_select", "sMYSQL");
  $this->extendClass("import_CSV", "CSV");
 }
}

// прикладной объект
abstract class ApplicationObject
{
 private $changed = array();
 private $inited  = array();

 // рассовывание подготовленных данных по свойствам объекта
 public function init($data = array())
 {
  foreach($data as $k => $v)
   $this->setProperty($k, $v);
 }

 public function setProperty($name, $value, $map = null)
 {
  // нижеследующий код - для отслеживания изменений в объекте, чтобы только для измененных полей генерить mysql update
  if(!isset($this->inited[$name]))    // если переменная не инициализировалась
   $this->inited[$name] = $value;   // первичное значение будет записано
  else           // если переменная инициализировалась
  {
   if($this->$name !== $value)     // и пришло другое значение
    if($this->inited[$name] == $value)  // если другое значение равно первичному
     unset($this->changed[$name]);  // сбрасываем флаг измененности
    else         // если другое значение отлично от первичного
     $this->changed[$name] = true;  // взводим флаг измененности
  }
  $this->$name   = $value;
 }

}

// конвертер типов данных (dynamic casting)
abstract class Mapper
{
 const CSV = "csv"; // комплексный тип CSV
 const APP = "app"; // комплексный тип на стороне сервера (в РНР)
 const DB = "db";  // комплексный тип в БД

 // конвертируем Значение согласно Метаданным поля, из комплексного типа А в комплексный тип Б
 static public function convert($value, $field, $tfrom=self::APP, $tto=self::APP)
 {
//  LOG::trace(sprintf("Mapper call: %s (%s, %s)",$method,$tf,$tt));
  $method = $tfrom."2".$tto;     // метод будет называться, например, cvs2app
  $tf  = $field[$tfrom."type"];   // из типа CSV.tumbler
  $tt  = $field[$tto."type"];    // в тип APP.int
  return self::$method($value, $tf, $tt);  // возвращаем конвертированное значение
 }
 
 // метод вернет Метаданные поля из Карты согласно заданным Имени атрибута и Значению
 static public function getFieldMap($name_attr, $name_val, $map)
 {
//  LOG::trace(sprintf("Mapper get field: %s = %s", $name_attr, $name_val));
  $fields = $map["fields"];
  for($i=0,$m=count($fields); $i<$m; $i++)
  {
   $field = $fields[$i];
   if($field[$name_attr] == $name_val)
    return $field;
  } 
  return null;
 }

 // далее - реализации из одного комплексного типа в другой

 // CSV -> APP
 static public function csv2app($value, $tf, $tt)
 {
  switch($tf)
  {
  case "string":
  case "date":
   break;
  case "datetime":
   if(is_string($value))
   {
    preg_match('/(?P<day>\d\d)\.(?P<month>\d\d)\.(?P<year>\d\d\d\d)( (?P<hour>\d?\d):(?P<minute>\d?\d))?/', $value, $m);
    $value = mktime($m["hour"], $m["minute"], 0, $m["month"], $m["day"], $m["year"]);
   }
   break;
  case "tumbler":
   $value = $value=="On" ? 1 : $value;
   $value = $value=="Off" ? 0 : $value;
   $value = (int)$value;
   break;
  }
  return $value;
 }

 // DB -> APP
 static public function db2app($value, $tf, $tt)
 {
  switch($tf)
  {
  case "string":
   break;
  case "date":
   preg_match('/(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)/', $value, $m);
   $value = sprintf("d.d.d", $m["day"], $m["month"], $m["year"]);
   break;
  case "datetime":
  case "int":
   $value = (int)$value;
   break;
  }
  return $value;
 }

 // APP -> DB
 static public function app2db($value, $tf, $tt)
 {
  switch($tt)
  {
  case "string":
   if($tf=="string")
    $value = "'".addslashes($value)."'";
   break;
  case "date":
   if($tf=="string")
   {
    preg_match('/(?P<day>\d\d)\.(?P<month>\d\d)\.(?P<year>\d\d\d\d)/', $value, $m);
    $value = sprintf("d-d-d 00:00:00", $m["year"], $m["month"], $m["day"]);
    $value = "'".addslashes($value)."'";
   }
   break;
  case "datetime":
  case "int":
   if($tf=="int")
    $value = $value===null?'null':(int)$value;
   break;
  }
  return $value;
 }

 // APP -> CSV
 static public function app2csv($value, $tf, $tt)
 {
  switch($tt)
  {
   case "string":
    break;
   case "date":
    break;
   case "datetime":
    $value = date("d.m.Y G:i", $value);
    break;
   case "int":
    $value = (int)$value;
    break;
   case "tumbler":
    $value = $value ? "On" : "Off";
    break;
  }
  return $value;
 }
}

// прикладной объект Персона
class Person extends ApplicationObject
{
/* public function __set($name, $value)
 {
  $this->setProperty($name, $value, self::$map["common"]);
 }
*/
 // карта объекта
 // с названиями полей и их типами для каждого представления - CSV, Application и MYSQL
 static public $map = array(
   "common"=> array(
   "table"    => "f_person",
   "fields"   => array(
    array("appname"=>"id",   "apptype"=>"int",                "dbname"=>"id",   "dbtype"=>"int"),
    array("appname"=>"fio",  "apptype"=>"string", "csvname"=>"Фамилия Имя",  "csvtype"=>"string", "dbname"=>"fio",   "dbtype"=>"string", "cvsexport"=>true),
    array("appname"=>"email",  "apptype"=>"string", "csvname"=>"E-mail",   "csvtype"=>"string", "dbname"=>"email",   "dbtype"=>"string", "cvsexport"=>true),
    array("appname"=>"dtborn", "apptype"=>"string", "csvname"=>"Дата рождения",  "csvtype"=>"date",  "dbname"=>"dtborn",  "dbtype"=>"date", "cvsexport"=>true),
    array("appname"=>"dtreg",  "apptype"=>"int",  "csvname"=>"Зарегистрирован", "csvtype"=>"datetime", "dbname"=>"dtreg",   "dbtype"=>"int", "cvsexport"=>true),
    array("appname"=>"status", "apptype"=>"int",  "csvname"=>"Статус",   "csvtype"=>"tumbler", "dbname"=>"status",  "dbtype"=>"int", "cvsexport"=>true)
   )
  )
 );

 public $id;
 public $fio;
 public $email;
 public $dtborn;
 public $dtreg;
 public $status;
}

?><pre>
<?

$importer = new importer();
$exporter = new exporter();


$File  = new File&#40;&#41;;        // создаем файловое хранилище
$File->init();           // инициализируем
$csvdata  = $File->restore();      // достаем данные. без параметра - значит безусловно. то есть все данные
// по зачитанным данным Импортер выдает массив подготовленных данных для заполнения коллекции
$dummies  = $importer->CSV->doit2(array("data"=>$csvdata, "map" => Person::$map["common"]));
$persons  = new PersonCollection();    // создаем коллекцию Персон 
$persons->init($dummies);        // создаем прикладные объекты, наполняем их, помещаем в коллекцию 

$MYSQL   = new MYSQL();       // создаем БД-хранилище 
$MYSQL->init();           // инициализируем 
// по прикладной коллекции Экспортер выдает массив mysql-insert для помещения в mysql-хранилище
$inserts  = $exporter->iMYSQL->doit2(array("data"=>$persons, "map" => Person::$map["common"])); 
$MYSQL->store($inserts);        // кладем данные в MYSQL 

$mysqldata  = $MYSQL->restore("select * from f_person"); // достаем данные с помощью условия отбора 
// по зачитанным данным Импортер выдает массив подготовленных данных для заполнения коллекции 
$dummies  = $importer->sMYSQL->doit2(array("map" => Person::$map["common"], "data"=>$mysqldata));
$persons  = new PersonCollection();    // создаем коллекцию Персон 
$persons->init($dummies);        // создаем прикладные объекты, наполняем их, помещаем в коллекцию 

$n    = rand(0, $persons->count()-1);   // случайный номер 
LOG::trace("random record: ".$n);
$person   = $persons->get($n);     // выбираем Персону 
$person->setProperty("status", (int)!$person->status); // меняем статус у Персоны 
LOG::trace("status set: ".$person->status);

// по прикладному объекту Экспортер выдает csv-строку для помещения в хранилище 
$csv   = $exporter->CSV->doit(array("data" => $person, "map" => Person::$map["common"]));
$Stream   = new Stream();       // создаем "хранилище" Поток 
$Stream->init();          // инициализируем 
$Stream->store($csv);         // кладем данные в Поток 

LOG::trace("end");

?>
</pre>

 

Revise this Paste

Your Name: Code Language: