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($this->filename);
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(); // создаем файловое хранилище
$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