diff --git a/common/Alias/Model.php b/common/Alias/Model.php new file mode 100644 index 0000000..574813a --- /dev/null +++ b/common/Alias/Model.php @@ -0,0 +1,146 @@ +container = $container; + return $this; + } + public function save() { + $pdo = ORM::getDb(); + $pdo->beginTransaction(); + try { + $result = parent::save(); + $pdo->commit(); + return $result; + } catch (\Exception $e) { + $pdo->rollBack(); + throw $e; + } + } + const KEY_EXCEPTION = 'Exception'; + protected function parseKeys(string $called_function, array $definitions, array $defaults) { + foreach ($defaults as $key => $value) { + if (!isset($definitions[$key])) { + if ($value == self::KEY_EXCEPTION) { + throw new \InvalidArgumentException("Missing '" . $key . "' in '" . $called_function . "' for " . get_called_class()); + } + $definitions[$key] = $value; + } + } + return (object) $definitions; + } + protected function validateVar(string $var) { + if (!isset($this->$var)) { + throw new \Exception("'" . $var . "' not found in '" . get_called_class() . "'."); + } + } + public function childOf(string $parent_class, array $id_definitions, $var = '') { + $id_definitions = $this->parseKeys('childOf', $id_definitions, [self::SELF_KEY => self::KEY_EXCEPTION, self::PARENT_KEY => 'id']); + if ($var == '') { + return $this->container->model->find($parent_class) + ->where([[$id_definitions->parent_key, $this->{$id_definitions->self_key}]]) + ->one(); + } + $this->validateVar($var); + if ($this->$var == null) { + $this->$var = $this->container->model->find($parent_class) + ->where([[$id_definitions->parent_key, $this->{$id_definitions->self_key}]]) + ->one(); + } + return $this->$var; + } + public function parentOf(string $child_class, array $id_definitions, $var = ''): ModelFactory { + $id_definitions = $this->parseKeys('parentOf', $id_definitions, [self::SELF_KEY => 'id', self::CHILD_KEY => self::KEY_EXCEPTION]); + if ($var == '') { + return $this->container->model->find($child_class) + ->where([[$id_definitions->child_key, $this->{$id_definitions->self_key}]]); + } + $this->validateVar($var); + if ($this->$var == null) { + $this->$var = $this->container->model->find($child_class) + ->where([[$id_definitions->child_key, $this->{$id_definitions->self_key}]]); + } + return $this->$var; + } + public function parentOfOne(string $child_class, array $id_definitions, $var = '') { + $factory = $this->parentOf($child_class, $id_definitions, $var); + return $factory->one(); + } + public function parentOfMany(string $child_class, array $id_definitions, $var = '') { + $factory = $this->parentOf($child_class, $id_definitions, $var); + return $factory->many(); + } + public function siblingOf(string $sibling_class, string $table_through, array $id_definitions, $var = ''): ModelFactory { + $id_definitions = $this->parseKeys('siblingOf', $id_definitions, [ + self::SIBLING_THROUGH_KEY => self::KEY_EXCEPTION, + self::SIBLING_KEY => 'id', + self::SELF_THROUGH_KEY => self::KEY_EXCEPTION, + self::SELF_KEY => 'id' + ]); + $sibling_table = self::getTable($sibling_class); + if ($var == '') { + return $this->container->model->find($sibling_class) + ->select($sibling_table . '.*') + ->join([ + [$table_through, $table_through . '.' . $id_definitions->sibling_through_key, $sibling_table . '.' . $id_definitions->sibling_key] + ]) + ->where([[$table_through . '.' . $id_definitions->self_through_key, $this->{$id_definitions->self_key}]]); + } + $this->validateVar($var); + if ($this->$var == null) { + $this->$var = $this->container->model->find($sibling_class) + ->select($sibling_table . '.*') + ->join([ + [$table_through, $table_through . '.' . $id_definitions->sibling_through_key, $sibling_table . '.' . $id_definitions->sibling_key] + ]) + ->where([[$table_through . '.' . $id_definitions->self_through_key, $this->{$id_definitions->self_key}]]); + } + return $this->$var; + } + public function siblingOfOne(string $sibling_class, string $table_through, array $id_definitions, $var = '') { + $factory = $this->siblingOf($sibling_class, $table_through, $id_definitions, $var); + if ($var == '') { + return $factory->one(); + } + $this->validateVar($var); + if (!is_a($this->$var, $sibling_class)) { + $this->$var = $factory->one(); + } + return $this->$var; + } + public function siblingOfMany(string $sibling_class, string $table_through, array $id_definitions, $var = '') { + $factory = $this->siblingOf($sibling_class, $table_through, $id_definitions, $var); + if ($var == '') { + return $factory->many(); + } + $this->validateVar($var); + if (!is_a($this->$var, $sibling_class)) { + $this->$var = $factory->many(); + } + return $this->$var; + } + public function toArray(): array { + return $this->asArray(); + } + public function jsonSerialize() { + return $this->toArray(); + } +} diff --git a/common/Definition/DatabaseEngineRequirements.php b/common/Definition/DatabaseEngineRequirements.php new file mode 100644 index 0000000..9999fac --- /dev/null +++ b/common/Definition/DatabaseEngineRequirements.php @@ -0,0 +1,8 @@ + '_id', 'parent_key' => 'id'] + * @return Model Return parent model + */ + public function childOf(string $parent_class, array $id_definitions); + /** + * Parent relationship + * @param string $child_class Child model class + * @param array $id_definitions ['self_key' => 'id', 'child_key' => '_id'] + * @return ModelFactory ModelFactory for modification before returning model + */ + public function parentOf(string $child_class, array $id_definitions): ModelFactory; + /** + * Parent relationship to one + * @param string $child_class Child model class + * @param array $id_definitions ['self_key' => 'id', 'child_key' => '_id'] + * @return Model Return child model + */ + public function parentOfOne(string $child_class, array $id_definitions); + /** + * Parent relationship to many + * @param string $child_class Child model class + * @param array $id_definitions ['self_key' => 'id', 'child_key' => '_id'] + * @return array Return array with child models + */ + public function parentOfMany(string $child_class, array $id_definitions); + /** + * Many to Many relationship + * @param string $sibling_class Sibling model class + * @param string $table_through Table connecting siblings + * @param array $id_definitions ['sibling_through_key' => '_id', + * 'sibling_key' => 'id', + * 'self_through_key' => '_id', + * 'self_key' => 'id' + * ] + * @return array Return array with sibling models + */ + public function siblingOf(string $sibling_class, string $table_through, array $id_definitions): ModelFactory; + public function siblingOfOne(string $sibling_class, string $table_through, array $id_definitions); + public function siblingOfMany(string $sibling_class, string $table_through, array $id_definitions); + /** + * Cast to array + * @return array Return array with all columns, can be overriden to include children + */ + public function toArray(): array; +} diff --git a/common/Factory/Model.php b/common/Factory/Model.php new file mode 100644 index 0000000..2df405f --- /dev/null +++ b/common/Factory/Model.php @@ -0,0 +1,253 @@ +container = $container; + } + + public function reset() { + foreach ($this as $property => $value) { + if ($property == 'container') { + continue; + } + $this->$property = null; + } + } + protected $class; + public function find(string $model_class): Model { + $this->reset(); + if (!class_exists($model_class)) { + throw new \InvalidArgumentException($model_class . ' not found.'); + } + $this->class = $model_class; + return $this; + } + protected $columns; + public function select($columns): Model { + if ($this->columns == null) { + $this->columns = []; + } + if (!is_array($columns)) { + $columns = [$columns]; + } + $this->columns = array_merge($this->columns, $columns); + return $this; + } + protected function parseSelect(ORM $orm): ORM { + if ($this->columns == null or count($this->columns) == 0) { + return $orm; + } + foreach ($this->columns as $column => $alias) { + if (is_numeric($column)) { + $orm = $orm->select($alias); + continue; + } + $orm = $orm->select($column, $alias); + } + return $orm; + } + protected $joins; + public function join(array $joins): Model { + if ($this->joins == null) { + $this->joins = []; + } + $this->joins = array_merge($this->joins, $joins); + return $this; + } + public function parseJoin(ORM $orm): ORM { + if ($this->joins == null or count($this->joins) == 0) { + return $orm; + } + foreach ($this->joins as $join) { + $method = 'join'; + if (isset($join['type'])) { + switch (strtolower($join['type'])) { + case 'left': + $method = 'leftOuterJoin'; + break; + case 'right': + $method = 'rightOuterJoin'; + break; + case 'raw': + $method = 'rawJoin'; + break; + } + } + $table = $join[0]; + if (isset($join['table'])) { + $table = $join['table']; + } + $field1 = $join[1]; + if (isset($join['field1'])) { + $field1 = $join['field1']; + } + $op = '='; + if (isset($join['operator'])) { + $op = $join['operator']; + } + $field2 = $join[2]; + if (isset($join['field2'])) { + $field2 = $join['field2']; + } + $alias = null; + if (isset($join['alias'])) { + $alias = $join['alias']; + } + $orm = $orm->{$method}($table, [$field1, $op, $field2], $alias); + } + return $orm; + } + protected $conditions; + public function where(array $conditions): Model { + if ($this->conditions == null) { + $this->conditions = []; + } + $this->conditions = array_merge($this->conditions, $conditions); + return $this; + } + protected function parseWhere(ORM $orm): ORM { + if ($this->conditions == null or count($this->conditions) == 0) { + return $orm; + } + foreach ($this->conditions as $condition) { + $method = 'where'; + $op = '='; + if (isset($condition[2])) { + $op = strtolower($condition[2]); + } + if (isset($condition['operator'])) { + $op = strtolower($condition['operator']); + } + $mod = ['=' => '', '>' => 'Gt', '>=' => 'Gte', '<' => 'Lt', '<=', 'Lte']; + if (isset($mod[$op])) { + $method .= $mod[$op]; + } else { + switch ($op) { + case 'raw': + $method = 'rawWhere'; + break; + } + } + $column = $condition[0]; + if (isset($condition['column'])) { + $column = $condition['column']; + } + $value = $condition[1]; + if (isset($condition['value'])) { + $value = $condition['value']; + } + $orm = $orm->{$method}($column, $value); + } + return $orm; + } + protected $ordering; + public function order($orders): Model { + if ($this->ordering == null) { + $this->ordering = []; + } + if (!is_array($orders)) { + $orders = [$orders]; + } + $this->ordering = array_merge($this->ordering, $orders); + return $this; + } + protected function parseOrder(ORM $orm): ORM { + if ($this->ordering == null or count($this->ordering) == 0) { + return $orm; + } + foreach ($this->ordering as $order => $dir) { + if (is_numeric($order)) { + $order = $dir; + $dir = 'asc'; + } + $method = 'orderBy' . ucfirst(strtolower($dir)); + $orm = $orm->{$method}($order); + } + return $orm; + } + protected $grouping; + public function group($groups): Model { + if ($this->grouping == null) { + $this->grouping = []; + } + if (!is_array($groups)) { + $groups = [$groups]; + } + $this->grouping = array_merge($this->grouping, $groups); + return $this; + } + protected function parseGroup(ORM $orm): ORM { + if ($this->grouping == null or count($this->grouping) == 0) { + return $orm; + } + foreach ($this->grouping as $group) { + $orm = $orm->groupBy($group); + } + return $orm; + } + protected $limits; + public function limit(int $limit, int $offset = 0): Model { + $this->limits = (object) ['limit' => $limit, 'offset' => $offset]; + return $this; + } + protected function parseLimit(ORM $orm): ORM { + if ($this->limits == null) { + return $orm; + } + $orm = $orm->limit($this->limits->limit); + if ($this->limits->offset > 0) { + $orm = $orm->offset($this->limits->offset); + } + return $orm; + } + public function build(): ORM { + $orm = BaseFactory::factory($this->class); + $methods = ['select', 'join', 'where', 'order', 'group', 'limit']; + foreach ($methods as $m) { + $method = 'parse' . ucfirst($m); + $orm = $this->{$method}($orm); + } + return $orm; + } + public function one($key = null) { + $model = $this->build()->findOne($key); + if ($model === false) { + return false; + } + $model->setContainer($this->container); + return $model; + } + public function many(): array { + $models = $this->build()->findMany(); + array_walk($models, function(&$item, $key, $container) { + $item->setContainer($container); + }, $this->container); + return $models; + } + public function array(): array { + $models = $this->many(); + return array_map(function($item) { + return $item->toArray(); + }, $models); + } + public function create(string $model_class, array $data) { + $factory = new Model($this->container); + $where = []; + foreach ($data as $column => $condition) { + $where []= [$column, $condition]; + } + $obj = $factory->find($model_class)->where($where)->one(); + if ($obj === false) { + $obj = BaseFactory::factory($model_class)->create($data); + $obj->save(); + $obj->setContainer($this->container); + } + return $obj; + } +} diff --git a/common/Service/ParisConfig.php b/common/Service/ParisConfig.php new file mode 100644 index 0000000..1cba587 --- /dev/null +++ b/common/Service/ParisConfig.php @@ -0,0 +1,48 @@ +default = $default; + $this->databases = $databases; + } + protected function getEngines() { + $dir = implode(DIRECTORY_SEPARATOR, [dirname(dirname(__DIR__)), 'src']); + $files = new \DirectoryIterator($dir); + $engines = []; + foreach ($files as $file) { + if ($file->isDir() or $file->getExtension() != 'php') { + continue; + } + $engine = $file->getBasename('.' . $file->getExtension()); + $name = str_replace('requirements', '', strtolower($engine)); + $engines[$name] = $engine; + } + return $engines; + } + public function config() { + $engines = $this->getEngines(); + foreach ($this->databases as $name => $database) { + if ($name == $this->default) { + $name = ORM::DEFAULT_CONNECTION; + } + $engine = $database->engine; + $requirements = implode("\\", ['Aldarien', 'Models', $engines[strtolower($engine)]]); + $requirements = new $requirements(); + $dsn = $requirements->getDSN($database); + ORM::configure($dsn, null, $name); + if ($requirements->hasUser()) { + ORM::configure('username', $database->user->name, $name); + ORM::configure('password', $database->user->password, $name); + } + } + if (isset($this->databases->short_table_names)) { + Model::$short_table_names = $this->databases->short_table_names; + } + } +}