diff --git a/DataStorage/Database/GrammarAbstract.php b/DataStorage/Database/GrammarAbstract.php index b948364e4..261bd5c42 100644 --- a/DataStorage/Database/GrammarAbstract.php +++ b/DataStorage/Database/GrammarAbstract.php @@ -32,7 +32,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $comment = '--'; + public string $comment = '--'; /** * String quotes style. @@ -40,7 +40,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $valueQuotes = '\''; + public string $valueQuotes = '\''; /** * System identifier. @@ -48,7 +48,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '"'; + public string $systemIdentifierStart = '"'; /** * System identifier. @@ -56,7 +56,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = '"'; + public string $systemIdentifierEnd = '"'; /** * And operator. @@ -64,7 +64,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $and = 'AND'; + public string $and = 'AND'; /** * Or operator. @@ -72,7 +72,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $or = 'OR'; + public string $or = 'OR'; /** * Special keywords. @@ -80,7 +80,7 @@ abstract class GrammarAbstract * @var string[] * @since 1.0.0 */ - protected array $specialKeywords = [ + public array $specialKeywords = [ 'COUNT(', 'MAX(', 'MIN(', @@ -96,7 +96,7 @@ abstract class GrammarAbstract * @var string * @since 1.0.0 */ - protected string $datetimeFormat = 'Y-m-d H:i:s'; + public string $datetimeFormat = 'Y-m-d H:i:s'; /** * Set the datetime format diff --git a/DataStorage/Database/Mapper/DataMapperAbstract.php b/DataStorage/Database/Mapper/DataMapperAbstract.php index 05f7c4840..a84480d16 100644 --- a/DataStorage/Database/Mapper/DataMapperAbstract.php +++ b/DataStorage/Database/Mapper/DataMapperAbstract.php @@ -28,20 +28,81 @@ use phpOMS\DataStorage\Database\Query\OrderType; */ abstract class DataMapperAbstract { + /** + * Base mapper + * + * @var DataMapperFactory + * @since 1.0.0 + */ protected DataMapperFactory $mapper; + /** + * Mapper type (e.g. writer, reader, ...) + * + * @var int + * @since 1.0.0 + */ protected int $type = 0; + /** + * Mapper depths. + * + * Mappers may have relations to other models (e.g. hasMany) which can have other relations, ... + * The depths indicates how deep in the relation tree we are + * + * @var int + * @since 1.0.0 + */ protected int $depth = 1; + /** + * Relations which should be loaded + * + * @var array + * @since 1.0.0 + */ protected array $with = []; + /** + * Sort order + * + * @var array + * @since 1.0.0 + */ protected array $sort = []; + /** + * Offset + * + * @var array + * @since 1.0.0 + */ + protected array $offset = []; + + /** + * Limit + * + * @var array + * @since 1.0.0 + */ protected array $limit = []; + /** + * Where conditions + * + * @var array + * @since 1.0.0 + */ protected array $where = []; + /** + * Base query which is merged with the query in the mapper + * + * Sometimes you want to merge two queries together. + * + * @var null|Builder + * @since 1.0.0 + */ protected ?Builder $query = null; /** @@ -52,12 +113,28 @@ abstract class DataMapperAbstract */ protected ConnectionAbstract $db; + /** Constructor. + * + * @param DataMapperFactory $mapper Base mapper + * @param ConnectionAbstract $db Database connection + * + * @since 1.0.0 + */ public function __construct(DataMapperFactory $mapper, ConnectionAbstract $db) { $this->mapper = $mapper; $this->db = $db; } + /** + * Define a query which is merged with the internal query generation. + * + * @param Builder $query Query + * + * @return static + * + * @since 1.0.0 + */ public function query(Builder $query = null) : self { $this->query = $query; @@ -65,7 +142,15 @@ abstract class DataMapperAbstract return $this; } - // Only for relations, no impact on anything else + /** + * Define model relations which should be loaded + * + * @param string $member Property name of the relation (e.g. hasMany, belongsTo, ...) + * + * @return static + * + * @since 1.0.0 + */ public function with(string $member) : self { $split = \explode('/', $member); @@ -78,6 +163,16 @@ abstract class DataMapperAbstract return $this; } + /** + * Sort order + * + * @param string $member Property name to sort by + * @param string $order Order type (DESC/ASC) + * + * @return static + * + * @since 1.0.0 + */ public function sort(string $member, string $order = OrderType::DESC) : self { $split = \explode('/', $member); @@ -91,6 +186,39 @@ abstract class DataMapperAbstract return $this; } + /** + * Define the result offset + * + * @param int $offset Offset + * @param string $member Property name to offset by ('' = base model, anything else for relations such as hasMany relations) + * + * @return static + * + * @since 1.0.0 + */ + public function offset(int $offset = 0, string $member = '') : self + { + $split = \explode('/', $member); + $memberSplit = \array_shift($split); + + $this->offset[$memberSplit ?? ''][] = [ + 'child' => \implode('/', $split), + 'offset' => $offset, + ]; + + return $this; + } + + /** + * Define the result limit + * + * @param int $limit Limit + * @param string $member Property name to limit by ('' = base model, anything else for relations such as hasMany relations) + * + * @return static + * + * @since 1.0.0 + */ public function limit(int $limit = 0, string $member = '') : self { $split = \explode('/', $member); @@ -104,7 +232,19 @@ abstract class DataMapperAbstract return $this; } - public function where(string $member, mixed $value, string $logic = '=', string $comparison = 'AND') : self + /** + * Define the result filtering + * + * @param string $member Property name to filter by + * @param mixed $value Filter value + * @param string $logic Comparison logic (e.g. =, in, ...) + * @param string $connector Filter connector (e.g. AND, OR, ...) + * + * @return static + * + * @since 1.0.0 + */ + public function where(string $member, mixed $value, string $logic = '=', string $connector = 'AND') : self { $split = \explode('/', $member); $memberSplit = \array_shift($split); @@ -113,12 +253,22 @@ abstract class DataMapperAbstract 'child' => \implode('/', $split), 'value' => $value, 'logic' => $logic, - 'comparison' => $comparison, + 'comparison' => $connector, ]; return $this; } + /** + * Populate a mapper (e.g. child mapper, relation mapper) based on the current mapper information. + * + * @param DataMapperAbstract $mapper Relation mapper to populate + * @param string $member Relation property (e.g. ownsOne, hasMany, ... property name) + * + * @return self + * + * @since 1.0.0 + */ public function createRelationMapper(self $mapper, string $member) : self { $relMapper = $mapper; @@ -143,6 +293,16 @@ abstract class DataMapperAbstract } } + if (isset($this->offset[$member])) { + foreach ($this->offset[$member] as $offset) { + if ($offset['child'] === '') { + continue; + } + + $relMapper->offset($offset['offset'], $offset['child']); + } + } + if (isset($this->limit[$member])) { foreach ($this->limit[$member] as $limit) { if ($limit['child'] === '') { @@ -201,5 +361,14 @@ abstract class DataMapperAbstract return $value; } + /** + * Execute mapper + * + * @param mixed ...$options Data for the mapper + * + * @return mixed + * + * @since 1.0.0 + */ abstract public function execute(...$options) : mixed; } diff --git a/DataStorage/Database/Mapper/DataMapperFactory.php b/DataStorage/Database/Mapper/DataMapperFactory.php index c88adf5bb..3928fee52 100644 --- a/DataStorage/Database/Mapper/DataMapperFactory.php +++ b/DataStorage/Database/Mapper/DataMapperFactory.php @@ -142,7 +142,7 @@ class DataMapperFactory * @since 1.0.0 * @codeCoverageIgnore */ - private function __construct() + final private function __construct() { } @@ -158,73 +158,199 @@ class DataMapperFactory { } - public static function db(ConnectionAbstract $db = null) : string + /** + * Set default database connection + * + * @param ConnectionAbstract $db Database connection + * + * @return class-string + * + * @since 1.0.0 + */ + public static function db(ConnectionAbstract $db) : string { self::$db = $db; return static::class; } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function reader(ConnectionAbstract $db = null) : ReadMapper { return new ReadMapper(new static(), $db ?? self::$db); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function get(ConnectionAbstract $db = null) : ReadMapper { return (new ReadMapper(new static(), $db ?? self::$db))->get(); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function getRaw(ConnectionAbstract $db = null) : ReadMapper { return (new ReadMapper(new static(), $db ?? self::$db))->getRaw(); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function getRandom(ConnectionAbstract $db = null) : ReadMapper { return (new ReadMapper(new static(), $db ?? self::$db))->getRandom(); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function count(ConnectionAbstract $db = null) : ReadMapper { return (new ReadMapper(new static(), $db ?? self::$db))->count(); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return Builder + * + * @since 1.0.0 + */ public static function getQuery(ConnectionAbstract $db = null) : Builder { return (new ReadMapper(new static(), $db ?? self::$db))->getQuery(); } + /** + * Create read mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return ReadMapper + * + * @since 1.0.0 + */ public static function getAll(ConnectionAbstract $db = null) : ReadMapper { return (new ReadMapper(new static(), $db ?? self::$db))->getAll(); } + /** + * Create write mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return WriteMapper + * + * @since 1.0.0 + */ public static function writer(ConnectionAbstract $db = null) : WriteMapper { return new WriteMapper(new static(), $db ?? self::$db); } + /** + * Create write mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return WriteMapper + * + * @since 1.0.0 + */ public static function create(ConnectionAbstract $db = null) : WriteMapper { return (new WriteMapper(new static(), $db ?? self::$db))->create(); } + /** + * Create update mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return UpdateMapper + * + * @since 1.0.0 + */ public static function updater(ConnectionAbstract $db = null) : UpdateMapper { return new UpdateMapper(new static(), $db ?? self::$db); } + /** + * Create update mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return UpdateMapper + * + * @since 1.0.0 + */ public static function update(ConnectionAbstract $db = null) : UpdateMapper { return (new UpdateMapper(new static(), $db ?? self::$db))->update(); } + /** + * Create delete mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return DeleteMapper + * + * @since 1.0.0 + */ public static function remover(ConnectionAbstract $db = null) : DeleteMapper { return new DeleteMapper(new static(), $db ?? self::$db); } + /** + * Create delete mapper + * + * @param ConnectionAbstract $db Database connection + * + * @return DeleteMapper + * + * @since 1.0.0 + */ public static function delete(ConnectionAbstract $db = null) : DeleteMapper { return (new DeleteMapper(new static(), $db ?? self::$db))->delete(); @@ -267,11 +393,11 @@ class DataMapperFactory /** * Create the empty base model * - * @return mixed + * @return object * * @since 1.0.0 */ - public static function createBaseModel() : mixed + public static function createBaseModel() : object { $class = empty(static::MODEL) ? \substr(static::class, 0, -6) : static::MODEL; @@ -283,18 +409,6 @@ class DataMapperFactory return new $class(); } - /** - * Get model from mapper - * - * @return string - * - * @since 1.0.0 - */ - public static function getModelName() : string - { - return empty(static::MODEL) ? \substr(static::class, 0, -6) : static::MODEL; - } - /** * Get id of object * diff --git a/DataStorage/Database/Mapper/DeleteMapper.php b/DataStorage/Database/Mapper/DeleteMapper.php index e51ad2dd7..bd2dd15e1 100644 --- a/DataStorage/Database/Mapper/DeleteMapper.php +++ b/DataStorage/Database/Mapper/DeleteMapper.php @@ -14,7 +14,6 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database\Mapper; -use phpOMS\DataStorage\Database\Exception\InvalidMapperException; use phpOMS\DataStorage\Database\Query\Builder; /** @@ -27,8 +26,15 @@ use phpOMS\DataStorage\Database\Query\Builder; * @link https://orange-management.org * @since 1.0.0 */ -class DeleteMapper extends DataMapperAbstract +final class DeleteMapper extends DataMapperAbstract { + /** + * Get delete mapper + * + * @return self + * + * @since 1.0.0 + */ public function delete() : self { $this->type = MapperType::DELETE; @@ -36,22 +42,37 @@ class DeleteMapper extends DataMapperAbstract return $this; } + /** + * Execute mapper + * + * @param array ...$options Options to pass to the selete mapper + * + * @return mixed + * + * @since 1.0.0 + */ public function execute(...$options) : mixed { switch($this->type) { case MapperType::DELETE: + /** @var object[] ...$options */ return $this->executeDelete(...$options); default: return null; } } - public function executeDelete(mixed $obj) : mixed + /** + * Execute mapper + * + * @param object $obj Object to delete + * + * @return mixed + * + * @since 1.0.0 + */ + public function executeDelete(object $obj) : mixed { - if ($obj === null) { - $obj = $this->mapper::get()->execute(); // todo: pass where conditions to read mapper - } - $refClass = new \ReflectionClass($obj); $objId = $this->mapper::getObjectId($obj, $refClass); @@ -67,6 +88,15 @@ class DeleteMapper extends DataMapperAbstract return $objId; } + /** + * Delete model + * + * @param mixed $objId Object id to delete + * + * @return void + * + * @since 1.0.0 + */ private function deleteModel(mixed $objId) : void { $query = new Builder($this->db); @@ -80,7 +110,18 @@ class DeleteMapper extends DataMapperAbstract } } - private function deleteSingleRelation(mixed $obj, \ReflectionClass $refClass, array $relation) : void + /** + * Delete ownsOne, belongsTo relations + * + * @param object $obj Object to delete + * @param \ReflectionClass $refClass Reflection of object to delete + * @param array $relation Relation data (e.g. ::BELONGS_TO, ::OWNS_ONE) + * + * @return void + * + * @since 1.0.0 + */ + private function deleteSingleRelation(object $obj, \ReflectionClass $refClass, array $relation) : void { if (empty($relation)) { return; @@ -95,7 +136,7 @@ class DeleteMapper extends DataMapperAbstract $mapper = $relData['mapper']; /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member); + $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member); $relMapper->depth = $this->depth + 1; $refProp = $refClass->getProperty($member); @@ -109,6 +150,17 @@ class DeleteMapper extends DataMapperAbstract } } + /** + * Delete hasMany + * + * @param \ReflectionClass $refClass Reflection of object to delete + * @param object $obj Object to delete + * @param mixed $objId Object id to delete + * + * @return void + * + * @since 1.0.0 + */ private function deleteHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void { if (empty($this->mapper::HAS_MANY)) { @@ -121,7 +173,7 @@ class DeleteMapper extends DataMapperAbstract continue; } - $objIds = []; + $objIds = []; $refProp = $refClass->getProperty($member); if (!$refProp->isPublic()) { $refProp->setAccessible(true); @@ -165,6 +217,17 @@ class DeleteMapper extends DataMapperAbstract } } + /** + * Delete has many relations if the relation is handled in a relation table + * + * @param string $member Property which contains the has many models + * @param array $objIds Objects which are related to the parent object + * @param mixed $objId Parent object id + * + * @return void + * + * @since 1.0.0 + */ public function deleteRelationTable(string $member, array $objIds = null, mixed $objId) : void { if ((empty($objIds) && $objIds !== null) diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index c7b2406d5..c145d9752 100644 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -25,10 +25,25 @@ use phpOMS\Utils\ArrayUtils; * @link https://orange-management.org * @since 1.0.0 */ -class ReadMapper extends DataMapperAbstract +final class ReadMapper extends DataMapperAbstract { - private $columns = []; + /** + * Columns to load + * + * @var array + * @since 1.0.0 + */ + private array $columns = []; + /** + * Create get mapper + * + * This makes execute() return a single object or an array of object depending the result size + * + * @return self + * + * @since 1.0.0 + */ public function get() : self { $this->type = MapperType::GET; @@ -36,6 +51,13 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Get raw result set + * + * @return self + * + * @since 1.0.0 + */ public function getRaw() : self { $this->type = MapperType::GET_RAW; @@ -43,6 +65,15 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Create get mapper + * + * This makes execute() always return an array of objects (or an empty array) + * + * @return self + * + * @since 1.0.0 + */ public function getAll() : self { $this->type = MapperType::GET_ALL; @@ -50,6 +81,13 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Create count mapper + * + * @return self + * + * @since 1.0.0 + */ public function count() : self { $this->type = MapperType::COUNT_MODELS; @@ -57,6 +95,13 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Create random mapper + * + * @return self + * + * @since 1.0.0 + */ public function getRandom() : self { $this->type = MapperType::GET_RANDOM; @@ -64,6 +109,13 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Create find mapper + * + * @return self + * + * @since 1.0.0 + */ public function find() : self { $this->type = MapperType::FIND; @@ -71,6 +123,16 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Define the columns to load + * + * @param array $columns Columns to load + * + * @return self + * + * @since 1.0.0 + * @todo: consider to accept properties instead and then check ::COLUMNS which contian the property and ADD that array into $this->columns. Maybe also consider a rename from columns() to property() + */ public function columns(array $columns) : self { $this->columns = $columns; @@ -78,18 +140,30 @@ class ReadMapper extends DataMapperAbstract return $this; } + /** + * Execute mapper + * + * @param mixed ...$options Options to pass to read mapper + * + * @return mixed + * + * @since 1.0.0 + */ public function execute(...$options) : mixed { switch($this->type) { case MapperType::GET: + /** @var null|Builder ...$options */ return $options !== null ? $this->executeGet(...$options) : $this->executeGet(); case MapperType::GET_RAW: + /** @var null|Builder ...$options */ return $options !== null ? $this->executeGetRaw(...$options) : $this->executeGetRaw(); case MapperType::GET_ALL: + /** @var null|Builder ...$options */ return $options !== null ? $this->executeGetAll(...$options) : $this->executeGetAll(); @@ -102,15 +176,24 @@ class ReadMapper extends DataMapperAbstract } } - // @todo: consider to always return an array, this way we could remove executeGetAll + /** + * Execute mapper + * + * @param null|Builder $query Query to use instead of the internally generated query (carefuly, this doesn't merge with the internal query. If you want to merge it use ->query() instead) + * + * @return object|array + * + * @todo: consider to always return an array, this way we could remove executeGetAll + * @since 1.0.0 + */ public function executeGet(Builder $query = null) : mixed { - $primaryKeys = []; + $primaryKeys = []; $memberOfPrimaryField = $this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal']; - $emptyWhere = empty($this->where); + $emptyWhere = empty($this->where); if (isset($this->where[$memberOfPrimaryField])) { - $keys = $this->where[$memberOfPrimaryField][0]['value']; + $keys = $this->where[$memberOfPrimaryField][0]['value']; $primaryKeys = \array_merge(\is_array($keys) ? $keys : [$keys], $primaryKeys); } @@ -141,6 +224,15 @@ class ReadMapper extends DataMapperAbstract return $obj; } + /** + * Execute mapper + * + * @param null|Builder $query Query to use instead of the internally generated query (carefuly, this doesn't merge with the internal query. If you want to merge it use ->query() instead) + * + * @return array + * + * @since 1.0.0 + */ public function executeGetRaw(Builder $query = null) : array { $query ??= $this->getQuery(); @@ -162,6 +254,15 @@ class ReadMapper extends DataMapperAbstract return $results === false ? [] : $results; } + /** + * Execute mapper + * + * @param null|Builder $query Query to use instead of the internally generated query (carefuly, this doesn't merge with the internal query. If you want to merge it use ->query() instead) + * + * @return array + * + * @since 1.0.0 + */ public function executeGetAll(Builder $query = null) : array { $result = $this->executeGet($query); @@ -205,8 +306,8 @@ class ReadMapper extends DataMapperAbstract /** * Get mapper specific builder * - * @param Builder $query Query to fill - * @param array $columns Columns to use + * @param null|Builder $query Query to fill + * @param array $columns Columns to use * * @return Builder * @@ -223,7 +324,7 @@ class ReadMapper extends DataMapperAbstract if (\is_string($values)) { $query->selectAs($key, $values); } else { - if (($values['writeonly'] ?? false) === false) { + if (($values['writeonly'] ?? false) === false || isset($this->with[$values['internal']])) { $query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth); } } @@ -235,9 +336,9 @@ class ReadMapper extends DataMapperAbstract // where foreach ($this->where as $member => $values) { - if(($col = $this->mapper::getColumnByMember($member)) !== null) { + if (($col = $this->mapper::getColumnByMember($member)) !== null) { /* variable in model */ - foreach ($values as $index => $where) { + foreach ($values as $where) { // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails if ($where['child'] !== '') { continue; @@ -248,7 +349,8 @@ class ReadMapper extends DataMapperAbstract } } elseif (isset($this->mapper::HAS_MANY[$member])) { /* variable in has many */ - foreach ($values as $index => $where) { + /* @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment + foreach ($values as $where) { // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails if ($where['child'] !== '') { continue; @@ -272,8 +374,10 @@ class ReadMapper extends DataMapperAbstract ); } } + */ } elseif (isset($this->mapper::BELONGS_TO[$member])) { /* variable in belogns to */ + /* @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment foreach ($values as $index => $where) { // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails if ($where['child'] !== '') { @@ -290,8 +394,10 @@ class ReadMapper extends DataMapperAbstract $this->mapper::BELONGS_TO[$member]['mapper']::TABLE . '_d' . $this->depth ); } + */ } elseif (isset($this->mapper::OWNS_ONE[$member])) { /* variable in owns one */ + /* @todo: maybe needed in the future, but needs adjustment, doesn't make sense at the moment foreach ($values as $index => $where) { // @todo: the has many, etc. if checks only work if it is a relation on the first level, if we have a deeper where condition nesting this fails if ($where['child'] !== '') { @@ -307,6 +413,7 @@ class ReadMapper extends DataMapperAbstract $this->mapper::OWNS_ONE[$member]['mapper']::TABLE . '_d' . $this->depth ); } + */ } } @@ -350,7 +457,7 @@ class ReadMapper extends DataMapperAbstract } /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($rel['mapper']::reader(db: $this->db), $member); + $relMapper = $this->createRelationMapper($rel['mapper']::reader(db: $this->db), $member); $relMapper->depth = $this->depth + 1; $query = $relMapper->getQuery( @@ -398,16 +505,14 @@ class ReadMapper extends DataMapperAbstract /** * Populate data. * - * @param array $result Query result set - * @param mixed $obj Object to populate + * @param array $result Query result set + * @param object $obj Object to populate * - * @return mixed - * - * @throws \UnexpectedValueException + * @return object * * @since 1.0.0 */ - public function populateAbstract(array $result, mixed $obj) : mixed + public function populateAbstract(array $result, object $obj) : object { $refClass = new \ReflectionClass($obj); @@ -627,8 +732,6 @@ class ReadMapper extends DataMapperAbstract } } - // @todo: MUST handle if member is in with here!!! - if (isset($this->mapper::OWNS_ONE[$member]['column'])) { return $result[$mapper::getColumnByMember($this->mapper::OWNS_ONE[$member]['column']) . '_d' . $this->depth]; } @@ -637,8 +740,8 @@ class ReadMapper extends DataMapperAbstract return $mapper::createNullModel(); } - /** @var class-string $ownsOneMapper */ - $ownsOneMapper = $this->createRelationMapper($mapper::get($this->db), $member); + /** @var self $ownsOneMapper */ + $ownsOneMapper = $this->createRelationMapper($mapper::get($this->db), $member); $ownsOneMapper->depth = $this->depth + 1; return $ownsOneMapper->populateAbstract($result, $mapper::createBaseModel()); @@ -663,7 +766,7 @@ class ReadMapper extends DataMapperAbstract /** @var class-string $mapper */ $mapper = $this->mapper::BELONGS_TO[$member]['mapper']; - if (!isset($this->with[$member])) { + if (!isset($this->with[$member])) { if (\array_key_exists($this->mapper::BELONGS_TO[$member]['external'] . '_d' . ($this->depth), $result)) { return isset($this->mapper::BELONGS_TO[$member]['column']) ? $result[$this->mapper::BELONGS_TO[$member]['external'] . '_d' . ($this->depth)] @@ -673,8 +776,6 @@ class ReadMapper extends DataMapperAbstract } } - // @todo: MUST handle if member is in with here!!! ??? - if (isset($this->mapper::BELONGS_TO[$member]['column'])) { return $result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['column']) . '_d' . $this->depth]; } @@ -688,16 +789,16 @@ class ReadMapper extends DataMapperAbstract // you want the profile but the account id is referenced // in this case you can get the profile by loading the profile based on the account reference column if (isset($this->mapper::BELONGS_TO[$member]['by'])) { - /** @var class-string $belongsToMapper */ - $belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member); + /** @var self $belongsToMapper */ + $belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member); $belongsToMapper->depth = $this->depth + 1; $belongsToMapper->where($this->mapper::BELONGS_TO[$member]['by'], $result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['by']) . '_d' . $this->depth + 1], '='); return $belongsToMapper->execute(); } - /** @var class-string $belongsToMapper */ - $belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member); + /** @var self $belongsToMapper */ + $belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member); $belongsToMapper->depth = $this->depth + 1; return $belongsToMapper->populateAbstract($result, $mapper::createBaseModel()); @@ -706,13 +807,13 @@ class ReadMapper extends DataMapperAbstract /** * Fill object with relations * - * @param mixed $obj Object to fill + * @param object $obj Object to fill * * @return void * * @since 1.0.0 */ - public function loadHasManyRelations(mixed $obj) : void + public function loadHasManyRelations(object $obj) : void { if (empty($this->with)) { return; @@ -770,15 +871,15 @@ class ReadMapper extends DataMapperAbstract $refProp = $refClass->getProperty($member); if (!$refProp->isPublic()) { $refProp->setAccessible(true); - $refProp->setValue($obj, !\is_array($objects) + $refProp->setValue($obj, !\is_array($objects) && ($many['conditional'] ?? false) === false ? [$many['mapper']::getObjectId($objects) => $objects] - : $objects + : $objects // if conditional === true the obj will be asigned (e.g. has many localizations but only one is loaded for the model) ); $refProp->setAccessible(false); } else { - $obj->{$member} = !\is_array($objects) + $obj->{$member} = !\is_array($objects) && ($many['conditional'] ?? false) === false ? [$many['mapper']::getObjectId($objects) => $objects] - : $objects; + : $objects; // if conditional === true the obj will be asigned (e.g. has many localizations but only one is loaded for the model) } continue; diff --git a/DataStorage/Database/Mapper/UpdateMapper.php b/DataStorage/Database/Mapper/UpdateMapper.php index a96cc3413..90a945aa2 100644 --- a/DataStorage/Database/Mapper/UpdateMapper.php +++ b/DataStorage/Database/Mapper/UpdateMapper.php @@ -21,16 +21,20 @@ use phpOMS\Utils\ArrayUtils; /** * Update mapper (CREATE). * - * @todo: allow to define single fields which should be updated (e.g. only description) - * @todo: allow to define where clause if no object is loaded yet - * * @package phpOMS\DataStorage\Database\Mapper * @license OMS License 1.0 * @link https://orange-management.org * @since 1.0.0 */ -class UpdateMapper extends DataMapperAbstract +final class UpdateMapper extends DataMapperAbstract { + /** + * Create update mapper + * + * @return self + * + * @since 1.0.0 + */ public function update() : self { $this->type = MapperType::UPDATE; @@ -38,22 +42,37 @@ class UpdateMapper extends DataMapperAbstract return $this; } + /** + * Execute mapper + * + * @param mixed ...$options Options to pass to update mapper + * + * @return mixed + * + * @since 1.0.0 + */ public function execute(...$options) : mixed { switch($this->type) { case MapperType::UPDATE: + /** @var object ...$options */ return $this->executeUpdate(...$options); default: return null; } } - public function executeUpdate(mixed $obj) : mixed + /** + * Execute mapper + * + * @param object $obj Object to update + * + * @return mixed + * + * @since 1.0.0 + */ + public function executeUpdate(object $obj) : mixed { - if (!isset($obj)) { - return null; - } - $refClass = new \ReflectionClass($obj); $objId = $this->mapper::getObjectId($obj, $refClass); @@ -72,6 +91,17 @@ class UpdateMapper extends DataMapperAbstract return $objId; } + /** + * Update model + * + * @param object $obj Object to update + * @param mixed $objId Id of the object to update + * @param \ReflectionClass $refClass Reflection of the object ot update + * + * @return void + * + * @since 1.0.0 + */ private function updateModel(object $obj, mixed $objId, \ReflectionClass $refClass = null) : void { // Model doesn't have anything to update @@ -87,7 +117,8 @@ class UpdateMapper extends DataMapperAbstract $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal']; if (isset($this->mapper::HAS_MANY[$propertyName]) || $column['internal'] === $this->mapper::PRIMARYFIELD - || ($column['readonly'] ?? false === true) + || (($column['readonly'] ?? false) === true && !isset($this->with[$propertyName])) + || (($column['writeonly'] ?? false) === true && !isset($this->with[$propertyName])) ) { continue; } @@ -95,7 +126,7 @@ class UpdateMapper extends DataMapperAbstract $refClass = $refClass ?? new \ReflectionClass($obj); $property = $refClass->getProperty($propertyName); - if (!($isPublic = $property->isPublic())) { + if (!($property->isPublic())) { $property->setAccessible(true); $tValue = $property->getValue($obj); $property->setAccessible(false); @@ -104,26 +135,14 @@ class UpdateMapper extends DataMapperAbstract } if (isset($this->mapper::OWNS_ONE[$propertyName])) { - $id = $this->updateOwnsOne($propertyName, $tValue); + $id = \is_object($tValue) ? $this->updateOwnsOne($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); - /** - * @todo Orange-Management/phpOMS#232 - * If a model gets updated all it's relations are also updated. - * This should be prevented if the relations didn't change. - * No solution yet. - */ $query->set([$this->mapper::TABLE . '.' . $column['name'] => $value]); } elseif (isset($this->mapper::BELONGS_TO[$propertyName])) { - $id = $this->updateBelongsTo($propertyName, $tValue); + $id = \is_object($tValue) ? $this->updateBelongsTo($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); - /** - * @todo Orange-Management/phpOMS#232 - * If a model gets updated all it's relations are also updated. - * This should be prevented if the relations didn't change. - * No solution yet. - */ $query->set([$this->mapper::TABLE . '.' . $column['name'] => $value]); } elseif ($column['name'] !== $this->mapper::PRIMARYFIELD) { if (\stripos($column['internal'], '/') !== false) { @@ -143,46 +162,70 @@ class UpdateMapper extends DataMapperAbstract $sth->execute(); } } catch (\Throwable $t) { + // @codeCoverageIgnoreStart echo $t->getMessage(); echo $query->toSql(); + // @codeCoverageIgnoreEnd } } - private function updateBelongsTo(string $propertyName, mixed $obj) : mixed + /** + * Update belongs to + * + * @param string $propertyName Name of the property to update + * @param object $obj Object to update + * + * @return mixed + * + * @since 1.0.0 + */ + private function updateBelongsTo(string $propertyName, object $obj) : mixed { - if (!\is_object($obj)) { - return $obj; - } - /** @var class-string $mapper */ $mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper']; /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); + $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); $relMapper->depth = $this->depth + 1; return $relMapper->execute($obj); } - private function updateOwnsOne(string $propertyName, mixed $obj) : mixed + /** + * Update owns one + * + * @param string $propertyName Name of the property to update + * @param object $obj Object to update + * + * @return mixed + * + * @since 1.0.0 + */ + private function updateOwnsOne(string $propertyName, object $obj) : mixed { - if (!\is_object($obj)) { - return $obj; - } - /** @var class-string $mapper */ $mapper = $this->mapper::OWNS_ONE[$propertyName]['mapper']; /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); + $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); $relMapper->depth = $this->depth + 1; return $relMapper->execute($obj); } + /** + * Update has many relations + * + * @param \ReflectionClass $refClass Reflection of the object containing the relations + * @param object $obj Object which contains the relations + * @param mixed $objId Object id which contains the relations + * + * @return void + * + * @since 1.0.0 + */ private function updateHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void { - // @todo: what if has_one has a has_many child (see readmapper, we already solved this here) if (empty($this->with) || empty($this->mapper::HAS_MANY)) { return; } @@ -230,7 +273,7 @@ class UpdateMapper extends DataMapperAbstract // already in db if (!empty($primaryKey)) { /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); + $relMapper = $this->createRelationMapper($mapper::update(db: $this->db), $propertyName); $relMapper->depth = $this->depth + 1; $relMapper->execute($value); @@ -255,13 +298,23 @@ class UpdateMapper extends DataMapperAbstract } } - $objsIds[$propertyName][$key] = $mapper::create(db: $this->db)->execute($value); // @todo: pass where + $objsIds[$propertyName][$key] = $mapper::create(db: $this->db)->execute($value); } } $this->updateRelationTable($objsIds, $objId); } + /** + * Update has many relations if the relation is handled in a relation table + * + * @param array $objsIds Objects which should be related to the parent object + * @param mixed $objId Parent object id + * + * @return void + * + * @since 1.0.0 + */ private function updateRelationTable(array $objsIds, mixed $objId) : void { foreach ($this->mapper::HAS_MANY as $member => $many) { @@ -288,7 +341,7 @@ class UpdateMapper extends DataMapperAbstract } $sth->execute(); - $result = $sth->fetchAll(\PDO::FETCH_COLUMN); + $result = $sth->fetchAll(\PDO::FETCH_COLUMN); $removes = \array_diff($result, \array_values($objsIds[$member] ?? [])); $adds = \array_diff(\array_values($objsIds[$member] ?? []), $result); diff --git a/DataStorage/Database/Mapper/WriteMapper.php b/DataStorage/Database/Mapper/WriteMapper.php index 1315cda18..8e0eecc76 100644 --- a/DataStorage/Database/Mapper/WriteMapper.php +++ b/DataStorage/Database/Mapper/WriteMapper.php @@ -27,8 +27,15 @@ use phpOMS\Utils\ArrayUtils; * @link https://orange-management.org * @since 1.0.0 */ -class WriteMapper extends DataMapperAbstract +final class WriteMapper extends DataMapperAbstract { + /** + * Create create mapper + * + * @return self + * + * @since 1.0.0 + */ public function create() : self { $this->type = MapperType::CREATE; @@ -36,22 +43,37 @@ class WriteMapper extends DataMapperAbstract return $this; } + /** + * Execute mapper + * + * @param mixed ...$options Model to create + * + * @return mixed + * + * @since 1.0.0 + */ public function execute(...$options) : mixed { switch($this->type) { case MapperType::CREATE: + /** @var object ...$options */ return $this->executeCreate(...$options); default: return null; } } - public function executeCreate(mixed $obj) : mixed + /** + * Create object + * + * @param object $obj Object to create + * + * @return mixed + * + * @since 1.0.0 + */ + public function executeCreate(object $obj) : mixed { - if (!isset($obj)) { - return null; - } - $refClass = new \ReflectionClass($obj); if ($this->mapper::isNullModel($obj)) { @@ -72,6 +94,16 @@ class WriteMapper extends DataMapperAbstract return $objId; } + /** + * Create model + * + * @param object $obj Object to create + * @param \ReflectionClass $refClass Reflection of the object to create + * + * @return mixed + * + * @since 1.0.0 + */ private function createModel(object $obj, \ReflectionClass $refClass) : mixed { $query = new Builder($this->db); @@ -93,12 +125,12 @@ class WriteMapper extends DataMapperAbstract } if (isset($this->mapper::OWNS_ONE[$propertyName])) { - $id = $this->createOwnsOne($propertyName, $tValue); + $id = \is_object($tValue) ? $this->createOwnsOne($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); $query->insert($column['name'])->value($value); } elseif (isset($this->mapper::BELONGS_TO[$propertyName])) { - $id = $this->createBelongsTo($propertyName, $tValue); + $id = \is_object($tValue) ? $this->createBelongsTo($propertyName, $tValue) : $tValue; $value = $this->parseValue($column['type'], $id); $query->insert($column['name'])->value($value); @@ -131,9 +163,12 @@ class WriteMapper extends DataMapperAbstract $sth = $this->db->con->prepare($query->toSql()); $sth->execute(); } catch (\Throwable $t) { + // @codeCoverageIgnoreStart \var_dump($t->getMessage()); \var_dump($a = $query->toSql()); + return -1; + // @codeCoverageIgnoreEND } $objId = empty($id = $this->mapper::getObjectId($obj, $refClass)) ? $this->db->con->lastInsertId() : $id; @@ -142,7 +177,17 @@ class WriteMapper extends DataMapperAbstract return $objId; } - private function createOwnsOne(string $propertyName, mixed $obj) : mixed + /** + * Create owns one model + * + * @param string $propertyName Name of the owns one property + * @param object $obj Object which contains the owns one model + * + * @return mixed + * + * @since 1.0.0 + */ + private function createOwnsOne(string $propertyName, object $obj) : mixed { if (!\is_object($obj)) { return $obj; @@ -159,7 +204,17 @@ class WriteMapper extends DataMapperAbstract return $primaryKey; } - private function createBelongsTo(string $propertyName, mixed $obj) : mixed + /** + * Create belongs to model + * + * @param string $propertyName Name of the belongs to property + * @param object $obj Object which contains the belongs to model + * + * @return mixed + * + * @since 1.0.0 + */ + private function createBelongsTo(string $propertyName, object $obj) : mixed { if (!\is_object($obj)) { return $obj; @@ -187,15 +242,26 @@ class WriteMapper extends DataMapperAbstract $mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper']; $primaryKey = $mapper::getObjectId($obj); - // @todo: the $mapper::create() might cause a problem is 'by' is set. because we don't want to create this obj but the child obj. + // @todo: the $mapper::create() might cause a problem if 'by' is set. because we don't want to create this obj but the child obj. return empty($primaryKey) ? $mapper::create(db: $this->db)->execute($obj) : $primaryKey; } + /** + * Create has many models + * + * @param \ReflectionClass $refClass Reflection of the object to create + * @param object $obj Object to create + * @param mixed $objId Id of the parent object + * + * @return void + * + * @since 1.0.0 + */ private function createHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void { foreach ($this->mapper::HAS_MANY as $propertyName => $rel) { if (!isset($this->mapper::HAS_MANY[$propertyName]['mapper'])) { - throw new InvalidMapperException(); + throw new InvalidMapperException(); // @codeCoverageIgnore } $property = $refClass->getProperty($propertyName); @@ -303,6 +369,17 @@ class WriteMapper extends DataMapperAbstract } } + /** + * Create has many relations if the relation is handled in a relation table + * + * @param string $propertyName Property which contains the has many models + * @param array $objsIds Objects which should be related to the parent object + * @param mixed $objId Parent object id + * + * @return void + * + * @since 1.0.0 + */ public function createRelationTable(string $propertyName, array $objsIds, mixed $objId) : void { if (empty($objsIds) || !isset($this->mapper::HAS_MANY[$propertyName]['external'])) { @@ -332,8 +409,10 @@ class WriteMapper extends DataMapperAbstract $sth->execute(); } } catch (\Throwable $e) { + // @codeCoverageIgnoreStart \var_dump($e->getMessage()); \var_dump($relQuery->toSql()); + // @codeCoverageIgnoreEnd } } } diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index 375386528..c0ff045f7 100644 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -1307,10 +1307,6 @@ class Builder extends BuilderAbstract */ public function on(string | array $columns, string | array $operator = null, string | array $values = null, string | array $boolean = 'and', string $table = null) : self { - if ($operator !== null && !\is_array($operator) && !\in_array(\strtolower($operator), self::OPERATORS)) { - throw new \InvalidArgumentException('Unknown operator.'); - } - if (!\is_array($columns)) { $columns = [$columns]; $operator = [$operator]; @@ -1408,10 +1404,12 @@ class Builder extends BuilderAbstract $sth->execute(); } catch (\Throwable $t) { + // @codeCoverageIgnoreStart \var_dump($t->getMessage()); \var_dump($this->toSql()); $sth = null; + // @codeCoverageIgnoreEnd } return $sth; diff --git a/DataStorage/Database/Query/Grammar/MysqlGrammar.php b/DataStorage/Database/Query/Grammar/MysqlGrammar.php index 1e02879de..9c3defb61 100644 --- a/DataStorage/Database/Query/Grammar/MysqlGrammar.php +++ b/DataStorage/Database/Query/Grammar/MysqlGrammar.php @@ -32,7 +32,7 @@ class MysqlGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '`'; + public string $systemIdentifierStart = '`'; /** * System identifier. @@ -40,7 +40,7 @@ class MysqlGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = '`'; + public string $systemIdentifierEnd = '`'; /** * Compile random. diff --git a/DataStorage/Database/Query/Grammar/SQLiteGrammar.php b/DataStorage/Database/Query/Grammar/SQLiteGrammar.php index 2631118b2..7b43b3acf 100644 --- a/DataStorage/Database/Query/Grammar/SQLiteGrammar.php +++ b/DataStorage/Database/Query/Grammar/SQLiteGrammar.php @@ -40,7 +40,7 @@ class SQLiteGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = '`'; + public string $systemIdentifierEnd = '`'; /** * Compile random. diff --git a/DataStorage/Database/Query/Grammar/SqlServerGrammar.php b/DataStorage/Database/Query/Grammar/SqlServerGrammar.php index d545f88ff..a16cdcedc 100644 --- a/DataStorage/Database/Query/Grammar/SqlServerGrammar.php +++ b/DataStorage/Database/Query/Grammar/SqlServerGrammar.php @@ -32,7 +32,7 @@ class SqlServerGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '['; + public string $systemIdentifierStart = '['; /** * System identifier. @@ -40,7 +40,7 @@ class SqlServerGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = ']'; + public string $systemIdentifierEnd = ']'; /** * Compile random. diff --git a/DataStorage/Database/Schema/Grammar/MysqlGrammar.php b/DataStorage/Database/Schema/Grammar/MysqlGrammar.php index 9f5d797c4..cafc231bc 100644 --- a/DataStorage/Database/Schema/Grammar/MysqlGrammar.php +++ b/DataStorage/Database/Schema/Grammar/MysqlGrammar.php @@ -33,7 +33,7 @@ class MysqlGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '`'; + public string $systemIdentifierStart = '`'; /** * System identifier. @@ -41,7 +41,7 @@ class MysqlGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = '`'; + public string $systemIdentifierEnd = '`'; /** * Compile remove diff --git a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php index cd61fd73d..cb270fda9 100644 --- a/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php +++ b/DataStorage/Database/Schema/Grammar/SQLiteGrammar.php @@ -30,7 +30,7 @@ class SQLiteGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '`'; + public string $systemIdentifierStart = '`'; /** * System identifier. @@ -38,5 +38,5 @@ class SQLiteGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = '`'; + public string $systemIdentifierEnd = '`'; } diff --git a/DataStorage/Database/Schema/Grammar/SqlServerGrammar.php b/DataStorage/Database/Schema/Grammar/SqlServerGrammar.php index a2814df33..18feac63d 100644 --- a/DataStorage/Database/Schema/Grammar/SqlServerGrammar.php +++ b/DataStorage/Database/Schema/Grammar/SqlServerGrammar.php @@ -30,7 +30,7 @@ class SqlServerGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierStart = '['; + public string $systemIdentifierStart = '['; /** * System identifier. @@ -38,5 +38,5 @@ class SqlServerGrammar extends Grammar * @var string * @since 1.0.0 */ - protected string $systemIdentifierEnd = ']'; + public string $systemIdentifierEnd = ']'; } diff --git a/Localization/Defaults/CountryMapper.php b/Localization/Defaults/CountryMapper.php index 87a25e756..6d6fbbcf6 100644 --- a/Localization/Defaults/CountryMapper.php +++ b/Localization/Defaults/CountryMapper.php @@ -33,13 +33,13 @@ class CountryMapper extends DataMapperFactory * @since 1.0.0 */ public const COLUMNS = [ - 'country_id' => ['name' => 'country_id', 'type' => 'int', 'internal' => 'id'], - 'country_name' => ['name' => 'country_name', 'type' => 'string', 'internal' => 'name'], - 'country_code2' => ['name' => 'country_code2', 'type' => 'string', 'internal' => 'code2'], - 'country_code3' => ['name' => 'country_code3', 'type' => 'string', 'internal' => 'code3'], - 'country_numeric' => ['name' => 'country_numeric', 'type' => 'int', 'internal' => 'numeric'], - 'country_region' => ['name' => 'country_region', 'type' => 'string', 'internal' => 'region'], - 'country_developed' => ['name' => 'country_developed', 'type' => 'bool', 'internal' => 'isDeveloped'], + 'country_id' => ['name' => 'country_id', 'type' => 'int', 'internal' => 'id'], + 'country_name' => ['name' => 'country_name', 'type' => 'string', 'internal' => 'name'], + 'country_code2' => ['name' => 'country_code2', 'type' => 'string', 'internal' => 'code2'], + 'country_code3' => ['name' => 'country_code3', 'type' => 'string', 'internal' => 'code3'], + 'country_numeric' => ['name' => 'country_numeric', 'type' => 'int', 'internal' => 'numeric'], + 'country_region' => ['name' => 'country_region', 'type' => 'string', 'internal' => 'region'], + 'country_developed' => ['name' => 'country_developed', 'type' => 'bool', 'internal' => 'isDeveloped'], ]; /** diff --git a/Localization/NullLocalization.php b/Localization/NullLocalization.php index c3280400a..b2295ba97 100644 --- a/Localization/NullLocalization.php +++ b/Localization/NullLocalization.php @@ -24,6 +24,13 @@ namespace phpOMS\Localization; */ final class NullLocalization extends Localization { + /** + * Constructor + * + * @param int $id Model id + * + * @since 1.0.0 + */ public function __construct(int $id = 0) { $this->id = $id; diff --git a/Module/ModuleManager.php b/Module/ModuleManager.php index cf1df90b0..20c28c2db 100644 --- a/Module/ModuleManager.php +++ b/Module/ModuleManager.php @@ -639,10 +639,15 @@ final class ModuleManager * * @param string $module Module name * - * @return \phpOMS\Module\ModuleAbstract + * @return object|\phpOMS\Module\ModuleAbstract * * @throws \Exception * + * @todo Remove docblock type hint hack "object". + * The return type object is only used to stop the annoying warning that a method doesn't exist + * if you chain call the methods part of the returned ModuleAbstract implementation. + * Remove it once alternative inline type hinting is possible for the specific returned implementation + * * @since 1.0.0 */ public function get(string $module) : ModuleAbstract diff --git a/System/File/Ftp/File.php b/System/File/Ftp/File.php index 380d4f7f7..815ca8c24 100644 --- a/System/File/Ftp/File.php +++ b/System/File/Ftp/File.php @@ -125,28 +125,29 @@ class File extends FileAbstract implements FileInterface { $exists = self::exists($con, $path); - // @todo: consider to use the php://memory way, used in the seUpBeforeClass in the test if ((ContentPutMode::hasFlag($mode, ContentPutMode::APPEND) && $exists) || (ContentPutMode::hasFlag($mode, ContentPutMode::PREPEND) && $exists) || (ContentPutMode::hasFlag($mode, ContentPutMode::REPLACE) && $exists) || (!$exists && ContentPutMode::hasFlag($mode, ContentPutMode::CREATE)) ) { - $tmpFile = 'file' . \mt_rand(); if (ContentPutMode::hasFlag($mode, ContentPutMode::APPEND) && $exists) { - \file_put_contents($tmpFile, self::get($con, $path) . $content); + $content = self::get($con, $path) . $content; } elseif (ContentPutMode::hasFlag($mode, ContentPutMode::PREPEND) && $exists) { - \file_put_contents($tmpFile, $content . self::get($con, $path)); + $content = $content . self::get($con, $path); } else { if (!Directory::exists($con, \dirname($path))) { Directory::create($con, \dirname($path), 0755, true); } - - \file_put_contents($tmpFile, $content); } - \ftp_put($con, $path, $tmpFile, \FTP_BINARY); + $fp = \fopen('php://memory', 'r+'); + \fwrite($fp, $content); + \rewind($fp); + + \ftp_fput($con, $path, $fp); + \fclose($fp); + \ftp_chmod($con, 0755, $path); - \unlink($tmpFile); return true; } diff --git a/tests/DataStorage/Database/DataMapperAbstractTest.php b/tests/DataStorage/Database/DataMapperAbstractTest.php index 1822784d5..c3e50a077 100644 --- a/tests/DataStorage/Database/DataMapperAbstractTest.php +++ b/tests/DataStorage/Database/DataMapperAbstractTest.php @@ -39,55 +39,7 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase */ protected function setUp() : void { - $this->model = new BaseModel(); - $this->modelArray = [ - 'id' => 0, - 'string' => 'Base', - 'int' => 11, - 'bool' => false, - 'null' => null, - 'float' => 1.3, - 'json' => [1, 2, 3], - 'jsonSerializable' => new class() implements \JsonSerializable { - public function jsonSerialize() - { - return [1, 2, 3]; - } - }, - 'datetime' => new \DateTime('2005-10-11'), - 'datetime_null' => null, - 'conditional' => '', - 'ownsOneSelf' => [ - 'id' => 0, - 'string' => 'OwnsOne', - ], - 'belongsToOne' => [ - 'id' => 0, - 'string' => 'BelongsTo', - ], - 'hasManyDirect' => [ - [ - 'id' => 0, - 'string' => 'ManyToManyDirect', - 'to' => 0, - ], - [ - 'id' => 0, - 'string' => 'ManyToManyDirect', - 'to' => 0, - ], - ], - 'hasManyRelations' => [ - [ - 'id' => 0, - 'string' => 'ManyToManyRel', - ], - [ - 'id' => 0, - 'string' => 'ManyToManyRel', - ], - ], - ]; + $this->model = new BaseModel(); $GLOBALS['dbpool']->get()->con->prepare( 'CREATE TABLE `test_base` ( @@ -231,10 +183,17 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase */ public function testRead() : void { - $id = BaseModelMapper::create()->execute($this->model); + $id = BaseModelMapper::create()->execute($this->model); /** @var BaseModel $modelR */ - $modelR = BaseModelMapper::get()->where('id', $id)->execute(); + $modelR = BaseModelMapper::get() + ->with('belongsToOne') + ->with('ownsOneSelf') + ->with('hasManyDirect') + ->with('hasMnayRelations') + ->with('conditional') + ->where('id', $id) + ->execute(); self::assertEquals($this->model->getId(), $modelR->getId()); self::assertEquals($this->model->string, $modelR->string); @@ -292,19 +251,6 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertEquals($model2->getId(), $by->getId()); } - public function testGetCached() : void - { - $id = BaseModelMapper::create()->execute($this->model); - - /** @var BaseModel $modelR */ - $modelR = BaseModelMapper::get()->where('id', $id)->execute(); - - /** @var BaseModel $modelR2 */ - $modelR2 = BaseModelMapper::get()->where('id', $id)->execute(); - - self::assertEquals($modelR->getId(), $modelR2->getId()); - } - public function testGetNewest() : void { $model1 = new BaseModel(); @@ -415,7 +361,7 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase public function testUpdate() : void { $id = BaseModelMapper::create()->execute($this->model); - $modelR = BaseModelMapper::get()->where('id', $id)->execute(); + $modelR = BaseModelMapper::get()->with('hasManyDirect')->with('hasManyRelations')->where('id', $id)->execute(); $modelR->string = 'Update'; $modelR->int = '321'; @@ -425,7 +371,7 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $modelR->datetime = new \DateTime('now'); $modelR->datetime_null = null; - $id2 = BaseModelMapper::update()->execute($modelR); + $id2 = BaseModelMapper::update()->with('hasManyDirect')->with('hasManyRelations')->execute($modelR); $modelR2 = BaseModelMapper::get()->where('id', $id2)->execute(); self::assertEquals($modelR->string, $modelR2->string); @@ -435,11 +381,6 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertEquals($modelR->null, $modelR2->null); self::assertEquals($modelR->datetime->format('Y-m-d'), $modelR2->datetime->format('Y-m-d')); self::assertNull($modelR2->datetime_null); - - /** - * @todo Orange-Management/phpOMS#226 - * Test the update of a model with relations (update relations). - */ } /** @@ -454,17 +395,11 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase */ public function testDelete() : void { - /* $id = BaseModelMapper::create()->execute($this->model); - BaseModelMapper::delete($this->model); + BaseModelMapper::delete()->with('belongsToOne')->with('ownsOneSelf')->with('hasManyRelations')->execute($this->model); $modelR = BaseModelMapper::get()->where('id', $id)->execute(); self::assertInstanceOf('phpOMS\tests\DataStorage\Database\TestModel\NullBaseModel', $modelR); - */ - /** - * @todo Orange-Management/phpOMS#225 - * Test the deletion of a model with relations (deleting relations). - */ } public function testDeleteHasManyRelation() : void @@ -482,4 +417,24 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertCount($count1 - 1, $base->hasManyRelations); } + + public function testReader() : void + { + self::assertInstanceOf('phpOMS\DataStorage\Database\Mapper\ReadMapper', BaseModelMapper::reader()); + } + + public function testWriter() : void + { + self::assertInstanceOf('phpOMS\DataStorage\Database\Mapper\WriteMapper', BaseModelMapper::writer()); + } + + public function testUpdater() : void + { + self::assertInstanceOf('phpOMS\DataStorage\Database\Mapper\UpdateMapper', BaseModelMapper::updater()); + } + + public function testRemover() : void + { + self::assertInstanceOf('phpOMS\DataStorage\Database\Mapper\DeleteMapper', BaseModelMapper::remover()); + } } diff --git a/tests/DataStorage/Database/Query/BuilderTest.php b/tests/DataStorage/Database/Query/BuilderTest.php index 5018588ee..1b6e73775 100644 --- a/tests/DataStorage/Database/Query/BuilderTest.php +++ b/tests/DataStorage/Database/Query/BuilderTest.php @@ -15,6 +15,9 @@ declare(strict_types=1); namespace phpOMS\tests\DataStorage\Database\Query; use phpOMS\DataStorage\Database\Connection\MysqlConnection; +use phpOMS\DataStorage\Database\Connection\PostgresConnection; +use phpOMS\DataStorage\Database\Connection\SQLiteConnection; +use phpOMS\DataStorage\Database\Connection\SqlServerConnection; use phpOMS\DataStorage\Database\Query\Builder; use phpOMS\DataStorage\Database\Query\Parameter; @@ -25,45 +28,56 @@ use phpOMS\DataStorage\Database\Query\Parameter; */ final class BuilderTest extends \PHPUnit\Framework\TestCase { - protected $con; - - /** - * {@inheritdoc} - */ - protected function setUp() : void + public function dbConnectionProvider() : array { - $this->con = new MysqlConnection($GLOBALS['CONFIG']['db']['core']['masters']['admin']); + return [ + [new MysqlConnection($GLOBALS['CONFIG']['db']['core']['masters']['admin'])], + [new PostgresConnection($GLOBALS['CONFIG']['db']['core']['postgresql']['admin'])], + [new SQLiteConnection($GLOBALS['CONFIG']['db']['core']['sqlite']['admin'])], + [new SqlServerConnection($GLOBALS['CONFIG']['db']['core']['mssql']['admin'])], + ]; } /** * @testdox Mysql selects form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlSelect() : void + public function testSelect($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` as t FROM `a` as b WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] as t FROM [a] as b WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->selectAs('a.test', 't')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT DISTINCT `a`.`test` FROM `a` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT DISTINCT [a].[test] FROM [a] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->distinct()->from('a')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test`, `b`.`test` FROM `a`, `b` WHERE `a`.`test` = \'abc\';'; + $query = new Builder($con); + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'abc\';'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', 'abc')->toSql()); - $query = new Builder($this->con); + $query = new Builder($con); $datetime = new \DateTime('now'); - $sql = 'SELECT `a`.`test`, `b`.`test` FROM `a`, `b` WHERE `a`.`test` = \'' . $datetime->format('Y-m-d H:i:s') . '\';'; + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'' . $datetime->format('Y-m-d H:i:s') + . '\';'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test', 'b.test')->from('a', 'b')->where('a.test', '=', $datetime)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test`, `b`.`test` FROM `a`, `b` WHERE `a`.`test` = \'abc\' ORDER BY `a`.`test` ASC, `b`.`test` DESC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = \'abc\' ORDER BY [a].[test] ASC, [b].[test] DESC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test', 'b.test') ->from('a', 'b') @@ -72,8 +86,9 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase ->toSql() ); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test`, `b`.`test` FROM `a`, `b` WHERE `a`.`test` = :abcValue ORDER BY `a`.`test` ASC, `b`.`test` DESC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test], [b].[test] FROM [a], [b] WHERE [a].[test] = :abcValue ORDER BY [a].[test] ASC, [b].[test] DESC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test', 'b.test') ->from('a', 'b') @@ -83,429 +98,598 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase ); self::assertEquals($query->toSql(), $query->__toString()); + } - $query = new Builder($this->con); + public function testRandomMysql() : void + { + $con = new MysqlConnection($GLOBALS['CONFIG']['db']['core']['masters']['admin']); + + $query = new Builder($con); $sql = 'SELECT `a`.`test` FROM `a` as b WHERE `a`.`test` = 1 ORDER BY \rand() LIMIT 1;'; self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); } + public function testRandomPostgresql() : void + { + $con = new PostgresConnection($GLOBALS['CONFIG']['db']['core']['postgresql']['admin']); + + $query = new Builder($con); + $sql = 'SELECT "a"."test" FROM "a" as b ORDER BY RANDOM() LIMIT 1;'; + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + public function testRandomSQLite() : void + { + $con = new SQLiteConnection($GLOBALS['CONFIG']['db']['core']['sqlite']['admin']); + + $query = new Builder($con); + $sql = 'SELECT `a`.`test` FROM `a` as b ORDER BY RANDOM() LIMIT 1;'; + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + + public function testRandomSqlServer() : void + { + $con = new SqlServerConnection($GLOBALS['CONFIG']['db']['core']['mssql']['admin']); + + $query = new Builder($con); + $sql = 'SELECT TOP 1 [a].[test] FROM [a] as b ORDER BY IDX FETCH FIRST 1 ROWS ONLY;'; + self::assertEquals($sql, $query->random('a.test')->fromAs('a', 'b')->where('a.test', '=', 1)->toSql()); + } + /** * @testdox Mysql orders form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlOrder() : void + public function testOrder($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` DESC;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->newest('a.test')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` ASC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->oldest('a.test')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` DESC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'DESC')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` ASC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy('a.test', 'ASC')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` DESC, `a`.`test2` DESC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] DESC, [a].[test2] DESC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], ['DESC', 'DESC'])->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 ORDER BY `a`.`test` ASC, `a`.`test2` ASC;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 ORDER BY [a].[test] ASC, [a].[test2] ASC;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orderBy(['a.test', 'a.test2'], 'ASC')->toSql()); } /** * @testdox Mysql offsets and limits form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlOffsetLimit() : void + public function testOffsetLimit($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 LIMIT 3;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 LIMIT 3;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->limit(3)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OFFSET 3;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OFFSET 3;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->offset(3)->toSql()); } /** * @testdox Mysql groupings form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlGroup() : void + public function testGroup($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 GROUP BY `a`;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 GROUP BY [a];'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 GROUP BY `a`, `b`;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 GROUP BY [a], [b];'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a')->groupBy('b')->toSql()); - $query = new Builder($this->con); + $query = new Builder($con); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->groupBy('a', 'b')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = :test GROUP BY `a`, `b`;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :test GROUP BY [a], [b];'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('test'))->groupBy('a', 'b')->toSql()); } /** * @testdox Mysql wheres form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlWheres() : void + public function testWheres($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 0;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 0;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', false)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', true)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = \'string\';'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = \'string\';'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 'string')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1.23;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1.23;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1.23)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 AND `a`.`test2` = 2;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 AND [a].[test2] = 2;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'and')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 AND `a`.`test2` = 2;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 AND [a].[test2] = 2;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->andWhere('a.test2', '=', 2)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` = 2;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] = 2;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->where('a.test2', '=', 2, 'or')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` = 2;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] = 2;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->orWhere('a.test2', '=', 2)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` IS NULL;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IS NULL;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNull('a.test2', 'or')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` IS NOT NULL;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IS NOT NULL;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereNotNull('a.test2', 'or')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` IN (1, 2, 3);'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IN (1, 2, 3);'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', [1, 2, 3], 'or')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = 1 OR `a`.`test2` IN (\'a\', \'b\', \'c\');'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = 1 OR [a].[test2] IN (\'a\', \'b\', \'c\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', 1)->whereIn('a.test2', ['a', 'b', 'c'], 'or')->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` WHERE `a`.`test` = :testWhere OR `a`.`test2` IN (\'a\', :bValue, \'c\');'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] WHERE [a].[test] = :testWhere OR [a].[test2] IN (\'a\', :bValue, \'c\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', new Parameter('testWhere'))->whereIn('a.test2', ['a', new Parameter('bValue'), 'c'], 'or')->toSql()); } /** * @testdox Mysql joins form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlJoins() : void + public function testJoins($con) : void { - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` OR `a`.`id2` = `b`.`id2` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] OR [a].[id2] = [b].[id2] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->orOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` AND `a`.`id2` = `b`.`id2` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] JOIN [b] ON [a].[id] = [b].[id] AND [a].[id2] = [b].[id2] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->andOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` LEFT JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] LEFT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->leftJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` LEFT OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] LEFT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->leftOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` LEFT INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] LEFT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->leftInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` RIGHT JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] RIGHT JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->rightJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` RIGHT OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] RIGHT OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->rightOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` RIGHT INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] RIGHT INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->rightInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->outerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] INNER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->innerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` CROSS JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] CROSS JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->crossJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` FULL JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] FULL JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->fullJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'SELECT `a`.`test` FROM `a` FULL OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'SELECT [a].[test] FROM [a] FULL OUTER JOIN [b] ON [a].[id] = [b].[id] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->select('a.test')->from('a')->fullOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); } /** * @testdox Mysql inserts form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlInsert() : void + public function testInsert($con) : void { - $query = new Builder($this->con); - $sql = 'INSERT INTO `a` VALUES (1, \'test\');'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'INSERT INTO [a] VALUES (1, \'test\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->insert()->into('a')->values(1, 'test')->toSql()); - $query = new Builder($this->con); - $sql = 'INSERT INTO `a` VALUES (1, \'test\');'; + $query = new Builder($con); + $sql = 'INSERT INTO [a] VALUES (1, \'test\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->insert()->into('a')->value([1, 'test'])->toSql()); - $query = new Builder($this->con); - $sql = 'INSERT INTO `a` (`test`, `test2`) VALUES (1, \'test\');'; + $query = new Builder($con); + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (1, \'test\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->toSql()); self::assertEquals([[1, 'test']], $query->getValues()); - $query = new Builder($this->con); - $sql = 'INSERT INTO `a` (`test`, `test2`) VALUES (1, \'test\'), (2, \'test2\');'; + $query = new Builder($con); + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (1, \'test\'), (2, \'test2\');'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->values(2, 'test2')->toSql()); - $query = new Builder($this->con); - $sql = 'INSERT INTO `a` (`test`, `test2`) VALUES (:test, :test2);'; + $query = new Builder($con); + $sql = 'INSERT INTO [a] ([test], [test2]) VALUES (:test, :test2);'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(new Parameter('test'), new Parameter('test2'))->toSql()); } /** * @testdox Mysql deletes form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlDelete() : void + public function testDelete($con) : void { - $query = new Builder($this->con); - $sql = 'DELETE FROM `a` WHERE `a`.`test` = 1;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'DELETE FROM [a] WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'DELETE FROM `a` WHERE `a`.`test` = :testVal;'; + $query = new Builder($con); + $sql = 'DELETE FROM [a] WHERE [a].[test] = :testVal;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->delete()->from('a')->where('a.test', '=', new Parameter('testVal'))->toSql()); } /** * @testdox Mysql updates form a valid query * @group framework + * @dataProvider dbConnectionProvider */ - public function testMysqlUpdate() : void + public function testUpdate($con) : void { - $query = new Builder($this->con); - $sql = 'UPDATE `a` SET `a`.`test` = 1, `a`.`test2` = 2 WHERE `a`.`test` = 1;'; + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); + $sql = 'UPDATE [a] SET [a].[test] = 1, [a].[test2] = 2 WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->update('a')->set(['a.test' => 1])->set(['a.test2' => 2])->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'UPDATE `a` SET `a`.`test` = 1, `a`.`test2` = 2 WHERE `a`.`test` = 1;'; + $query = new Builder($con); + $sql = 'UPDATE [a] SET [a].[test] = 1, [a].[test2] = 2 WHERE [a].[test] = 1;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->update('a')->sets('a.test', 1)->sets('a.test2', 2)->where('a.test', '=', 1)->toSql()); - $query = new Builder($this->con); - $sql = 'UPDATE `a` SET `a`.`test` = 1, `a`.`test2` = :test2 WHERE `a`.`test` = :test3;'; + $query = new Builder($con); + $sql = 'UPDATE [a] SET [a].[test] = 1, [a].[test2] = :test2 WHERE [a].[test] = :test3;'; + $sql = \str_replace(['[', ']'], [$iS, $iE], $sql); self::assertEquals($sql, $query->update('a')->set(['a.test' => 1])->set(['a.test2' => new Parameter('test2')])->where('a.test', '=', new Parameter('test3'))->toSql()); } /** * @testdox Raw queries get output as defined * @group framework + * @dataProvider dbConnectionProvider */ - public function testRawInputOutput() : void + public function testRawInputOutput($con) : void { - $query = new Builder($this->con); + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con); self::assertEquals('SELECT test.val FROM test;', $query->raw('SELECT test.val FROM test;')->toSql()); } /** * @testdox Read only queries allow selects * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyRawSelect() : void + public function testReadOnlyRawSelect($con) : void { - $query = new Builder($this->con, true); + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + + $query = new Builder($con, true); self::assertInstanceOf(Builder::class, $query->raw('SELECT * from oms;')); } /** * @testdox Read only queries don't allow drops * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyRawDrop() : void + public function testReadOnlyRawDrop($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->raw('DROP DATABASE oms;'); } /** * @testdox Read only queries don't allow deletes * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyRawDelete() : void + public function testReadOnlyRawDelete($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->raw('DELETE oms;'); } /** * @testdox Read only queries don't allow creates * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyRawCreate() : void + public function testReadOnlyRawCreate($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->raw('CREATE oms;'); } /** * @testdox Read only queries don't allow modifications * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyRawAlter() : void + public function testReadOnlyRawAlter($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->raw('ALTER oms;'); } /** * @testdox Read only queries don't allow inserts * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyInsert() : void + public function testReadOnlyInsert($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->insert('test'); } /** * @testdox Read only queries don't allow updates * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyUpdate() : void + public function testReadOnlyUpdate($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->update(); } /** * @testdox Read only queries don't allow deletes * @group framework + * @dataProvider dbConnectionProvider */ - public function testReadOnlyDelete() : void + public function testReadOnlyDelete($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\Exception::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->delete(); } /** * @testdox Invalid select types throw a InvalidArgumentException * @group framework + * @dataProvider dbConnectionProvider */ - public function testInvalidSelectParameter() : void + public function testInvalidSelectParameter($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\InvalidArgumentException::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->select(false); } /** * @testdox Invalid from types throw a InvalidArgumentException * @group framework + * @dataProvider dbConnectionProvider */ - public function testInvalidFromParameter() : void + public function testInvalidFromParameter($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\InvalidArgumentException::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->from(false); } /** * @testdox Invalid group types throw a InvalidArgumentException * @group framework + * @dataProvider dbConnectionProvider */ - public function testInvalidGroupByParameter() : void + public function testInvalidGroupByParameter($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\InvalidArgumentException::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->groupBy(false); } /** * @testdox Invalid where operators throw a InvalidArgumentException * @group framework + * @dataProvider dbConnectionProvider */ - public function testInvalidWhereOperator() : void + public function testInvalidWhereOperator($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\InvalidArgumentException::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->where('a', 'invalid', 'b'); } /** * @testdox Invalid join operators throw a InvalidArgumentException * @group framework + * @dataProvider dbConnectionProvider */ - public function testInvalidJoinOperator() : void + public function testInvalidJoinOperator($con) : void { + $iS = $con->getGrammar()->systemIdentifierStart; + $iE = $con->getGrammar()->systemIdentifierEnd; + $this->expectException(\InvalidArgumentException::class); - $query = new Builder($this->con, true); + $query = new Builder($con, true); $query->join('b')->on('a', 'invalid', 'b'); } } diff --git a/tests/DataStorage/Database/TestModel/BaseModelMapper.php b/tests/DataStorage/Database/TestModel/BaseModelMapper.php index 8335d2d37..fc0a19c0c 100644 --- a/tests/DataStorage/Database/TestModel/BaseModelMapper.php +++ b/tests/DataStorage/Database/TestModel/BaseModelMapper.php @@ -47,15 +47,15 @@ class BaseModelMapper extends DataMapperFactory */ public const BELONGS_TO = [ 'belongsToOne' => [ - 'mapper' => BelongsToModelMapper::class, - 'external' => 'test_base_belongs_to_one', + 'mapper' => BelongsToModelMapper::class, + 'external' => 'test_base_belongs_to_one', ], ]; public const OWNS_ONE = [ 'ownsOneSelf' => [ - 'mapper' => OwnsOneModelMapper::class, - 'external' => 'test_base_owns_one_self', + 'mapper' => OwnsOneModelMapper::class, + 'external' => 'test_base_owns_one_self', ], ]; @@ -67,29 +67,47 @@ class BaseModelMapper extends DataMapperFactory */ public const HAS_MANY = [ 'hasManyDirect' => [ - 'mapper' => ManyToManyDirectModelMapper::class, - 'table' => 'test_has_many_direct', - 'self' => 'test_has_many_direct_to', - 'external' => null, + 'mapper' => ManyToManyDirectModelMapper::class, + 'table' => 'test_has_many_direct', + 'self' => 'test_has_many_direct_to', + 'external' => null, ], 'hasManyRelations' => [ - 'mapper' => ManyToManyRelModelMapper::class, - 'table' => 'test_has_many_rel_relations', - 'external' => 'test_has_many_rel_relations_src', - 'self' => 'test_has_many_rel_relations_dest', + 'mapper' => ManyToManyRelModelMapper::class, + 'table' => 'test_has_many_rel_relations', + 'external' => 'test_has_many_rel_relations_src', + 'self' => 'test_has_many_rel_relations_dest', ], 'conditional' => [ - 'mapper' => ConditionalMapper::class, - 'table' => 'test_conditional', - 'self' => 'test_conditional_base', - 'column' => 'title', - 'external' => null, + 'mapper' => ConditionalMapper::class, + 'table' => 'test_conditional', + 'self' => 'test_conditional_base', + 'column' => 'title', + 'external' => null, ], ]; + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ public const TABLE = 'test_base'; + /** + * Created at. + * + * @var string + * @since 1.0.0 + */ public const CREATED_AT = 'test_base_datetime'; + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ public const PRIMARYFIELD ='test_base_id'; } diff --git a/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php b/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php index cd577dea2..fb2ffe1bf 100644 --- a/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php +++ b/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php @@ -29,7 +29,19 @@ class BelongsToModelMapper extends DataMapperFactory 'test_belongs_to_one_string' => ['name' => 'test_belongs_to_one_string', 'type' => 'string', 'internal' => 'string'], ]; + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ public const TABLE = 'test_belongs_to_one'; + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ public const PRIMARYFIELD ='test_belongs_to_one_id'; } diff --git a/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php b/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php index ed65c4fe0..7986f171c 100644 --- a/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php +++ b/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php @@ -29,7 +29,19 @@ class ManyToManyDirectModelMapper extends DataMapperFactory 'test_has_many_direct_to' => ['name' => 'test_has_many_direct_to', 'type' => 'int', 'internal' => 'to'], ]; + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ public const TABLE = 'test_has_many_direct'; + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ public const PRIMARYFIELD ='test_has_many_direct_id'; } diff --git a/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php b/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php index 8ef9271ec..13838ebb1 100644 --- a/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php +++ b/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php @@ -29,7 +29,19 @@ class ManyToManyRelModelMapper extends DataMapperFactory 'test_has_many_rel_string' => ['name' => 'test_has_many_rel_string', 'type' => 'string', 'internal' => 'string'], ]; + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ public const TABLE = 'test_has_many_rel'; + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ public const PRIMARYFIELD ='test_has_many_rel_id'; } diff --git a/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php b/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php index 0a181fc34..70750a355 100644 --- a/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php +++ b/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php @@ -29,7 +29,19 @@ class OwnsOneModelMapper extends DataMapperFactory 'test_owns_one_string' => ['name' => 'test_owns_one_string', 'type' => 'string', 'internal' => 'string'], ]; + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ public const TABLE = 'test_owns_one'; + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ public const PRIMARYFIELD ='test_owns_one_id'; } diff --git a/tests/Views/testArray.tpl.php b/tests/Views/testArray.tpl.php index 803203256..39811f285 100644 --- a/tests/Views/testArray.tpl.php +++ b/tests/Views/testArray.tpl.php @@ -1,2 +1,14 @@ -Test';