Merge branch 'develop'

This commit is contained in:
2019-09-13 17:44:56 -03:00
9 changed files with 621 additions and 0 deletions

146
common/Alias/Model.php Normal file
View File

@ -0,0 +1,146 @@
<?php
namespace Aldarien\Common\Alias;
use Psr\Container\ContainerInterface;
use \ORM as ORM;
use \Model as BaseModel;
use Aldarien\Common\Definition\Model as ModelInterface;
use Aldarien\Common\Factory\Model as ModelFactory;
class Model extends BaseModel implements ModelInterface {
const CHILD_KEY = 'child_key';
const PARENT_KEY = 'parent_key';
const SELF_KEY = 'self_key';
const SELF_THROUGH_KEY = 'self_through_key';
const SIBLING_THROUGH_KEY = 'sibling_through_key';
const SIBLING_KEY = 'sibling_key';
public static function getTable(string $model_class): string {
return self::_get_table_name($model_class);
}
protected $container;
public function setContainer(ContainerInterface $container): ModelInterface {
$this->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();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Aldarien\Common\Definition;
interface DatabaseEngineRequirements {
public function dsnSpecs(): array;
public function getDSN(object $config): string;
public function hasUser(): bool;
}

View File

@ -0,0 +1,72 @@
<?php
namespace Aldarien\Common\Definition;
use Psr\Container\ContainerInterface;
use Aldarien\Common\Factory\Model as ModelFactory;
interface Model extends \JsonSerializable {
/**
* Get table name
* @param string $model_class Model class
* @return string Return model table name
*/
public static function getTable(string $model_class): string;
/**
* Set container for dependency injection
* @param ContainerInterface $container Dependency injection container
* @return Model Returns $this
*/
public function setContainer(ContainerInterface $container): Model;
/**
* Transitions save, rolls back on exception
* @return mixed BaseModel::save
*/
public function save();
/**
* Child relationship
* @param string $parent_class Parent model class
* @param array $id_definitions ['self_key' => '<parent_table|singular>_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' => '<self_table|singular>_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' => '<self_table|singular>_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' => '<self_table|singular>_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' => '<sibling_table|singular>_id',
* 'sibling_key' => 'id',
* 'self_through_key' => '<self_table|singular>_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;
}

253
common/Factory/Model.php Normal file
View File

@ -0,0 +1,253 @@
<?php
namespace Aldarien\Common\Factory;
use Psr\Container\ContainerInterface;
use \ORM as ORM;
use \Model as BaseFactory;
class Model {
protected $container;
public function __construct(ContainerInterface $container) {
$this->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;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Aldarien\Common\Service;
use \ORM as ORM;
use Aldarien\Common\Alias\Model;
class ParisConfig {
protected $default;
protected $databases;
public function __construct(string $default, object $databases) {
$this->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;
}
}
}

24
composer.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "aldarien/models",
"description": "Model base",
"type": "module",
"require": {
"j4mie/paris": "^1.5"
},
"require-dev": {
"phpunit/phpunit": "^8.2"
},
"license": "MIT",
"authors": [
{
"name": "Aldarien",
"email": "aldarien85@gmail.com"
}
],
"autoload": {
"psr-4": {
"Aldarien\\Common\\": "common",
"Aldarien\\Models\\": "src"
}
}
}

8
phpunit.xml Normal file
View File

@ -0,0 +1,8 @@
<phpunit colors="true"
verbose="true">
<testsuites>
<testsuite name="Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

25
src/MySQLRequirements.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace Aldarien\Models;
use Aldarien\Common\Definition\DatabaseEngineRequirements as EngineRequirementsInterface;
class MySQLRequirements implements EngineRequirementsInterface {
public function dsnSpecs(): array {
return [
'host_name',
'host_port',
'database_name'
];
}
public function getDSN(object $config): string {
$dsn = 'mysql:host=' . $config->host->name;
if (isset($config->host->port)) {
$dsn .= ';port=' . $config->host->port;
}
$dsn .= ';dbname=' . $config->database->name;
return $dsn;
}
public function hasUser(): bool {
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
use PHPUnit\Framework\TestCase;
use Aldarien\Models\MySQLRequirements;
class MySQLRequirementsTest extends TestCase {
public function testStart() {
$req = new MySQLRequirements();
$this->assertTrue(true);
}
public function testSpecs() {
$req = new MySQLRequirements();
$expected = ['host_name', 'host_port', 'database_name'];
$result = $req->dsnSpecs();
$this->assertEquals($expected, $result);
}
public function testGetDSN() {
$req = new MySQLRequirements();
$config = (object) [
'host' => (object) [
'name' => 'localhost',
'port' => 3306
],
'database' => (object) [
'name' => 'test_db'
]
];
$expected = 'mysql:host=localhost;port=3306;dbname=test_db';
$result = $req->getDSN($config);
$this->assertEquals($expected, $result);
}
public function testHasUser() {
$req = new MySQLRequirements();
$expected = true;
$result = $req->hasUser();
$this->assertEquals($expected, $result);
}
}