From aa004569f50fb88b509848725ce8969ed9282d5a Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sat, 11 Dec 2021 11:54:17 +0100 Subject: [PATCH] new datamapper mostly implemented --- DataStorage/Database/DataMapperAbstract.php | 3798 ----------------- .../Database/Mapper/DataMapperAbstract.php | 12 +- .../Database/Mapper/DataMapperFactory.php | 89 +- DataStorage/Database/Mapper/DeleteMapper.php | 197 +- DataStorage/Database/Mapper/ReadMapper.php | 158 +- DataStorage/Database/Mapper/UpdateMapper.php | 5 +- DataStorage/Database/Mapper/WriteMapper.php | 28 +- Localization/Defaults/CityMapper.php | 10 +- Localization/Defaults/CountryMapper.php | 10 +- Localization/Defaults/CurrencyMapper.php | 10 +- Localization/Defaults/IbanMapper.php | 10 +- Localization/Defaults/LanguageMapper.php | 10 +- Localization/NullLocalization.php | 4 + Module/ModuleAbstract.php | 12 +- Router/SocketRouter.php | 4 +- tests/Bootstrap.php | 4 +- .../Database/DataMapperAbstractTest.php | 241 +- .../Database/TestModel/BaseModelMapper.php | 20 +- .../TestModel/BelongsToModelMapper.php | 11 +- .../Database/TestModel/ConditionalMapper.php | 10 +- .../TestModel/ManyToManyDirectModelMapper.php | 10 +- .../TestModel/ManyToManyRelModelMapper.php | 11 +- .../Database/TestModel/OwnsOneModelMapper.php | 11 +- .../Localization/Defaults/CityMapperTest.php | 10 +- .../Defaults/CountryMapperTest.php | 10 +- .../Defaults/CurrencyMapperTest.php | 10 +- .../Localization/Defaults/IbanMapperTest.php | 10 +- .../Defaults/LanguageMapperTest.php | 10 +- tests/Module/ModuleAbstractTest.php | 33 +- tests/Module/ModuleManagerTest.php | 9 +- tests/Socket/Client/ClientTestHelper.php | 4 +- 31 files changed, 401 insertions(+), 4370 deletions(-) delete mode 100644 DataStorage/Database/DataMapperAbstract.php diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php deleted file mode 100644 index 36d1a705f..000000000 --- a/DataStorage/Database/DataMapperAbstract.php +++ /dev/null @@ -1,3798 +0,0 @@ - The tags without l11n are not shown in the list and therefor the user doesn't know about them and cannot fix them. (wrong join type?) - * If the defined conditional doesn't exist (e.g. language) the element is not shown at all. - * This can be a problem if the user wants the conditional as preferred result - * but also accepts alternatives if nothing exists for this conditional but for other conditionals. E.g. - * * News article doesn't exist in the defined l11n - * * However if the article exists in english language it should at least show in that language. - */ -class DataMapperAbstract implements DataMapperInterface -{ - /** - * Database connection. - * - * @var ConnectionAbstract - * @since 1.0.0 - */ - protected static ConnectionAbstract $db; - - /** - * Overwriting extended values. - * - * @var bool - * @since 1.0.0 - */ - protected static bool $overwrite = true; - - /** - * Primary field name. - * - * @var string - * @since 1.0.0 - */ - protected static string $primaryField = ''; - - /** - * Autoincrement primary field. - * - * @var bool - * @since 1.0.0 - */ - protected static bool $autoincrement = true; - - /** - * Primary field name. - * - * @var string - * @since 1.0.0 - */ - protected static string $createdAt = ''; - - /** - * Columns. - * - * @var array - * @since 1.0.0 - */ - protected static array $columns = []; - - /** - * Has many relation. - * - * @var array - * @since 1.0.0 - */ - protected static array $hasMany = []; - - /** - * Relations. - * - * Relation is defined in current mapper - * - * @var array - * @since 1.0.0 - */ - protected static array $ownsOne = []; - - /** - * Belongs to. - * - * @var array - * @since 1.0.0 - */ - protected static array $belongsTo = []; - - /** - * Table. - * - * @var string - * @since 1.0.0 - */ - protected static string $table = ''; - - /** - * Parent column. - * - * @var string - * @since 1.0.0 - */ - protected static string $parent = ''; - - /** - * Model to use by the mapper. - * - * @var string - * @since 1.0.0 - */ - protected static string $model = ''; - - /** - * Fields to load. - * - * @var array[] - * @since 1.0.0 - */ - protected static array $withFields = []; - - /** - * Initialized objects for cross reference to reduce initialization costs - * - * @var array[] - * @since 1.0.0 - */ - protected static array $initObjects = []; - - /** - * Initialized arrays for cross reference to reduce initialization costs - * - * @var array[] - * @since 1.0.0 - */ - protected static array $initArrays = []; - - /** - * Highest mapper to know when to clear initialized objects - * - * @var null|string - * @since 1.0.0 - */ - protected static ?string $parentMapper = null; - - /** - * Relation type for nesting/joins - * - * @var int - * @since 1.0.0 - */ - protected static int $relations = RelationType::ALL; - - /** - * Datetime format of the database datetime - * - * This is only for the datetime stored in the database not the generated query. - * For the query check the datetime in Grammar:$datetimeFormat - * - * @var string - * @since 1.0.0 - */ - protected static string $datetimeFormat = 'Y-m-d H:i:s'; - - /** - * Raw query data from last query - * - * @var array - * @since 1.0.0 - */ - protected static array $lastQueryData = []; - - /** - * Fields to sort by. - * - * @var array[] - * @since 1.0.0 - */ - protected static array $sortFields = []; - - /** - * Constructor. - * - * @since 1.0.0 - * @codeCoverageIgnore - */ - private function __construct() - { - } - - /** - * Clone. - * - * @return void - * - * @since 1.0.0 - * @codeCoverageIgnore - */ - private function __clone() - { - } - - /** - * Set database connection. - * - * @param ConnectionAbstract $con Database connection - * - * @return void - * - * @since 1.0.0 - */ - public static function setConnection(ConnectionAbstract $con) : void - { - self::$db = $con; - } - - /** - * Get primary field. - * - * @return string - * - * @since 1.0.0 - */ - public static function getPrimaryField() : string - { - return static::$primaryField; - } - - /** - * Get main table. - * - * @return string - * - * @since 1.0.0 - */ - public static function getTable() : string - { - return static::$table; - } - - /** - * Create a conditional value - * - * @param string $id Id of the conditional - * @param mixed $value Value of the conditional - * @param string[] $models Models to apply the conditional on - * @param string $comparison Comparison operator - * @param string $orderBy Field name to order by - * @param string $sortOrder Sort order - * @param int $limit Limit - * - * @return string - * - * @since 1.0.0 - */ - public static function with( - string $id, - mixed $value = null, - ?array $models = [], - string $comparison = '=', - string $orderBy = null, - string $sortOrder = null, - int $limit = null - ) : string - { - // @todo: this doesn't allow a offset / secondary condition (e.g. between two values) - self::$withFields[$id] = [ - 'value' => $value, - 'models' => $models === [] ? null : $models, - 'comparison' => $comparison, - 'orderBy' => $orderBy, - 'sortOrder' => $sortOrder, - 'limit' => $limit, - 'ignore' => $models === null, // don't load this model - ]; - - // @todo: ignore seems to be a bug, models === null is true VERY often because i usually omit the models definition. Why is it still working, or is it? - - /** @var string */ - return static::class; - } - - /** - * Create a conditional value - * - * @param string $by Name of the variable to sort by - * @param string $order ASC or DESC - * @param string[] $models Models to apply the sort on - * - * @return string - * - * @since 1.0.0 - */ - public static function sortBy( - string $by, - string $order = 'DESC', - ?array $models = [], - ) : string - { - self::$sortFields[$by] = [ - 'order' => $order, - 'models' => $models === [] ? null : $models, - ]; - - /** @var string */ - return static::class; - } - - /** - * Resets all loaded mapper variables. - * - * This is used after one action is performed otherwise other models would use wrong settings. - * - * @return void - * - * @since 1.0.0 - */ - public static function clear() : void - { - // clear parent and objects - if (static::class !== self::$parentMapper) { - return; - } - - self::$parentMapper = null; - self::$withFields = []; - self::$sortFields = []; - self::$relations = RelationType::ALL; - } - - /** - * Find data. - * - * @param string $search Search for - * @param int $searchDepth Depth of the search - * @param Builder $query Query - * - * @return array - * - * @since 1.0.0 - */ - public static function find(string $search, int $searchDepth = 2, Builder $query = null) : array - { - $query = self::findQuery($search, $searchDepth, $query); - - return static::getAllByQuery($query, RelationType::ALL, $searchDepth); - } - - /** - * Find data query. - * - * @param string $search Search for - * @param int $searchDepth Depth of the search - * @param Builder $query Query - * - * @return Builder - * - * @since 1.0.0 - */ - public static function findQuery(string $search, int $searchDepth = 2, Builder $query = null) : Builder - { - $query ??= static::getQuery(null, [], RelationType::ALL, $searchDepth); - - $where1 = new Where(self::$db); - $where2 = new Where(self::$db); - - $modelName = self::getModelName(); - - $hasConditionals = false; - foreach (self::$withFields as $condKey => $condValue) { - if (($column = self::getColumnByMember($condKey)) === null - || ($condValue['models'] !== null && !\in_array($modelName, $condValue['models'])) - || $condValue['ignore'] - ) { - continue; - } - - if ($condValue['value'] !== null) { - $where1->andWhere(static::$table . '_d' . $searchDepth . '.' . $column, $condValue['comparison'], $condValue['value']); - } - - if ($condValue['orderBy'] !== null) { - $where1->orderBy(static::$table . '_d' . $searchDepth . '.' . static::getColumnByMember($condValue['orderBy']), $condValue['sortOrder']); - } - - if ($condValue['limit'] !== null) { - $where1->limit($condValue['limit']); - } - - $hasConditionals = true; - } - - $hasAutocompletes = false; - foreach (static::$columns as $col) { - if (isset($col['autocomplete']) && $col['autocomplete']) { - $where2->where(static::$table . '_d' . $searchDepth . '.' . $col['name'], 'LIKE', '%' . $search . '%', 'OR'); - $hasAutocompletes = true; - } - } - - if ($hasConditionals) { - $query->andWhere($where1); - } - - if ($hasAutocompletes) { - $query->orWhere($where2); - } - - if ($searchDepth > 2) { - foreach (static::$ownsOne as $one) { - $one['mapper']::findQuery($search, $searchDepth - 1, $query); - } - - foreach (static::$belongsTo as $belongs) { - $belongs['mapper']::findQuery($search, $searchDepth - 1, $query); - } - - foreach (static::$hasMany as $many) { - $many['mapper']::findQuery($search, $searchDepth - 1, $query); - } - } - - return $query; - } - - /** - * Create object in db. - * - * @param mixed $obj Object reference (gets filled with insert id) - * @param int $relations Create all relations as well - * - * @return mixed - * - * @since 1.0.0 - */ - public static function create(mixed $obj, int $relations = RelationType::ALL) : mixed - { - if (!isset($obj)) { - return null; - } - - self::$relations = $relations; - - $refClass = new \ReflectionClass($obj); - - if (self::isNullModel($obj)) { - $objId = self::getObjectId($obj, $refClass); - - return $objId === 0 ? null : $objId; - } - - if (!empty($id = self::getObjectId($obj, $refClass)) && static::$autoincrement) { - $objId = $id; - } else { - $objId = self::createModel($obj, $refClass); - self::setObjectId($refClass, $obj, $objId); - } - - if ($relations === RelationType::ALL) { - self::createHasMany($refClass, $obj, $objId); - } - - self::clear(); - - return $objId; - } - - /** - * Create object in db. - * - * @param array $obj Object reference (gets filled with insert id) - * @param int $relations Create all relations as well - * - * @return mixed - * - * @since 1.0.0 - */ - public static function createArray(array &$obj, int $relations = RelationType::ALL) : mixed - { - self::$relations = $relations; - - if (!empty($id = $obj[static::$columns[static::$primaryField]['internal']])) { - $objId = $id; - } else { - $objId = self::createModelArray($obj); - \settype($objId, static::$columns[static::$primaryField]['type']); - $obj[static::$columns[static::$primaryField]['internal']] = $objId; - } - - if ($relations === RelationType::ALL) { - self::createHasManyArray($obj, $objId); - } - - self::clear(); - - return $objId; - } - - /** - * Create base model. - * - * @param object $obj Model to create - * @param \ReflectionClass $refClass Reflection class - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createModel(object $obj, \ReflectionClass $refClass) : mixed - { - $query = new Builder(self::$db); - $query->into(static::$table); - - foreach (static::$columns as $column) { - $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal']; - if (isset(static::$hasMany[$propertyName])) { - continue; - } - - $property = $refClass->getProperty($propertyName); - if (!$property->isPublic()) { - $property->setAccessible(true); - $tValue = $property->getValue($obj); - $property->setAccessible(false); - } else { - $tValue = $obj->{$propertyName}; - } - - if (isset(static::$ownsOne[$propertyName])) { - $id = self::createOwnsOne($propertyName, $tValue); - $value = self::parseValue($column['type'], $id); - - $query->insert($column['name'])->value($value); - } elseif (isset(static::$belongsTo[$propertyName])) { - $id = self::createBelongsTo($propertyName, $tValue); - $value = self::parseValue($column['type'], $id); - - $query->insert($column['name'])->value($value); - } elseif ($column['name'] !== static::$primaryField || !empty($tValue)) { - if (\stripos($column['internal'], '/') !== false) { - $path = \substr($column['internal'], \stripos($column['internal'], '/') + 1); - $tValue = ArrayUtils::getArray($path, $tValue, '/'); - } - - /* - if (($column['type'] === 'int' || $column['type'] === 'string') - && \is_object($tValue) && \property_exists($tValue, 'id') - ) { - $tValue = - } - */ - - $value = self::parseValue($column['type'], $tValue); - - $query->insert($column['name'])->value($value); - } - } - - // if a table only has a single column = primary key column. This must be done otherwise the query is empty - if ($query->getType() === QueryType::NONE) { - $query->insert(static::$primaryField)->value(0); - } - - try { - $sth = self::$db->con->prepare($query->toSql()); - $sth->execute(); - } catch (\Throwable $t) { - \var_dump($t->getMessage()); - \var_dump($a = $query->toSql()); - return -1; - } - - $objId = empty($id = self::getObjectId($obj, $refClass)) ? self::$db->con->lastInsertId() : $id; - \settype($objId, static::$columns[static::$primaryField]['type']); - - return $objId; - } - - /** - * Create base model. - * - * @param array $obj Model to create - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createModelArray(array &$obj) : mixed - { - $query = new Builder(self::$db); - $query->into(static::$table); - - foreach (static::$columns as $key => $column) { - if (isset(static::$hasMany[$key])) { - continue; - } - - $path = $column['internal']; - if (\stripos($column['internal'], '/') === 0) { - $path = \ltrim($column['internal'], '/'); - } - - $property = ArrayUtils::getArray($column['internal'], $obj, '/'); - - if (isset(static::$ownsOne[$path])) { - $id = self::createOwnsOneArray($column['internal'], $property); - $value = self::parseValue($column['type'], $id); - - $query->insert($column['name'])->value($value); - } elseif (isset(static::$belongsTo[$path])) { - $id = self::createBelongsToArray($column['internal'], $property); - $value = self::parseValue($column['type'], $id); - - $query->insert($column['name'])->value($value); - } elseif ($column['internal'] === $path && $column['name'] !== static::$primaryField) { - $value = self::parseValue($column['type'], $property); - - $query->insert($column['name'])->value($value); - } - } - - // if a table only has a single column = primary key column. This must be done otherwise the query is empty - if ($query->getType() === QueryType::NONE) { - $query->insert(static::$primaryField)->value(0); - } - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - } - - return self::$db->con->lastInsertId(); - } - - /** - * Get id of object - * - * @param object $obj Model to create - * @param \ReflectionClass $refClass Reflection class - * @param string $member Member name for the id, if it is not the primary key - * - * @return mixed - * - * @since 1.0.0 - */ - public static function getObjectId(object $obj, \ReflectionClass $refClass = null, string $member = null) : mixed - { - $refClass ??= new \ReflectionClass($obj); - $propertyName = $member ?? static::$columns[static::$primaryField]['internal']; - $refProp = $refClass->getProperty($propertyName); - - if (!$refProp->isPublic()) { - $refProp->setAccessible(true); - $objectId = $refProp->getValue($obj); - $refProp->setAccessible(false); - } else { - $objectId = $obj->{$propertyName}; - } - - return $objectId; - } - - /** - * Set id to model - * - * @param \ReflectionClass $refClass Reflection class - * @param object $obj Object to create - * @param mixed $objId Id to set - * - * @return void - * - * @since 1.0.0 - */ - private static function setObjectId(\ReflectionClass $refClass, object $obj, mixed $objId) : void - { - $propertyName = static::$columns[static::$primaryField]['internal']; - $refProp = $refClass->getProperty($propertyName); - - \settype($objId, static::$columns[static::$primaryField]['type']); - if (!$refProp->isPublic()) { - $refProp->setAccessible(true); - $refProp->setValue($obj, $objId); - $refProp->setAccessible(false); - } else { - $obj->{$propertyName} = $objId; - } - } - - /** - * Create relation - * - * This is only possible for hasMany objects which are stored in a relation table - * - * @param string $member Member name of the relation - * @param mixed $id1 Id of the primary object - * @param mixed $id2 Id of the secondary object - * - * @return bool - * - * @since 1.0.0 - */ - public static function createRelation(string $member, mixed $id1, mixed $id2) : bool - { - if (!isset(static::$hasMany[$member]) || !isset(static::$hasMany[$member]['external'])) { - return false; - } - - self::createRelationTable($member, \is_array($id2) ? $id2 : [$id2], $id1); - self::removeInitialized(static::class, $id1); - - return true; - } - - /** - * Delete relation - * - * This is only possible for hasMany objects which are stored in a relation table - * - * @param string $member Member name of the relation - * @param mixed $id1 Id of the primary object - * @param mixed $id2 Id of the secondary object - * - * @return bool - * - * @since 1.0.0 - */ - public static function deleteRelation(string $member, mixed $id1, mixed $id2) : bool - { - if (!isset(static::$hasMany[$member]) || !isset(static::$hasMany[$member]['external'])) { - return false; - } - - self::removeInitialized(static::class, $id1); - self::deleteRelationTable($member, \is_array($id2) ? $id2 : [$id2], $id1); - - return true; - } - - /** - * Create has many - * - * @param \ReflectionClass $refClass Reflection class - * @param object $obj Object to create - * @param mixed $objId Id to set - * - * @return void - * - * @throws InvalidMapperException Throws this exception if the mapper in the has many relation is invalid - * - * @since 1.0.0 - */ - private static function createHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void - { - foreach (static::$hasMany as $propertyName => $rel) { - if (!isset(static::$hasMany[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - $property = $refClass->getProperty($propertyName); - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - $values = $property->getValue($obj); - } else { - $values = $obj->{$propertyName}; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$propertyName]['mapper']; - $internalName = isset($mapper::$columns[static::$hasMany[$propertyName]['self']]) - ? $mapper::$columns[static::$hasMany[$propertyName]['self']]['internal'] - : 'ERROR'; - - if (\is_object($values)) { - // conditionals - $relReflectionClass = new \ReflectionClass($values); - $relProperty = $relReflectionClass->getProperty($internalName); - - if (!$relProperty->isPublic()) { - $relProperty->setAccessible(true); - $relProperty->setValue($values, $objId); - $relProperty->setAccessible(false); - } else { - $values->{$internalName} = $objId; - } - - if (!$isPublic) { - $property->setAccessible(false); - } - - $mapper::create($values); - continue; - } elseif (!\is_array($values)) { - if (!$isPublic) { - $property->setAccessible(false); - } - - // conditionals - continue; - } - - if (!$isPublic) { - $property->setAccessible(false); - } - - $objsIds = []; - $relReflectionClass = !empty($values) ? new \ReflectionClass(\reset($values)) : null; - - foreach ($values as $key => $value) { - if (!\is_object($value)) { - // Is scalar => already in database - $objsIds[$key] = $value; - - continue; - } - - /** @var \ReflectionClass $relReflectionClass */ - $primaryKey = $mapper::getObjectId($value, $relReflectionClass); - - // already in db - if (!empty($primaryKey)) { - $objsIds[$key] = $value; - - continue; - } - - // Setting relation value (id) for relation (since the relation is not stored in an extra relation table) - if (!isset(static::$hasMany[$propertyName]['external'])) { - $relProperty = $relReflectionClass->getProperty($internalName); - - if (!$isPublic) { - $relProperty->setAccessible(true); - } - - // todo maybe consider to just set the column type to object, and then check for that (might be faster) - if (isset($mapper::$belongsTo[$internalName]) - || isset($mapper::$ownsOne[$internalName]) - ) { - if (!$isPublic) { - $relProperty->setValue($value, self::createNullModel($objId)); - } else { - $value->{$internalName} = self::createNullModel($objId); - } - } else { - if (!$isPublic) { - $relProperty->setValue($value, $objId); - } else { - $value->{$internalName} = $objId; - } - } - - if (!$isPublic) { - $relProperty->setAccessible(false); - } - } - - $objsIds[$key] = $mapper::create($value); - } - - self::createRelationTable($propertyName, $objsIds, $objId); - } - } - - /** - * Create has many - * - * @param array $obj Object to create - * @param mixed $objId Id to set - * - * @return void - * - * @throws InvalidMapperException Throws this exception if the mapper in the has many relation is invalid - * - * @since 1.0.0 - */ - private static function createHasManyArray(array &$obj, mixed $objId) : void - { - foreach (static::$hasMany as $propertyName => $rel) { - if (!isset(static::$hasMany[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - $values = $obj[$propertyName] ?? null; - - /** @var self $mapper */ - $mapper = static::$hasMany[$propertyName]['mapper']; - - if (!\is_array($values)) { - continue; - } - - /** @var self $mapper */ - $objsIds = []; - - foreach ($values as $key => &$value) { - if (!\is_array($value)) { - // Is scalar => already in database - $objsIds[$key] = $value; - - continue; - } - - $primaryKey = $value[$mapper::$columns[$mapper::$primaryField]['internal']]; - - // already in db - if (!empty($primaryKey)) { - $objsIds[$key] = $value; - - continue; - } - - // Setting relation value (id) for relation (since the relation is not stored in an extra relation table) - if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table - && isset($mapper::$columns[static::$hasMany[$propertyName]['self']]) - ) { - $value[$mapper::$columns[static::$hasMany[$propertyName]['self']]['internal']] = $objId; - } - - $objsIds[$key] = $mapper::createArray($value); - } - - self::createRelationTable($propertyName, $objsIds, $objId); - } - } - - /** - * Create owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to create - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createOwnsOne(string $propertyName, mixed $obj) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; - $primaryKey = $mapper::getObjectId($obj); - - if (empty($primaryKey)) { - return $mapper::create($obj); - } - - return $primaryKey; - } - - /** - * Create owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param array $obj Object to create - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createOwnsOneArray(string $propertyName, array &$obj) : mixed - { - /** @var self $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; - $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; - - return empty($primaryKey) ? $mapper::createArray($obj) : $primaryKey; - } - - /** - * Create owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to create - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createBelongsTo(string $propertyName, mixed $obj) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - $mapper = ''; - $primaryKey = 0; - - if (isset(static::$belongsTo[$propertyName]['by'])) { - // has by (obj is stored as a different model e.g. model = profile but reference/db is account) - - $refClass = new \ReflectionClass($obj); - $refProp = $refClass->getProperty(static::$belongsTo[$propertyName]['by']); - - if (!$refProp->isPublic()) { - $refProp->setAccessible(true); - $obj = $refProp->getValue($obj); - $refProp->setAccessible(false); - } else { - $obj = $obj->{static::$belongsTo[$propertyName]['by']}; - } - - /** @var self $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']::getBelongsTo(static::$belongsTo[$propertyName]['by'])['mapper']; - $primaryKey = $mapper::getObjectId($obj); - } else { - /** @var self $mapper */ - $mapper = static::$belongsTo[$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. - return empty($primaryKey) ? $mapper::create($obj) : $primaryKey; - } - - /** - * Create owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param array $obj Object to create - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createBelongsToArray(string $propertyName, array $obj) : mixed - { - /** @var self $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']; - $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; - - return empty($primaryKey) ? $mapper::createArray($obj) : $primaryKey; - } - - /** - * Create relation table entry - * - * In case of a many to many relation the relation has to be stored in a relation table - * - * @param string $propertyName Property name to initialize - * @param array $objsIds Object ids to insert (can also be the object itself) - * @param mixed $objId Model to reference - * - * @return void - * - * @since 1.0.0 - */ - private static function createRelationTable(string $propertyName, array $objsIds, mixed $objId) : void - { - if (empty($objsIds) || !isset(static::$hasMany[$propertyName]['external'])) { - return; - } - - $relQuery = new Builder(self::$db); - $relQuery->into(static::$hasMany[$propertyName]['table']) - ->insert(static::$hasMany[$propertyName]['external'], static::$hasMany[$propertyName]['self']); - - foreach ($objsIds as $src) { - if (\is_object($src)) { - $mapper = (\stripos($mapper = \get_class($src), '\Null') !== false - ? \str_replace('\Null', '\\', $mapper) - : $mapper) - . 'Mapper'; - - $src = $mapper::getObjectId($src); - } - - $relQuery->values($src, $objId); - } - - try { - $sth = self::$db->con->prepare($relQuery->toSql()); - if ($sth !== false) { - $sth->execute(); - } - } catch (\Throwable $e) { - \var_dump($e->getMessage()); - \var_dump($relQuery->toSql()); - } - } - - /** - * Parse value - * - * @param string $type Value type - * @param mixed $value Value to parse - * - * @return mixed - * - * @since 1.0.0 - */ - private static function parseValue(string $type, mixed $value = null) : mixed - { - if ($value === null) { - return null; - } elseif ($type === 'int') { - return (int) $value; - } elseif ($type === 'string') { - return (string) $value; - } elseif ($type === 'float') { - return (float) $value; - } elseif ($type === 'bool') { - return (bool) $value; - } elseif ($type === 'DateTime' || $type === 'DateTimeImmutable') { - return $value === null ? null : $value->format(self::$datetimeFormat); - } elseif ($type === 'Json' || $value instanceof \JsonSerializable) { - return (string) \json_encode($value); - } elseif ($type === 'Serializable') { - return $value->serialize(); - } elseif (\is_object($value) && \method_exists($value, 'getId')) { - return $value->getId(); - } - - return $value; - } - - /** - * Update has many - * - * @param \ReflectionClass $refClass Reflection class - * @param object $obj Object to create - * @param mixed $objId Id to set - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return void - * - * @throws InvalidMapperException Throws this exception if the mapper in the has many relation is invalid - * - * @since 1.0.0 - */ - private static function updateHasMany(\ReflectionClass $refClass, object $obj, mixed $objId, int $depth = 1) : void - { - $objsIds = []; - - foreach (static::$hasMany as $propertyName => $rel) { - if ($rel['readonly'] ?? false === true) { - continue; - } - - if (!isset(static::$hasMany[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - $property = $refClass->getProperty($propertyName); - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - $values = $property->getValue($obj); - $property->setAccessible(false); - } else { - $values = $obj->{$propertyName}; - } - - if (!\is_array($values) || empty($values)) { - continue; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$propertyName]['mapper']; - $relReflectionClass = new \ReflectionClass(\reset($values)); - $objsIds[$propertyName] = []; - - foreach ($values as $key => &$value) { - if (!\is_object($value)) { - // Is scalar => already in database - $objsIds[$propertyName][$key] = $value; - - continue; - } - - $primaryKey = $mapper::getObjectId($value, $relReflectionClass); - - // already in db - if (!empty($primaryKey)) { - $mapper::update($value, self::$relations, $depth); - - $objsIds[$propertyName][$key] = $value; - - continue; - } - - // create if not existing - if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table - && isset($mapper::$columns[static::$hasMany[$propertyName]['self']]) - ) { - $relProperty = $relReflectionClass->getProperty($mapper::$columns[static::$hasMany[$propertyName]['self']]['internal']); - - if (!$isPublic) { - $relProperty->setAccessible(true); - $relProperty->setValue($value, $objId); - $relProperty->setAccessible(false); - } else { - $value->{$mapper::$columns[static::$hasMany[$propertyName]['self']]['internal']} = $objId; - } - } - - $objsIds[$propertyName][$key] = $mapper::create($value); - } - } - - self::updateRelationTable($objsIds, $objId); - } - - /** - * Update has many - * - * @param array $obj Object to create - * @param mixed $objId Id to set - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return void - * - * @throws InvalidMapperException Throws this exception if the mapper in the has many relation is invalid - * - * @since 1.0.0 - */ - private static function updateHasManyArray(array &$obj, mixed $objId, int $depth = 1) : void - { - $objsIds = []; - - foreach (static::$hasMany as $propertyName => $rel) { - if ($rel['readonly'] ?? false === true) { - continue; - } - - if (!isset(static::$hasMany[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - $values = $obj[$propertyName] ?? null; - - if (!\is_array($values)) { - // conditionals - continue; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$propertyName]['mapper']; - $objsIds[$propertyName] = []; - - foreach ($values as $key => &$value) { - if (!\is_array($value)) { - // Is scalar => already in database - $objsIds[$propertyName][$key] = $value; - - continue; - } - - $primaryKey = $value[$mapper::$columns[$mapper::$primaryField]['internal']]; - - // already in db - if (!empty($primaryKey)) { - $mapper::updateArray($value, self::$relations, $depth); - - $objsIds[$propertyName][$key] = $value; - - continue; - } - - // create if not existing - if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table - && isset($mapper::$columns[static::$hasMany[$propertyName]['self']]) - ) { - $value[$mapper::$columns[static::$hasMany[$propertyName]['self']]['internal']] = $objId; - } - - $objsIds[$propertyName][$key] = $mapper::createArray($value); - } - } - - self::updateRelationTable($objsIds, $objId); - } - - /** - * Update relation table entry - * - * Deletes old entries and creates new ones - * - * @param array $objsIds Object ids to insert - * @param mixed $objId Model to reference - * - * @return void - * - * @since 1.0.0 - */ - private static function updateRelationTable(array $objsIds, mixed $objId) : void - { - $many = self::getHasManyRaw($objId); - - foreach (static::$hasMany as $propertyName => $rel) { - $removes = \array_diff($many[$propertyName], \array_keys($objsIds[$propertyName] ?? [])); - $adds = \array_diff(\array_keys($objsIds[$propertyName] ?? []), $many[$propertyName]); - - if (!empty($removes)) { - self::deleteRelationTable($propertyName, $removes, $objId); - } - - if (!empty($adds)) { - self::createRelationTable($propertyName, $adds, $objId); - } - } - } - - /** - * Delete relation table entry - * - * @param string $propertyName Property name to initialize - * @param array $objsIds Object ids to delete - * @param mixed $objId Model to reference - * - * @return void - * - * @since 1.0.0 - */ - private static function deleteRelationTable(string $propertyName, array $objsIds, mixed $objId) : void - { - if (empty($objsIds) - || static::$hasMany[$propertyName]['table'] === static::$table - || static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table - ) { - return; - } - - foreach ($objsIds as $src) { - $relQuery = new Builder(self::$db); - $relQuery->delete() - ->from(static::$hasMany[$propertyName]['table']) - ->where(static::$hasMany[$propertyName]['table'] . '.' . static::$hasMany[$propertyName]['external'], '=', $src) - ->where(static::$hasMany[$propertyName]['table'] . '.' . static::$hasMany[$propertyName]['self'], '=', $objId, 'and'); - - $sth = self::$db->con->prepare($relQuery->toSql()); - if ($sth !== false) { - $sth->execute(); - } - } - } - - /** - * Update owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to update - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - private static function updateOwnsOne(string $propertyName, mixed $obj, int $depth = 1) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; - - return $mapper::update($obj, self::$relations, $depth); - } - - /** - * Update owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param array $obj Object to update - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - private static function updateOwnsOneArray(string $propertyName, array $obj, int $depth = 1) : mixed - { - /** @var self $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; - - return $mapper::updateArray($obj, self::$relations, $depth); - } - - /** - * Update owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to update - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - private static function updateBelongsTo(string $propertyName, mixed $obj, int $depth = 1) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']; - - return $mapper::update($obj, self::$relations, $depth); - } - - /** - * Update owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to update - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - private static function updateBelongsToArray(string $propertyName, mixed $obj, int $depth = 1) : mixed - { - if (!\is_array($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']; - - return $mapper::updateArray($obj, self::$relations, $depth); - } - - /** - * Update object in db. - * - * @param object $obj Model to update - * @param mixed $objId Model id - * @param \ReflectionClass $refClass Reflection class - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return void - * - * @since 1.0.0 - */ - private static function updateModel(object $obj, mixed $objId, \ReflectionClass $refClass = null, int $depth = 1) : void - { - // Model doesn't have anything to update - if (\count(static::$columns) < 2) { - return; - } - - $query = new Builder(self::$db); - $query->update(static::$table) - ->where(static::$table . '.' . static::$primaryField, '=', $objId); - - foreach (static::$columns as $column) { - $propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal']; - if (isset(static::$hasMany[$propertyName]) - || $column['internal'] === static::$primaryField - || ($column['readonly'] ?? false === true) - ) { - continue; - } - - $refClass = $refClass ?? new \ReflectionClass($obj); - $property = $refClass->getProperty($propertyName); - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - $tValue = $property->getValue($obj); - $property->setAccessible(false); - } else { - $tValue = $obj->{$propertyName}; - } - - if (isset(static::$ownsOne[$propertyName])) { - $id = self::updateOwnsOne($propertyName, $tValue, $depth); - $value = self::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([static::$table . '.' . $column['name'] => $value]); - } elseif (isset(static::$belongsTo[$propertyName])) { - $id = self::updateBelongsTo($propertyName, $tValue, $depth); - $value = self::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([static::$table . '.' . $column['name'] => $value]); - } elseif ($column['name'] !== static::$primaryField) { - if (\stripos($column['internal'], '/') !== false) { - $path = \substr($column['internal'], \stripos($column['internal'], '/') + 1); - $tValue = ArrayUtils::getArray($path, $tValue, '/'); - } - - $value = self::parseValue($column['type'], $tValue); - - $query->set([static::$table . '.' . $column['name'] => $value]); - } - } - - try { - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - } - } catch (\Throwable $t) { - echo $t->getMessage(); - echo $query->toSql(); - } - } - - /** - * Update object in db. - * - * @param array $obj Model to update - * @param mixed $objId Model id - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return void - * - * @since 1.0.0 - */ - private static function updateModelArray(array $obj, mixed $objId, int $depth = 1) : void - { - $query = new Builder(self::$db); - $query->update(static::$table) - ->where(static::$table . '.' . static::$primaryField, '=', $objId); - - foreach (static::$columns as $key => $column) { - if (isset(static::$hasMany[$key]) - || ($column['readonly'] ?? false === true) - ) { - continue; - } - - $path = $column['internal']; - if (\stripos($column['internal'], '/') !== false) { - $path = \substr($column['internal'], \stripos($column['internal'], '/') + 1); - //$path = \ltrim($column['internal'], '/'); - } - - $property = ArrayUtils::getArray($column['internal'], $obj, '/'); - - if (isset(static::$ownsOne[$path])) { - $id = self::updateOwnsOneArray($column['internal'], $property, $depth); - $value = self::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([static::$table . '.' . $column['name'] => $value]); - } elseif (isset(static::$belongsTo[$path])) { - $id = self::updateBelongsToArray($column['internal'], $property, $depth); - $value = self::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([static::$table . '.' . $column['name'] => $value]); - } elseif ($column['name'] !== static::$primaryField) { - $value = self::parseValue($column['type'], $property); - - $query->set([static::$table . '.' . $column['name'] => $value]); - } - } - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - } - } - - /** - * Update object in db. - * - * @param mixed $obj Object reference (gets filled with insert id) - * @param int $relations Create all relations as well - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - public static function update(mixed $obj, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - if (!isset($obj)) { - return null; - } - - self::$relations = $relations; - - $refClass = new \ReflectionClass($obj); - $objId = self::getObjectId($obj, $refClass); - - if ($depth < 1 || self::isNullModel($obj)) { - return $objId === 0 ? null : $objId; - } - - self::addInitialized(static::class, $objId, $obj, $depth); - --$depth; - - if ($relations === RelationType::ALL) { - self::updateHasMany($refClass, $obj, $objId, $depth); - } - - if (empty($objId)) { - return self::create($obj, self::$relations); - } - - self::updateModel($obj, $objId, $refClass, $depth); - self::clear(); - - return $objId; - } - - /** - * Update object in db. - * - * @param array $obj Object reference (gets filled with insert id) - * @param int $relations Create all relations as well - * @param int $depth Depth of relations to update (default = 1 = none) - * - * @return mixed - * - * @since 1.0.0 - */ - public static function updateArray(array &$obj, int $relations = RelationType::ALL, int $depth = 1) : mixed - { - if (empty($obj)) { - return null; - } - - self::$relations = $relations; - - $objId = $obj[static::$columns[static::$primaryField]['internal']]; - $update = true; - - if ($depth < 1) { - return $objId; - } - - self::addInitializedArray(static::class, $objId, $obj, $depth); - --$depth; - - if (empty($objId)) { - $update = false; - self::createArray($obj, self::$relations); - } - - if ($relations === RelationType::ALL) { - self::updateHasManyArray($obj, $objId, $depth); - } - - if ($update) { - self::updateModelArray($obj, $objId, $depth); - } - - self::clear(); - - return $objId; - } - - /** - * Delete has many - * - * @param \ReflectionClass $refClass Reflection class - * @param object $obj Object to create - * @param mixed $objId Id to set - * - * @return void - * - * @throws InvalidMapperException Throws this exception if the mapper in the has many relation is invalid - * - * @since 1.0.0 - */ - private static function deleteHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void - { - foreach (static::$hasMany as $propertyName => $rel) { - if (!isset(static::$hasMany[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - $property = $refClass->getProperty($propertyName); - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - $values = $property->getValue($obj); - $property->setAccessible(false); - } else { - $values = $obj->{$propertyName}; - } - - if (!\is_array($values)) { - // conditionals - continue; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$propertyName]['mapper']; - $objsIds = []; - $relReflectionClass = !empty($values) ? new \ReflectionClass(\reset($values)) : null; - - foreach ($values as $key => &$value) { - if (!\is_object($value)) { - // Is scalar => already in database - $objsIds[$key] = $value; - - continue; - } - - $primaryKey = $mapper::getObjectId($value, $relReflectionClass); - - // already in db - if (!empty($primaryKey)) { - $objsIds[$key] = self::$relations === RelationType::ALL ? $mapper::delete($value) : $primaryKey; - - continue; - } - - /** - * @todo Orange-Management/phpOMS#233 - * On delete the relations and relation tables need to be deleted first - * The exception is of course the belongsTo relation. - */ - } - - self::deleteRelationTable($propertyName, $objsIds, $objId); - } - } - - /** - * Delete owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to delete - * - * @return mixed - * - * @since 1.0.0 - */ - private static function deleteOwnsOne(string $propertyName, mixed $obj) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$ownsOne[$propertyName]['mapper']; - - /** - * @todo Orange-Management/phpOMS#??? [p:low] [t:question] [d:expert] - * Deleting a owned one object is not recommended since it can be owned by something else? - * Or does owns one mean that nothing else can have a relation to this model? - */ - - return $mapper::delete($obj); - } - - /** - * Delete owns one - * - * The reference is stored in the main model - * - * @param string $propertyName Property name to initialize - * @param mixed $obj Object to delete - * - * @return mixed - * - * @since 1.0.0 - */ - private static function deleteBelongsTo(string $propertyName, mixed $obj) : mixed - { - if (!\is_object($obj)) { - return $obj; - } - - /** @var self $mapper */ - $mapper = static::$belongsTo[$propertyName]['mapper']; - - return $mapper::delete($obj); - } - - /** - * Delete object in db. - * - * @param object $obj Model to delete - * @param mixed $objId Model id - * @param \ReflectionClass $refClass Reflection class - * - * @return void - * - * @since 1.0.0 - */ - private static function deleteModel(object $obj, mixed $objId, \ReflectionClass $refClass = null) : void - { - $query = new Builder(self::$db); - $query->delete() - ->from(static::$table) - ->where(static::$table . '.' . static::$primaryField, '=', $objId); - - $refClass = $refClass ?? new \ReflectionClass($obj); - $properties = $refClass->getProperties(); - - if (self::$relations === RelationType::ALL) { - foreach ($properties as $property) { - $propertyName = $property->getName(); - - if (isset(static::$hasMany[$propertyName])) { - continue; - } - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - } - - /** - * @todo Orange-Management/phpOMS#233 - * On delete the relations and relation tables need to be deleted first - * The exception is of course the belongsTo relation. - */ - foreach (static::$columns as $key => $column) { - $value = $isPublic ? $obj->{$propertyName} : $property->getValue($obj); - if (self::$relations === RelationType::ALL - && isset(static::$ownsOne[$propertyName]) - && $column['internal'] === $propertyName - ) { - self::deleteOwnsOne($propertyName, $value); - break; - } elseif (self::$relations === RelationType::ALL - && isset(static::$belongsTo[$propertyName]) - && $column['internal'] === $propertyName - ) { - self::deleteBelongsTo($propertyName, $value); - break; - } - } - - if (!$isPublic) { - $property->setAccessible(false); - } - } - } - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - } - } - - /** - * Delete object in db. - * - * @param mixed $obj Object reference (gets filled with insert id) - * @param int $relations Create all relations as well - * - * @return mixed - * - * @since 1.0.0 - */ - public static function delete(mixed $obj, int $relations = RelationType::REFERENCE) : mixed - { - // @todo: only do this if RelationType !== NONE - if (\is_scalar($obj)) { - $obj = static::get($obj); - } - - self::$relations = $relations; - - $refClass = new \ReflectionClass($obj); - $objId = self::getObjectId($obj, $refClass); - - if (empty($objId)) { - return null; - } - - self::removeInitialized(static::class, $objId); - - if ($relations !== RelationType::NONE) { - self::deleteHasMany($refClass, $obj, $objId); - } - - self::deleteModel($obj, $objId, $refClass); - self::clear(); - - return $objId; - } - - /** - * @todo Orange-Management/phpOMS#221 - * Create the delete functionality for arrays (deleteArray, deleteArrayModel). - */ - - /** - * Populate data. - * - * @param array $result Result set - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function populateIterable(array $result, int $depth = 3) : array - { - $obj = []; - - foreach ($result as $element) { - if (isset($element[static::$primaryField . '_d' . $depth]) - && self::isInitialized(static::class, $element[static::$primaryField . '_d' . $depth], $depth) - ) { - $obj[$element[static::$primaryField . '_d' . $depth]] = self::$initObjects[static::class][$element[static::$primaryField . '_d' . $depth]['obj']]; - - continue; - } - - $toFill = self::createBaseModel(); - - if (!isset($element[static::$primaryField . '_d' . $depth])) { - throw new \Exception(); - } - - $obj[$element[static::$primaryField . '_d' . $depth]] = self::populateAbstract($element, $toFill, $depth); - self::addInitialized(static::class, $element[static::$primaryField . '_d' . $depth], $obj[$element[static::$primaryField . '_d' . $depth]], $depth); - } - - return $obj; - } - - /** - * Populate data. - * - * @param array $result Result set - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function populateIterableArray(array $result, int $depth = 3) : array - { - $obj = []; - - foreach ($result as $element) { - if (isset($element[static::$primaryField]) - && self::isInitializedArray(static::class, $element[static::$primaryField], $depth) - ) { - $obj[$element[static::$primaryField]] = self::$initArrays[static::class][$element[static::$primaryField]]['obj']; - - continue; - } - - if (!isset($element[static::$primaryField])) { - throw new \Exception(); - } - - $obj[$element[static::$primaryField]] = self::populateAbstractArray($element, [], $depth); - self::addInitializedArray(static::class, $element[static::$primaryField], $obj[$element[static::$primaryField]], $depth); - } - - return $obj; - } - - /** - * Populate data. - * - * @param array[] $result Result set - * @param mixed $obj Object to add the relations to - * @param int $depth Relation depth - * - * @return void - * - * @since 1.0.0 - */ - public static function populateManyToMany(array $result, mixed &$obj, int $depth = 3) : void - { - $refClass = new \ReflectionClass($obj); - - foreach ($result as $member => $values) { - if (empty($values) - || !$refClass->hasProperty($member) - || isset(static::$hasMany[$member]['column']) // handled in getQuery() - ) { - continue; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$member]['mapper']; - $refProp = $refClass->getProperty($member); - - $objects = !isset(static::$hasMany[$member]['by']) - ? $mapper::get($values, RelationType::ALL, $depth) - : $mapper::get($values, RelationType::ALL, $depth, static::$hasMany[$member]['by']); - - if (!$refProp->isPublic()) { - $refProp->setAccessible(true); - // @todo: \is_array($values) is weird, was necessary for the itemmanagement list at some point, but only suddenly????!!!! - $refProp->setValue($obj, !\is_array($objects) && (!isset(static::$hasMany[$member]['conditional']) || (\is_array($values) && \count($values) > 1)) - ? [$mapper::getObjectId($objects) => $objects] - : $objects - ); - $refProp->setAccessible(false); - } else { - $obj->{$member} = !\is_array($objects) && !isset(static::$hasMany[$member]['conditional']) - ? [$mapper::getObjectId($objects) => $objects] - : $objects; - } - } - } - - /** - * Populate data. - * - * @param array[] $result Result set - * @param array $obj Object to add the relations to - * @param int $depth Relation depth - * - * @return void - * - * @since 1.0.0 - */ - public static function populateManyToManyArray(array $result, array &$obj, int $depth = 3) : void - { - foreach ($result as $member => $values) { - if (empty($values) - || isset(static::$hasMany[$member]['conditional']) // handled in getQuery() - ) { - continue; - } - - /** @var self $mapper */ - $mapper = static::$hasMany[$member]['mapper']; - - $objects = $mapper::getArray($values, RelationType::ALL, $depth); - $obj[$member] = $objects; - } - } - - /** - * Populate data. - * - * @param string $member Member name - * @param array $result Result data - * @param int $depth Relation depth - * @param mixed $default Default value - * - * @return mixed - * - * @todo: in the future we could pass not only the $id ref but all of the data as a join!!! and save an additional select!!! - * @todo: parent and child elements however must be loaded because they are not loaded - * - * @since 1.0.0 - */ - public static function populateOwnsOne(string $member, array $result, int $depth = 3, mixed $default = null) : mixed - { - /** @var class-string $mapper */ - $mapper = static::$ownsOne[$member]['mapper']; - - if ($depth < 1) { - if (\array_key_exists(static::$ownsOne[$member]['external'] . '_d' . ($depth + 1), $result)) { - return isset(static::$ownsOne[$member]['column']) - ? $result[static::$ownsOne[$member]['external'] . '_d' . ($depth + 1)] - : $mapper::createNullModel($result[static::$ownsOne[$member]['external'] . '_d' . ($depth + 1)]); - } else { - return $default; - } - } - - if (isset(static::$ownsOne[$member]['column'])) { - return $result[$mapper::getColumnByMember(static::$ownsOne[$member]['column']) . '_d' . $depth]; - } - - if (!isset($result[$mapper::$primaryField . '_d' . $depth])) { - return $mapper::createNullModel(); - } - - $obj = $mapper::getInitialized($mapper, $result[$mapper::$primaryField . '_d' . $depth], $depth); - - return $obj ?? $mapper::populateAbstract($result, $mapper::createBaseModel(), $depth); - } - - /** - * Populate data. - * - * @param string $member Member name - * @param array $result Result data - * @param int $depth Relation depth - * @param mixed $default Default value - * - * @return array - * - * @since 1.0.0 - */ - public static function populateOwnsOneArray(string $member, array $result, int $depth = 3, mixed $default = null) : array - { - /** @var class-string $mapper */ - $mapper = static::$ownsOne[$member]['mapper']; - - if ($depth < 1) { - if (\array_key_exists(static::$ownsOne[$member]['external'] . '_d' . ($depth + 1), $result)) { - return $result[static::$ownsOne[$member]['external'] . '_d' . ($depth + 1)]; - } else { - return $default; - } - } - - if (isset(static::$ownsOne[$member]['column'])) { - return $result[$mapper::getColumnByMember(static::$ownsOne[$member]['column']) . '_d' . $depth]; - } - - if (!isset($result[$mapper::$primaryField . '_d' . $depth])) { - return []; - } - - $obj = $mapper::getInitializedArray($mapper, $result[$mapper::$primaryField . '_d' . $depth], $depth); - - return $obj ?? $mapper::populateAbstractArray($result, [], $depth); - } - - /** - * Populate data. - * - * @param string $member Member name - * @param array $result Result data - * @param int $depth Relation depth - * @param mixed $default Default value - * - * @return mixed - * - * @todo: in the future we could pass not only the $id ref but all of the data as a join!!! and save an additional select!!! - * @todo: only the belongs to model gets populated the children of the belongsto model are always null models. either this function needs to call the get for the children, it should call get for the belongs to right away like the has many, or i find a way to recursevily load the data for all sub models and then populate that somehow recursively, probably too complex. - * - * @since 1.0.0 - */ - public static function populateBelongsTo(string $member, array $result, int $depth = 3, mixed $default = null) : mixed - { - /** @var class-string $mapper */ - $mapper = static::$belongsTo[$member]['mapper']; - - if ($depth < 1) { - if (\array_key_exists(static::$belongsTo[$member]['external'] . '_d' . ($depth + 1), $result)) { - return isset(static::$belongsTo[$member]['column']) - ? $result[static::$belongsTo[$member]['external'] . '_d' . ($depth + 1)] - : $mapper::createNullModel($result[static::$belongsTo[$member]['external'] . '_d' . ($depth + 1)]); - } else { - return $default; - } - } - - if (isset(static::$belongsTo[$member]['column'])) { - return $result[$mapper::getColumnByMember(static::$belongsTo[$member]['column']) . '_d' . $depth]; - } - - if (!isset($result[$mapper::$primaryField . '_d' . $depth])) { - return $mapper::createNullModel(); - } - - // get the belongs to based on a different column (not primary key) - // this is often used if the value is actually a different model: - // 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(static::$belongsTo[$member]['by'])) { - return $mapper::getBy($result[$mapper::getColumnByMember(static::$belongsTo[$member]['by']) . '_d' . $depth], static::$belongsTo[$member]['by']); - } - - $obj = $mapper::getInitialized($mapper, $result[$mapper::$primaryField . '_d' . $depth], $depth); - - return $obj ?? $mapper::populateAbstract($result, $mapper::createBaseModel(), $depth); - } - - /** - * Populate data. - * - * @param string $member Member name - * @param array $result Result data - * @param int $depth Relation depth - * @param mixed $default Default value - * - * @return array - * - * @since 1.0.0 - */ - public static function populateBelongsToArray(string $member, array $result, int $depth = 3, mixed $default = null) : array - { - /** @var class-string $mapper */ - $mapper = static::$belongsTo[$member]['mapper']; - - if ($depth < 1) { - if (\array_key_exists(static::$belongsTo[$member]['external'] . '_d' . ($depth + 1), $result)) { - return $result[static::$belongsTo[$member]['external'] . '_d' . ($depth + 1)]; - } else { - return $default; - } - } - - if (isset(static::$belongsTo[$member]['column'])) { - return $result[$mapper::getColumnByMember(static::$belongsTo[$member]['column']) . '_d' . $depth]; - } - - if (!isset($result[$mapper::$primaryField . '_d' . $depth])) { - return []; - } - - $obj = $mapper::getInitializedArray($mapper, $result[$mapper::$primaryField . '_d' . $depth], $depth); - - return $obj ?? $mapper::populateAbstractArray($result, [], $depth); - } - - /** - * Populate data. - * - * @param array $result Query result set - * @param mixed $obj Object to populate - * @param int $depth Relation depth - * - * @return mixed - * - * @throws \UnexpectedValueException - * - * @since 1.0.0 - */ - public static function populateAbstract(array $result, mixed $obj, int $depth = 3) : mixed - { - $refClass = new \ReflectionClass($obj); - - foreach (static::$columns as $column => $def) { - $alias = $column . '_d' . $depth; - - if (!\array_key_exists($alias, $result)) { - continue; - } - - $value = $result[$alias]; - - $hasPath = false; - $aValue = []; - $arrayPath = ''; - - if (\stripos($def['internal'], '/') !== false) { - $hasPath = true; - $path = \explode('/', $def['internal']); - $refProp = $refClass->getProperty($path[0]); - - if (!($isPublic = $refProp->isPublic())) { - $refProp->setAccessible(true); - } - - \array_shift($path); - $arrayPath = \implode('/', $path); - $aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); - } else { - $refProp = $refClass->getProperty($def['internal']); - - if (!($isPublic = $refProp->isPublic())) { - $refProp->setAccessible(true); - } - } - - if (isset(static::$ownsOne[$def['internal']])) { - $default = null; - if ($depth - 1 < 1 && $refProp->isInitialized($obj)) { - $default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); - } - - $value = self::populateOwnsOne($def['internal'], $result, $depth - 1, $default); - - if (\is_object($value) && isset(static::$ownsOne[$def['internal']]['mapper'])) { - static::$ownsOne[$def['internal']]['mapper']::fillRelations($value, self::$relations, $depth - 1); - } - - if (!empty($value)) { - // todo: find better solution. this was because of a bug with the sales billing list query depth = 4. The address was set (from the client, referral or creator) but then somehow there was a second address element which was all null and null cannot be asigned to a string variable (e.g. country). The problem with this solution is that if the model expects an initialization (e.g. at lest set the elements to null, '', 0 etc.) this is now not done. - $refProp->setValue($obj, $value); - } - } elseif (isset(static::$belongsTo[$def['internal']])) { - $default = null; - if ($depth - 1 < 1 && $refProp->isInitialized($obj)) { - $default = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); - } - - $value = self::populateBelongsTo($def['internal'], $result, $depth - 1, $default); - - if (\is_object($value) && isset(static::$belongsTo[$def['internal']]['mapper'])) { - static::$belongsTo[$def['internal']]['mapper']::fillRelations($value, self::$relations, $depth - 1); - } - - $refProp->setValue($obj, $value); - } elseif (\in_array($def['type'], ['string', 'int', 'float', 'bool'])) { - if ($value !== null || $refProp->getValue($obj) !== null) { - \settype($value, $def['type']); - } - - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['type'] === 'Json') { - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, \json_decode($value, true)); - } elseif ($def['type'] === 'Serializable') { - $member = $isPublic ? $obj->{$def['internal']} : $refProp->getValue($obj); - - if ($value === null) { - $obj->{$def['internal']} = $value; - } else { - $member->unserialize($value); - } - } - - if (!$isPublic) { - $refProp->setAccessible(false); - } - } - - foreach (static::$hasMany as $member => $def) { - $column = $def['mapper']::getColumnByMember($def['column'] ?? $member); - $alias = $column . '_d' . ($depth - 1); - - if (!\array_key_exists($alias, $result) || !isset($def['column'])) { - continue; - } - - $value = $result[$alias]; - $hasPath = false; - $aValue = null; - $arrayPath = '/'; - - if (\stripos($member, '/') !== false) { - $hasPath = true; - $path = \explode('/', $member); - $refProp = $refClass->getProperty($path[0]); - - if (!($isPublic = $refProp->isPublic())) { - $refProp->setAccessible(true); - } - - \array_shift($path); - $arrayPath = \implode('/', $path); - $aValue = $isPublic ? $obj->{$path[0]} : $refProp->getValue($obj); - } else { - $refProp = $refClass->getProperty($member); - - if (!($isPublic = $refProp->isPublic())) { - $refProp->setAccessible(true); - } - } - - if (\in_array($def['mapper']::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { - if ($value !== null || $refProp->getValue($obj) !== null) { - \settype($value, $def['mapper']::$columns[$column]['type']); - } - - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['mapper']::$columns[$column]['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['mapper']::$columns[$column]['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, $value); - } elseif ($def['mapper']::$columns[$column]['type'] === 'Json') { - if ($hasPath) { - $value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true); - } - - $refProp->setValue($obj, \json_decode($value, true)); - } elseif ($def['mapper']::$columns[$column]['type'] === 'Serializable') { - $member = $isPublic ? $obj->{$member} : $refProp->getValue($obj); - $member->unserialize($value); - } - - if (!$isPublic) { - $refProp->setAccessible(false); - } - } - - return $obj; - } - - /** - * Populate data. - * - * @param array $result Query result set - * @param array $obj Object to populate - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function populateAbstractArray(array $result, array $obj = [], int $depth = 3) : array - { - foreach (static::$columns as $column => $def) { - $alias = $column . '_d' . $depth; - - if (!\array_key_exists($alias, $result)) { - continue; - } - - $value = $result[$alias]; - - $path = static::$columns[$column]['internal']; - if (\stripos($path, '/') !== false) { - $path = \explode('/', $path); - - \array_shift($path); - $path = \implode('/', $path); - } - - if (isset(static::$ownsOne[$def['internal']])) { - $value = self::populateOwnsOneArray(static::$columns[$column]['internal'], $result, $depth - 1); - - static::$ownsOne[$def['internal']]['mapper']::fillRelationsArray($value, self::$relations, $depth - 1); - } elseif (isset(static::$belongsTo[$def['internal']])) { - $value = self::populateBelongsToArray(static::$columns[$column]['internal'], $result, $depth - 1); - - static::$belongsTo[$def['internal']]['mapper']::fillRelationsArray($value, self::$relations, $depth - 1); - } elseif (\in_array(static::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { - \settype($value, static::$columns[$column]['type']); - } elseif (static::$columns[$column]['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); - } elseif (static::$columns[$column]['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); - } elseif (static::$columns[$column]['type'] === 'Json') { - $value = \json_decode($value, true); - } - - $obj = ArrayUtils::setArray($path, $obj, $value, '/', true); - } - - foreach (static::$hasMany as $member => $def) { - $column = $def['mapper']::getColumnByMember($member); - $alias = $column . '_d' . ($depth - 1); - - if (!\array_key_exists($alias, $result) || !isset($def['column'])) { - continue; - } - - $value = $result[$alias]; - - $path = $member; - if (\in_array($def['mapper']::$columns[$column]['type'], ['string', 'int', 'float', 'bool'])) { - \settype($value, $def['mapper']::$columns[$column]['type']); - } elseif ($def['mapper']::$columns[$column]['type'] === 'DateTime') { - $value = $value === null ? null : new \DateTime($value); - } elseif ($def['mapper']::$columns[$column]['type'] === 'DateTimeImmutable') { - $value = $value === null ? null : new \DateTimeImmutable($value); - } elseif ($def['mapper']::$columns[$column]['type'] === 'Json') { - $value = \json_decode($value, true); - } - - $obj = ArrayUtils::setArray($path, $obj, $value, '/', true); - } - - return $obj; - } - - /** - * Count the number of elements before a pivot element - * - * @param mixed $pivot Pivet id - * @param null|string $column Name of the field in the model - * - * @return int - * - * @since 1.0.0 - */ - public static function countBeforePivot(mixed $pivot, string $column = null) : int - { - $query = new Builder(self::$db); - $query->where(static::$table . '.' . ($column !== null ? self::getColumnByMember($column) : static::$primaryField), '<', $pivot); - - return self::count($query); - } - - /** - * Count the number of elements after a pivot element - * - * @param mixed $pivot Pivet id - * @param null|string $column Name of the field in the model - * - * @return int - * - * @since 1.0.0 - */ - public static function countAfterPivot(mixed $pivot, string $column = null) : int - { - $query = new Builder(self::$db); - $query->where(static::$table . '.' . ($column !== null ? self::getColumnByMember($column) : static::$primaryField), '>', $pivot); - - return self::count($query); - } - - /** - * Count the number of elements - * - * @param null|Builder $query Builder - * - * @return int - * - * @since 1.0.0 - */ - public static function count(Builder $query = null) : int - { - $query ??= new Builder(self::$db); - $query->select('COUNT(*)') - ->from(static::$table); - - return (int) $query->execute()->fetchColumn(); - } - - /** - * Get objects for pagination - * - * @param mixed $pivot Pivot - * @param string $column Sort column/pivot column - * @param int $limit Result limit - * @param int $relations Load relations - * @param int $depth Relation depth - * @param Builder $query Query - * - * @return array - * - * @since 1.0.0 - */ - public static function getAfterPivot( - mixed $pivot, - string $column = null, - int $limit = 50, - int $relations = RelationType::ALL, - int $depth = 3, - Builder $query = null - ) : array - { - $query ??= self::getQuery(depth: $depth); - $query->where(static::$table . '_d' . $depth . '.' . ($column !== null ? self::getColumnByMember($column) : static::$primaryField), '>', $pivot); - - if ($limit > 0) { - $query->limit($limit); - } - - return self::getAllByQuery($query, $relations, $depth); - } - - /** - * Get objects for pagination - * - * @param mixed $pivot Pivot - * @param string $column Sort column/pivot column - * @param int $limit Result limit - * @param int $relations Load relations - * @param int $depth Relation depth - * @param Builder $query Query - * - * @return array - * - * @since 1.0.0 - * - * @todo Orange-Management/phpOMS#? [p:medium] [d:medium] [t:bug] - * If the pivot element doesn't exist the result set is empty. - * It should just return the closes elements "before" the pivot element. - */ - public static function getBeforePivot( - mixed $pivot, - string $column = null, - int $limit = 50, - int $relations = RelationType::ALL, - int $depth = 3, - Builder $query = null - ) : array - { - $query ??= self::getQuery(depth: $depth); - $query->where(static::$table . '_d' . $depth . '.' . ($column !== null ? self::getColumnByMember($column) : static::$primaryField), '<', $pivot); - - if ($limit > 0) { - $query->limit($limit); - } - - return self::getAllByQuery($query, $relations, $depth); - } - - /** - * Get object. - * - * @param mixed $primaryKey Key - * @param int $relations Load relations - * @param int $depth Relation depth - * @param string $ref Ref (for getBy and getFor) - * @param Builder $query Query - * - * @return mixed - * - * @since 1.0.0 - */ - public static function get( - mixed $primaryKey, - int $relations = RelationType::ALL, - int $depth = 3, - string $ref = null, - Builder $query = null - ) : mixed - { - if ($depth < 1) { - return self::createNullModel($primaryKey); - } - - if (!isset(self::$parentMapper)) { - self::$parentMapper = static::class; - } - - self::$relations = $relations; - - $keys = (array) $primaryKey; - $obj = []; - - foreach ($keys as $key => $value) { - if (!self::isInitialized(static::class, $value, $depth) || $ref !== null) { - continue; - } - - $obj[$value] = self::$initObjects[static::class][$value]['obj']; - unset($keys[$key]); - } - - if (!empty($keys) || $primaryKey === null) { - $dbData = self::getRaw($keys, self::$relations, $depth, $ref, $query); - - if (static::class === self::$parentMapper) { - static::$lastQueryData = $dbData; - } - - foreach ($dbData as $row) { - $value = $row[static::$primaryField . '_d' . $depth]; - $obj[$value] = self::createBaseModel(); - self::addInitialized(static::class, $value, $obj[$value], $depth); - - $obj[$value] = self::populateAbstract($row, $obj[$value], $depth); - self::fillRelations($obj[$value], self::$relations, $depth - 1); - } - } - - self::clear(); - - $countResulsts = \count($obj); - - if ($countResulsts === 0) { - return self::createNullModel(); - } elseif ($countResulsts === 1) { - return \reset($obj); - } - - return $obj; - } - - /** - * Get the raw data from the last query - * - * @return array - * - * @since 1.0.0 - */ - public static function getDataLastQuery() : array - { - return static::$lastQueryData; - } - - /** - * Get object. - * - * @param mixed $primaryKey Key - * @param int $relations Load relations - * @param int $depth Relation depth - * @param string $ref Ref (for getBy and getFor) - * - * @return array - * - * @since 1.0.0 - */ - public static function getArray(mixed $primaryKey, int $relations = RelationType::ALL, int $depth = 3, string $ref = null) : array - { - if ($depth < 1) { - return $primaryKey; - } - - if (!isset(self::$parentMapper)) { - self::$parentMapper = static::class; - } - - $primaryKey = (array) $primaryKey; - $obj = []; - - self::$relations = $relations; - - foreach ($primaryKey as $key => $value) { - if (!self::isInitializedArray(static::class, $value, $depth) || $ref !== null) { - continue; - } - - $obj[$value] = self::$initArrays[static::class][$value]['obj']; - unset($primaryKey[$key]); - } - - $dbData = self::getRaw($primaryKey, $relations, $depth, $ref); - if (empty($dbData)) { - $countResulsts = \count($obj); - - if ($countResulsts === 0) { - return []; - } elseif ($countResulsts === 1) { - return \reset($obj); - } - - return $obj; - } - - foreach ($dbData as $row) { - $value = $row[static::$primaryField . '_d' . $depth]; - $obj[$value] = self::populateAbstractArray($row, [], $depth); - - self::addInitializedArray(static::class, $value, $obj[$value], $depth); - self::fillRelationsArray($obj[$value], self::$relations, $depth - 1); - } - - self::clear(); - - return \count($obj) === 1 ? \reset($obj) : $obj; - } - - /** - * Get object. - * - * @param mixed $forKey Key - * @param string $for The field that defines the for - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return mixed - * - * @since 1.0.0 - * @todo by and for look the same, this cannot be correct. - */ - public static function getFor(mixed $forKey, string $for, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - return self::get($forKey, $relations, $depth, $for); - } - - /** - * Get object. - * - * @param mixed $byKey Key - * @param string $by The field that defines the for - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return mixed - * - * @since 1.0.0 - * @todo by and for look the same, this cannot be correct. - */ - public static function getBy(mixed $byKey, string $by, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - return self::get($byKey, $relations, $depth, $by); - } - - /** - * Get object. - * - * @param mixed $refKey Key - * @param string $ref The field that defines the for - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return mixed - * - * @since 1.0.0 - */ - public static function getForArray(mixed $refKey, string $ref, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - return self::getArray($refKey, $relations, $depth, $ref); - } - - /** - * Get object. - * - * @param mixed $byKey Key - * @param string $by The field that defines the for - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return mixed - * - * @since 1.0.0 - */ - public static function getByArray(mixed $byKey, string $by, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - return self::getArray($byKey, $relations, $depth, $by); - } - - /** - * Get object. - * - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function getAll(int $relations = RelationType::ALL, int $depth = 3) : array - { - $result = self::get(null, $relations, $depth); - - if (\is_object($result) && \stripos(\get_class($result), '\Null') !== false) { - return []; - } - - return !\is_array($result) ? [$result] : $result; - } - - /** - * Get object. - * - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function getAllArray(int $relations = RelationType::ALL, int $depth = 3) : array - { - $result = self::getArray(null, $relations, $depth); - - return !\is_array(\reset($result)) ? [$result] : $result; - } - - /** - * Get newest. - * - * This will fall back to the insert id if no datetime column is present. - * - * @param int $limit Newest limit - * @param Builder $query Pre-defined query - * @param int $relations Load relations - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function getNewest(int $limit = 1, Builder $query = null, int $relations = RelationType::ALL, int $depth = 3) : array - { - $query = self::getQuery($query, [], $relations, $depth); - $query->limit($limit); - - if (!empty(static::$createdAt)) { - $query->orderBy(static::$table . '_d' . $depth . '.' . static::$columns[static::$createdAt]['name'], 'DESC'); - } else { - $query->orderBy(static::$table . '_d' . $depth . '.' . static::$columns[static::$primaryField]['name'], 'DESC'); - } - - return self::getAllByQuery($query, $relations, $depth); - } - - /** - * Parent parent. - * - * @param mixed $value Parent value id - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function getByParent(mixed $value, int $depth = 3) : array - { - $query = self::getQuery(); - $query->where(static::$table . '_d' . $depth . '.' . static::$parent, '=', $value); - - return self::getAllByQuery($query, RelationType::ALL, $depth); - } - - /** - * Get all by custom query. - * - * @param Builder $query Query - * @param int $relations Relations - * @param int $depth Relation depth - * - * @return array - * - * @since 1.0.0 - */ - public static function getAllByQuery(Builder $query, int $relations = RelationType::ALL, int $depth = 3) : array - { - $result = self::get(null, $relations, $depth, null, $query); - - if (\is_object($result) && \stripos(\get_class($result), '\Null') !== false) { - return []; - } - - return !\is_array($result) ? [$result] : $result; - } - - /** - * Get random object - * - * @param int $amount Amount of random models - * @param int $relations Relations type - * @param int $depth Relation depth - * - * @return mixed - * - * @since 1.0.0 - */ - public static function getRandom(int $amount = 1, int $relations = RelationType::ALL, int $depth = 3) : mixed - { - if ($depth < 1) { - return null; - } - - $query = new Builder(self::$db); - $query->random(static::$primaryField) - ->limit($amount); - - return self::getAllByQuery($query, $relations, $depth); - } - - /** - * Fill object with relations - * - * @param mixed $obj Object to fill - * @param int $relations Relations type - * @param int $depth Relation depth - * - * @return void - * - * @since 1.0.0 - */ - public static function fillRelations(mixed $obj, int $relations = RelationType::ALL, int $depth = 3) : void - { - if ($depth < 1 - || empty(static::$hasMany) - || $relations === RelationType::NONE - ) { - return; - } - - $key = self::getObjectId($obj); - if (empty($key)) { - return; - } - - // @todo: check if there are more cases where the relation is already loaded with joins etc. - // there can be pseudo has many elements like localizations. They are has manies but these are already loaded with joins! - $hasRelHasMany = false; - foreach (static::$hasMany as $many) { - if (!isset($many['column'])) { - $hasRelHasMany = true; - break; - } - } - - if (!$hasRelHasMany) { - return; - } - - // todo: let hasmanyraw return the full data already and let populatemanytomany just fill it! - // todo: create a get has many raw id function because sometimes we need this (see update function) - self::populateManyToMany(self::getHasManyRaw($key, $relations), $obj, $depth); - } - - /** - * Fill object with relations - * - * @param array $obj Objects to fill - * @param int $relations Relations type - * @param int $depth Relation depth - * - * @return void - * - * @since 1.0.0 - */ - public static function fillRelationsArray(array &$obj, int $relations = RelationType::ALL, int $depth = 3) : void - { - if ($depth < 1 - || empty(static::$hasMany) - || $relations === RelationType::NONE - ) { - return; - } - - $key = $obj[static::$columns[static::$primaryField]['internal']]; - if (empty($key)) { - return; - } - - /* loading relations from relations table and populating them and then adding them to the object */ - self::populateManyToManyArray(self::getHasManyRaw($key, $relations), $obj, $depth); - } - - /** - * Get object. - * - * @param mixed $keys Key - * @param int $relations Relations type - * @param int $depth Relation depth - * @param null|string $ref Ref (for getBy and getFor) - * @param null|Builder $query Query - * - * @return array - * - * @since 1.0.0 - */ - public static function getRaw(mixed $keys, int $relations = RelationType::ALL, int $depth = 3, string $ref = null, Builder $query = null) : array - { - $comparison = \is_array($keys) && \count($keys) > 1 ? 'in' : '='; - $keys = $comparison === 'in' ? $keys : \reset($keys); - - $query ??= self::getQuery(null, [], $relations, $depth); - $hasBy = $ref === null ? false : isset(static::$columns[self::getColumnByMember($ref)]); - - if ($ref === null || $hasBy) { - $ref = $ref === null || !$hasBy ? static::$primaryField : static::$columns[self::getColumnByMember($ref)]['name']; - - if ($keys !== null && $keys !== false) { - $query->where(static::$table . '_d' . $depth . '.' . $ref, $comparison, $keys); - } - } else { - if (isset(static::$hasMany[$ref])) { - if ($keys !== null && $keys !== false) { - $query->where(static::$hasMany[$ref]['table'] . '_d' . $depth . '.' . static::$hasMany[$ref]['external'], $comparison, $keys); - } - - $query->leftJoin(static::$hasMany[$ref]['table'], static::$hasMany[$ref]['table'] . '_d' . $depth); - if (static::$hasMany[$ref]['external'] !== null) { - $query->on( - static::$table . '_d' . $depth . '.' . static::$hasMany[$ref][static::$primaryField], '=', - static::$hasMany[$ref]['table'] . '_d' . $depth . '.' . static::$hasMany[$ref]['self'], 'and', - static::$hasMany[$ref]['table'] . '_d' . $depth - ); - } else { - $query->on( - static::$table . '_d' . $depth . '.' . static::$primaryField, '=', - static::$hasMany[$ref]['table'] . '_d' . $depth . '.' . static::$hasMany[$ref]['self'], 'and', - static::$hasMany[$ref]['table'] . '_d' . $depth - ); - } - } elseif (isset(static::$belongsTo[$ref]) && static::$belongsTo[$ref]['external'] !== null) { - if ($keys !== null && $keys !== false) { - $query->where(static::$table . '_d' . $depth . '.' . $ref, $comparison, $keys); - } - - $query->leftJoin(static::$belongsTo[$ref]['mapper']::getTable(), static::$belongsTo[$ref]['mapper']::getTable() . '_d' . $depth) - ->on( - static::$table . '_d' . $depth . '.' . static::$belongsTo[$ref]['external'], '=', - static::$belongsTo[$ref]['mapper']::getTable() . '_d' . $depth . '.' . static::$belongsTo[$ref]['mapper']::getPrimaryField() , 'and', - static::$belongsTo[$ref]['mapper']::getTable() . '_d' . $depth - ); - } elseif (isset(static::$ownsOne[$ref]) && static::$ownsOne[$ref]['external'] !== null) { - if ($keys !== null && $keys !== false) { - $query->where(static::$table . '_d' . $depth . '.' . $ref, $comparison, $keys); - } - - $query->leftJoin(static::$ownsOne[$ref]['mapper']::getTable(), static::$ownsOne[$ref]['mapper']::getTable() . '_d' . $depth) - ->on( - static::$table . '_d' . $depth . '.' . static::$ownsOne[$ref]['external'], '=', - static::$ownsOne[$ref]['mapper']::getTable() . '_d' . $depth . '.' . static::$ownsOne[$ref]['mapper']::getPrimaryField() , 'and', - static::$ownsOne[$ref]['mapper']::getTable() . '_d' . $depth - ); - } - } - - try { - $results = false; - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - $results = $sth->fetchAll(\PDO::FETCH_ASSOC); - } - } catch (\Throwable $t) { - $results = false; - \var_dump($a = $query->toSql()); - \var_dump($t->getMessage()); - } - - return $results === false ? [] : $results; - } - - /** - * Get object. - * - * @param mixed $refKey Key - * @param string $ref Ref - * - * @return array - * - * @since 1.0.0 - */ - public static function getHasManyPrimaryKeys(mixed $refKey, string $ref) : array - { - $query = new Builder(self::$db); - $query->select(static::$hasMany[$ref]['table'] . '.' . static::$hasMany[$ref]['self']) - ->from(static::$hasMany[$ref]['table']) - ->where(static::$hasMany[$ref]['table'] . '.' . static::$hasMany[$ref]['external'], '=', $refKey); - - $result = false; - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - $result = $sth->fetchAll(\PDO::FETCH_NUM); - } - - return $result === false ? [] : \array_column($result, 0); - } - - /** - * Get raw by primary key - * - * @param mixed $primaryKey Primary key - * @param int $relations Load relations - * - * @return array - * - * @since 1.0.0 - */ - public static function getHasManyRaw(mixed $primaryKey, int $relations = RelationType::ALL) : array - { - $result = []; - $cachedTables = []; // used by conditionals - - self::$relations = $relations; - - foreach (static::$hasMany as $member => $value) { - if ($value['writeonly'] ?? false === true - || self::$relations !== RelationType::ALL - || (isset(self::$withFields[$member]['ignore']) && self::$withFields[$member]['ignore']) // should not be loaded - ) { - continue; - } - - if (isset($cachedTables[$value['table']])) { - $result[$member] = $cachedTables[$value['table']]; - - continue; - } - - $query = new Builder(self::$db); - $src = $value['external'] ?? $value['mapper']::$primaryField; - - // @todo: what if a specific column name is defined instead of primaryField for the join? Fix, it should be stored in 'column' - $query->select($value['table'] . '.' . $src) - ->from($value['table']) - ->where($value['table'] . '.' . $value['self'], '=', $primaryKey); - - if ($value['table'] !== $value['mapper']::getTable()) { - $query->leftJoin($value['mapper']::getTable()) - ->on($value['table'] . '.' . $src, '=', $value['mapper']::getTable() . '.' . $value['mapper']::getPrimaryField()); - } - - $modelName = $value['mapper']::getModelName(); - - // @todo: here the relation table should probably join the the model table for better ::with() handling - - if (isset(self::$sortFields[$member]) - && ($column = $value['mapper']::getColumnByMember($member)) !== null - && (self::$sortFields[$member]['models'] === null || \in_array($modelName, self::$sortFields[$member]['models'])) - ) { - $query->orderBy($value['mapper']::getTable() . '.' . $column, self::$sortFields[$member]['order']); - } elseif (isset($value['sort'])) { - $query->orderBy($value['mapper']::getTable() . '.' . $value['mapper']::getColumnByMember($value['sort']['orderBy']), $value['sort']['sortOrder']); - } - - if (isset(self::$withFields[$member]) && self::$withFields[$member]['limit'] !== null) { - $query->limit(self::$withFields[$member]['limit']); - } - - // @todo: like the foreach loop below, I probably also need to loop all sortFields to check if ther is a sortField defined which is part of the hasMany definition?! - - foreach (self::$withFields as $condKey => $condValue) { - if (($column = $value['mapper']::getColumnByMember($condKey)) === null - || ($condValue['models'] !== null && !\in_array($modelName, $condValue['models'])) - || ($value['conditional'] ?? false) === false - || $condValue['ignore'] - ) { - continue; - } - - if ($condValue['value'] !== null) { - $query->andWhere($value['mapper']::getTable() . '.' . $column, $condValue['comparison'], $condValue['value']); - } - - if ($condValue['limit'] !== null) { - $query->limit($condValue['limit']); - } - } - - $sth = self::$db->con->prepare($query->toSql()); - if ($sth !== false) { - $sth->execute(); - $result[$member] = $cachedTables[$value['table']] = $sth->fetchAll(\PDO::FETCH_COLUMN); - } - } - - // @todo: this returns IDs it should return the database data here in order to reduce the requests. - return $result; - } - - /** - * Get mapper specific builder - * - * @param Builder $query Query to fill - * @param array $columns Columns to use - * @param int $relations Which relations should be considered in the query - * @param int $depth Depths of the relations to be considered - * - * @return Builder - * - * @since 1.0.0 - */ - public static function getQuery(Builder $query = null, array $columns = [], int $relations = RelationType::ALL, int $depth = 3) : Builder - { - $query ??= new Builder(self::$db); - - if (empty($columns)) { - $columns = static::$columns; - } - - self::$relations = $relations; - - foreach ($columns as $key => $values) { - if ($values['writeonly'] ?? false === false) { - $query->selectAs(static::$table . '_d' . $depth . '.' . $key, $key . '_d' . $depth); - } - } - - if (empty($query->from)) { - $query->fromAs(static::$table, static::$table . '_d' . $depth); - } - - // handle sort, the column name order is very important. Therefore it cannot be done in the foreach loop above! - $modelName = self::getModelName(); - foreach (self::$sortFields as $member => $sort) { - if (($column = self::getColumnByMember($member)) === null - || ($sort['models'] !== null && !\in_array($modelName, $sort['models'])) - ) { - continue; - } - - $query->orderBy(static::$table . '_d' . $depth . '.' . $column, $sort['order']); - } - - // handle conditional - foreach (self::$withFields as $condKey => $condValue) { - if (($column = self::getColumnByMember($condKey)) === null - || ($condValue['models'] !== null && !\in_array($modelName, $condValue['models'])) - || $condValue['ignore'] - ) { - continue; - } - - if ($condValue['value'] !== null) { - $query->andWhere(static::$table . '_d' . $depth . '.' . $column, $condValue['comparison'], $condValue['value']); - } - - if ($condValue['orderBy'] !== null) { - $query->orderBy(static::$table . '_d' . $depth . '.' . $columns[$condValue['orderBy']], $condValue['sortOrder']); - } - - if ($condValue['limit'] !== null) { - $query->limit($condValue['limit']); - } - } - - // get OwnsOneQuery - if ($depth > 1 && self::$relations === RelationType::ALL) { - foreach (static::$ownsOne as $key => $rel) { - if (isset(self::$withFields[$key]) && self::$withFields[$key]['ignore']) { - continue; - } - - $query->leftJoin($rel['mapper']::getTable(), $rel['mapper']::getTable() . '_d' . ($depth - 1)) - ->on( - static::$table . '_d' . $depth . '.' . $rel['external'], '=', - $rel['mapper']::getTable() . '_d' . ($depth - 1) . '.' . ( - isset($rel['by']) ? $rel['mapper']::getColumnByMember($rel['by']) : $rel['mapper']::getPrimaryField() - ), 'and', - $rel['mapper']::getTable() . '_d' . ($depth - 1) - ); - - $query = $rel['mapper']::getQuery( - $query, - isset($rel['column']) ? [$rel['mapper']::getColumnByMember($rel['column']) => []] : [], - self::$relations, - $depth - 1 - ); - } - } - - // get BelognsToQuery - if ($depth > 1 && self::$relations === RelationType::ALL) { - foreach (static::$belongsTo as $key => $rel) { - if (isset(self::$withFields[$key]) && self::$withFields[$key]['ignore']) { - continue; - } - - $query->leftJoin($rel['mapper']::getTable(), $rel['mapper']::getTable() . '_d' . ($depth - 1)) - ->on( - static::$table . '_d' . $depth . '.' . $rel['external'], '=', - $rel['mapper']::getTable() . '_d' . ($depth - 1) . '.' . ( - isset($rel['by']) ? $rel['mapper']::getColumnByMember($rel['by']) : $rel['mapper']::getPrimaryField() - ), 'and', - $rel['mapper']::getTable() . '_d' . ($depth - 1) - ); - - $query = $rel['mapper']::getQuery( - $query, - isset($rel['column']) ? [$rel['mapper']::getColumnByMember($rel['column']) => []] : [], - self::$relations, - $depth - 1 - ); - } - } - - // get HasManyQuery (but only for elements which have a 'column' defined) - if ($depth > 1 && self::$relations === RelationType::ALL) { - foreach (static::$hasMany as $key => $rel) { - // @todo: impl. conditional/with handling, sort, limit, filter or is this not required here? - if (isset($rel['external']) || !isset($rel['column']) // @todo: conflict with getHasMany()???!?!?!?! - || (isset(self::$withFields[$key]) && self::$withFields[$key]['ignore']) - ) { - continue; - } - - // todo: handle self and self === null - $query->leftJoin($rel['mapper']::getTable(), $rel['mapper']::getTable() . '_d' . ($depth - 1)) - ->on( - static::$table . '_d' . $depth . '.' . ($rel['external'] ?? static::$primaryField), '=', - $rel['mapper']::getTable() . '_d' . ($depth - 1) . '.' . ( - isset($rel['by']) ? $rel['mapper']::getColumnByMember($rel['by']) : $rel['self'] - ), 'and', - $rel['mapper']::getTable() . '_d' . ($depth - 1) - ); - - $query = $rel['mapper']::getQuery( - $query, - isset($rel['column']) ? [$rel['mapper']::getColumnByMember($rel['column']) => []] : [], - self::$relations, - $depth - 1 - ); - } - } - - return $query; - } - - /** - * Get created at column - * - * @return string - * - * @since 1.0.0 - */ - public static function getCreatedAt() : string - { - return !empty(static::$createdAt) ? static::$createdAt : static::$primaryField; - } - - /** - * Add initialized object to local cache - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param object $obj Model to cache locally - * @param int $depth Model depth - * - * @return void - * - * @since 1.0.0 - */ - private static function addInitialized(string $mapper, mixed $id, object $obj = null, int $depth = 3) : void - { - if (!isset(self::$initObjects[$mapper])) { - self::$initObjects[$mapper] = []; - } - - self::$initObjects[$mapper][$id] = [ - 'obj' => $obj, - 'relation' => self::$relations, - 'depth' => $depth, - ]; - } - - /** - * Add initialized object to local cache - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param array $obj Model to cache locally - * @param int $depth Model depth - * - * @return void - * - * @since 1.0.0 - */ - private static function addInitializedArray(string $mapper, mixed $id, array $obj = null, int $depth = 3) : void - { - if (!isset(self::$initArrays[$mapper])) { - self::$initArrays[$mapper] = []; - } - - self::$initArrays[$mapper][$id] = [ - 'obj' => $obj, - 'relation' => self::$relations, - 'depth' => $depth, - ]; - } - - /** - * Check if a object is initialized - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * - * @return bool - * - * @since 1.0.0 - */ - private static function isInitialized(string $mapper, mixed $id, int $depth = 3) : bool - { - return !empty($id) - && isset(self::$initObjects[$mapper], self::$initObjects[$mapper][$id]) - && self::$initObjects[$mapper][$id]['relation'] >= self::$relations - && self::$initObjects[$mapper][$id]['depth'] >= $depth; - } - - /** - * Check if a object is initialized - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param int $depth Model depth - * - * @return bool - * - * @since 1.0.0 - */ - private static function isInitializedArray(string $mapper, mixed $id, int $depth = 3) : bool - { - return !empty($id) - && isset(self::$initArrays[$mapper], self::$initArrays[$mapper][$id]) - && self::$initArrays[$mapper][$id]['relation'] >= self::$relations - && self::$initArrays[$mapper][$id]['depth'] >= $depth; - } - - /** - * Get initialized object - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param int $depth Depth - * - * @return mixed - * - * @since 1.0.0 - */ - private static function getInitialized(string $mapper, mixed $id, int $depth) : mixed - { - if (!self::isInitialized($mapper, $id, $depth)) { - return null; - } - - return self::$initObjects[$mapper][$id]['obj'] ?? null; - } - - /** - * Get initialized object - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param int $depth Depth - * - * @return mixed - * - * @since 1.0.0 - */ - private static function getInitializedArray(string $mapper, mixed $id, int $depth) : mixed - { - if (!self::isInitializedArray($mapper, $id, $depth)) { - return null; - } - - return self::$initArrays[$mapper][$id]['obj'] ?? null; - } - - /** - * Remove initialized object - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * - * @return void - * - * @since 1.0.0 - */ - private static function removeInitialized(string $mapper, mixed $id) : void - { - if (isset(self::$initObjects[$mapper][$id])) { - unset(self::$initObjects[$mapper][$id]); - } - - if (isset(self::$initArrays[$mapper][$id])) { - unset(self::$initArrays[$mapper][$id]); - } - } - - /** - * Clear cache - * - * @return void - * - * @since 1.0.0 - */ - public static function clearCache() : void - { - self::$initObjects = []; - self::$initArrays = []; - } - - /** - * Find database column name by member name - * - * @param string $name member name - * - * @return null|string - * - * @since 1.0.0 - */ - public static function getColumnByMember(string $name) : ?string - { - foreach (static::$columns as $cName => $column) { - if ($column['internal'] === $name) { - return $cName; - } - } - - return null; - } - - /** - * Get belongsTo definitions - * - * @param string $name member name - * - * @return null|array - * - * @since 1.0.0 - */ - public static function getBelongsTo(string $name) : ?array - { - return static::$belongsTo[$name] ?? []; - } - - /** - * Test if object is null object - * - * @param mixed $obj Object to check - * - * @return bool - * - * @since 1.0.0 - */ - private static function isNullModel(mixed $obj) : bool - { - return \is_object($obj) && \strpos(\get_class($obj), '\Null') !== false; - } - - /** - * Creates the current null object - * - * @param mixed $id Model id - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createNullModel(mixed $id = null) : mixed - { - $class = empty(static::$model) ? \substr(static::class, 0, -6) : static::$model; - $parts = \explode('\\', $class); - $name = $parts[$c = (\count($parts) - 1)]; - $parts[$c] = 'Null' . $name; - $class = \implode('\\', $parts); - - return $id !== null ? new $class($id) : new $class(); - } - - /** - * Create the empty base model - * - * @return mixed - * - * @since 1.0.0 - */ - private static function createBaseModel() : mixed - { - $class = empty(static::$model) ? \substr(static::class, 0, -6) : static::$model; - - /** - * @todo Orange-Management/phpOMS#67 - * Since some models require special initialization a model factory should be implemented. - * This could be a simple initialize() function in the mapper where the default initialize() is the current defined empty initialization in the DataMapperAbstract. - */ - return new $class(); - } - - /** - * Get model from mapper - * - * @return string - * - * @since 1.0.0 - */ - private static function getModelName() : string - { - return empty(static::$model) ? \substr(static::class, 0, -6) : static::$model; - } -} - -/* C0: setup for C1 -CREATE TABLE IF NOT EXISTS `tag` ( - `tag_id` int(11) NOT NULL, - `tag_bla` int(11) NULL, - PRIMARY KEY (`tag_id`) -) DEFAULT CHARSET=utf8; - -CREATE TABLE IF NOT EXISTS `tag_l11n` ( - `tag_l11n_id` int(11) NOT NULL, - `tag_l11n_tag` int(11) NOT NULL, - `tag_l11n_title` varchar(250) NOT NULL, - `tag_l11n_language` varchar(2) NOT NULL, - PRIMARY KEY (`tag_l11n_id`) -) DEFAULT CHARSET=utf8; - -INSERT INTO `tag` (`tag_id`) VALUES - ('1'), ('2'), ('3'); - -INSERT INTO `tag_l11n` (`tag_l11n_id`, `tag_l11n_tag`, `tag_l11n_title`, `tag_l11n_language`) VALUES - ('1', '1', 'German', 'de'), - ('2', '2', 'German', 'de'), - ('3', '1', 'English', 'en'), - ('4', '1', 'Italian', 'it'), - ('5', '3', 'German', 'de'), - ('6', '2', 'English', 'en'), - ('7', '3', 'Spanish', 'sp'); - -*/ - -/* C1: conditional values with priorities -https://dbfiddle.uk/?rdbms=mariadb_10.4&fiddle=54359372c3481cfee85f423af76665e4 -SELECT - `tag_3`.`tag_id` as tag_id_3, `tag_3`.`tag_bla` as tag_bla_3, - `tag_l11n_2`.`tag_l11n_title` as tag_l11n_title_2, `tag_l11n_2`.`tag_l11n_language` -FROM - `tag` as tag_3 -LEFT JOIN - `tag_l11n` as tag_l11n_2 ON `tag_3`.`tag_id` = `tag_l11n_2`.`tag_l11n_tag` -WHERE ( - `tag_l11n_2`.`tag_l11n_language` = 'it' -OR ( - `tag_l11n_2`.`tag_l11n_language` = 'en' - AND NOT EXISTS (SELECT * - FROM tag_l11n t3 - WHERE t3.tag_l11n_tag = tag_3.tag_id - AND t3.tag_l11n_language = 'it') -) -OR ( - NOT EXISTS (SELECT * - FROM tag_l11n t3 - WHERE t3.tag_l11n_tag = tag_3.tag_id - AND t3.tag_l11n_language in ('en', 'it'))) -) -GROUP BY tag_id_3 -ORDER BY - `tag_3`.`tag_id` ASC -LIMIT 25; -*/ - -/* C2: try this -SELECT - `tag_3`.`tag_id` as tag_id_3, - COALESCE(`tag_l11n_2`.`tag_l11n_title`, `tag_l11n_3`.`tag_l11n_title`, `tag_l11n_4`.`tag_l11n_title`) as tag_l11n_title_2 -FROM - `tag` as tag_3 -LEFT JOIN - `tag_l11n` as tag_l11n_2 ON `tag_3`.`tag_id` = `tag_l11n_2`.`tag_l11n_tag` - AND `tag_l11n_2`.`tag_l11n_language` = 'it' -LEFT JOIN - `tag_l11n` as tag_l11n_3 ON `tag_3`.`tag_id` = `tag_l11n_3`.`tag_l11n_tag` - AND `tag_l11n_3`.`tag_l11n_language` = 'en' -LEFT JOIN - `tag_l11n` as tag_l11n_4 ON `tag_3`.`tag_id` = `tag_l11n_4`.`tag_l11n_tag` - AND `tag_l11n_4`.`tag_l11n_language` NOT IN ('en', 'it') -ORDER BY - `tag_3`.`tag_id` ASC -LIMIT 25; -*/ diff --git a/DataStorage/Database/Mapper/DataMapperAbstract.php b/DataStorage/Database/Mapper/DataMapperAbstract.php index 1230c8bdc..05f7c4840 100644 --- a/DataStorage/Database/Mapper/DataMapperAbstract.php +++ b/DataStorage/Database/Mapper/DataMapperAbstract.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace phpOMS\DataStorage\Database\Mapper; use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; +use phpOMS\DataStorage\Database\Query\Builder; use phpOMS\DataStorage\Database\Query\OrderType; /** @@ -41,6 +42,8 @@ abstract class DataMapperAbstract protected array $where = []; + protected ?Builder $query = null; + /** * Database connection. * @@ -55,6 +58,13 @@ abstract class DataMapperAbstract $this->db = $db; } + public function query(Builder $query = null) : self + { + $this->query = $query; + + return $this; + } + // Only for relations, no impact on anything else public function with(string $member) : self { @@ -191,5 +201,5 @@ abstract class DataMapperAbstract return $value; } - abstract public function execute(array ...$options) : mixed; + abstract public function execute(...$options) : mixed; } diff --git a/DataStorage/Database/Mapper/DataMapperFactory.php b/DataStorage/Database/Mapper/DataMapperFactory.php index ac662eced..c88adf5bb 100644 --- a/DataStorage/Database/Mapper/DataMapperFactory.php +++ b/DataStorage/Database/Mapper/DataMapperFactory.php @@ -126,7 +126,7 @@ class DataMapperFactory * @var ConnectionAbstract * @since 1.0.0 */ - public static ConnectionAbstract $db; + protected static ConnectionAbstract $db; /** * Initialized objects for cross reference to reduce initialization costs @@ -230,93 +230,6 @@ class DataMapperFactory return (new DeleteMapper(new static(), $db ?? self::$db))->delete(); } - /** - * Add initialized object to local cache - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * @param object $obj Model to cache locally - * - * @return void - * - * @since 1.0.0 - */ - public static function addInitialized(string $mapper, mixed $id, object $obj = null) : void - { - if (!isset(self::$initObjects[$mapper])) { - self::$initObjects[$mapper] = []; - } - - self::$initObjects[$mapper][$id] = [ - 'obj' => $obj, - ]; - } - - /** - * Check if a object is initialized - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * - * @return bool - * - * @since 1.0.0 - */ - public static function isInitialized(string $mapper, mixed $id) : bool - { - return !empty($id) - && isset(self::$initObjects[$mapper], self::$initObjects[$mapper][$id]); - } - - /** - * Clear cache - * - * @return void - * - * @since 1.0.0 - */ - public static function clearCache() : void - { - self::$initObjects = []; - } - - - /** - * Get initialized object - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * - * @return mixed - * - * @since 1.0.0 - */ - public static function getInitialized(string $mapper, mixed $id) : mixed - { - if (!self::isInitialized($mapper, $id)) { - return null; - } - - return self::$initObjects[$mapper][$id]['obj'] ?? null; - } - - /** - * Remove initialized object - * - * @param string $mapper Mapper name - * @param mixed $id Object id - * - * @return void - * - * @since 1.0.0 - */ - public static function removeInitialized(string $mapper, mixed $id) : void - { - if (isset(self::$initObjects[$mapper][$id])) { - unset(self::$initObjects[$mapper][$id]); - } - } - /** * Test if object is null object * diff --git a/DataStorage/Database/Mapper/DeleteMapper.php b/DataStorage/Database/Mapper/DeleteMapper.php index 49f917d3f..e51ad2dd7 100644 --- a/DataStorage/Database/Mapper/DeleteMapper.php +++ b/DataStorage/Database/Mapper/DeleteMapper.php @@ -36,7 +36,7 @@ class DeleteMapper extends DataMapperAbstract return $this; } - public function execute(array ...$options) : mixed + public function execute(...$options) : mixed { switch($this->type) { case MapperType::DELETE: @@ -59,126 +59,76 @@ class DeleteMapper extends DataMapperAbstract return null; } - $this->mapper::removeInitialized(static::class, $objId); + $this->deleteSingleRelation($obj, $refClass, $this->mapper::BELONGS_TO); $this->deleteHasMany($refClass, $obj, $objId); - $this->deleteModel($obj, $objId, $refClass); + $this->deleteModel($objId); + $this->deleteSingleRelation($obj, $refClass, $this->mapper::OWNS_ONE); return $objId; } - private function deleteModel(object $obj, mixed $objId, \ReflectionClass $refClass = null) : void + private function deleteModel(mixed $objId) : void { $query = new Builder($this->db); $query->delete() ->from($this->mapper::TABLE) ->where($this->mapper::TABLE . '.' . $this->mapper::PRIMARYFIELD, '=', $objId); - $refClass = $refClass ?? new \ReflectionClass($obj); - $properties = $refClass->getProperties(); - - foreach ($properties as $property) { - $propertyName = $property->getName(); - - if (isset($this->mapper::HAS_MANY[$propertyName])) { - continue; - } - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - } - - /** - * @todo Orange-Management/phpOMS#233 - * On delete the relations and relation tables need to be deleted first - * The exception is of course the belongsTo relation. - */ - foreach ($this->mapper::COLUMNS as $key => $column) { - $value = $isPublic ? $obj->{$propertyName} : $property->getValue($obj); - if (isset($this->mapper::OWNS_ONE[$propertyName]) - && $column['internal'] === $propertyName - ) { - $this->deleteOwnsOne($propertyName, $value); - break; - } elseif (isset($this->mapper::BELONGS_TO[$propertyName]) - && $column['internal'] === $propertyName - ) { - $this->deleteBelongsTo($propertyName, $value); - break; - } - } - - if (!$isPublic) { - $property->setAccessible(false); - } - } - $sth = $this->db->con->prepare($query->toSql()); if ($sth !== false) { $sth->execute(); } } - private function deleteBelongsTo(string $propertyName, mixed $obj) : mixed + private function deleteSingleRelation(mixed $obj, \ReflectionClass $refClass, array $relation) : void { - if (!\is_object($obj)) { - return $obj; + if (empty($relation)) { + return; } - /** @var class-string $mapper */ - $mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper']; + foreach ($relation as $member => $relData) { + if (!isset($this->with[$member])) { + continue; + } - /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $propertyName); - $relMapper->depth = $this->depth + 1; + /** @var class-string $mapper */ + $mapper = $relData['mapper']; - return $relMapper->execute($obj); - } + /** @var self $relMapper */ + $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $member); + $relMapper->depth = $this->depth + 1; - private function deleteOwnsOne(string $propertyName, mixed $obj) : mixed - { - if (!\is_object($obj)) { - return $obj; + $refProp = $refClass->getProperty($member); + if (!$refProp->isPublic()) { + $refProp->setAccessible(true); + $relMapper->execute($refProp->getValue($obj)); + $refProp->setAccessible(false); + } else { + $relMapper->execute($obj->{$member}); + } } - - /** @var class-string $mapper */ - $mapper = $this->mapper::OWNS_ONE[$propertyName]['mapper']; - - /** - * @todo Orange-Management/phpOMS#??? [p:low] [t:question] [d:expert] - * Deleting a owned one object is not recommended since it can be owned by something else? - * Or does owns one mean that nothing else can have a relation to this model? - */ - - /** @var self $relMapper */ - $relMapper = $this->createRelationMapper($mapper::delete(db: $this->db), $propertyName); - $relMapper->depth = $this->depth + 1; - - return $relMapper->execute($obj); } private function deleteHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void { - if (empty($this->with) || empty($this->mapper::HAS_MANY)) { + if (empty($this->mapper::HAS_MANY)) { return; } - foreach ($this->mapper::HAS_MANY as $propertyName => $rel) { - if (!isset($this->mapper::HAS_MANY[$propertyName]['mapper'])) { - throw new InvalidMapperException(); - } - - if (isset($rel['column']) || !isset($this->with[$propertyName])) { + foreach ($this->mapper::HAS_MANY as $member => $rel) { + // always + if (!isset($this->with[$member]) && !isset($rel['external'])) { continue; } - $property = $refClass->getProperty($propertyName); - - if (!($isPublic = $property->isPublic())) { - $property->setAccessible(true); - $values = $property->getValue($obj); - $property->setAccessible(false); + $objIds = []; + $refProp = $refClass->getProperty($member); + if (!$refProp->isPublic()) { + $refProp->setAccessible(true); + $values = $refProp->getValue($obj); + $refProp->setAccessible(false); } else { - $values = $obj->{$propertyName}; + $values = $obj->{$member}; } if (!\is_array($values)) { @@ -187,70 +137,55 @@ class DeleteMapper extends DataMapperAbstract } /** @var class-string $mapper */ - $mapper = $this->mapper::HAS_MANY[$propertyName]['mapper']; - $objsIds = []; + $mapper = $this->mapper::HAS_MANY[$member]['mapper']; $relReflectionClass = !empty($values) ? new \ReflectionClass(\reset($values)) : null; - foreach ($values as $key => &$value) { + foreach ($values as $key => $value) { if (!\is_object($value)) { // Is scalar => already in database - $objsIds[$key] = $value; + $objIds[$key] = $value; continue; } - $primaryKey = $mapper::getObjectId($value, $relReflectionClass); - - // already in db - if (!empty($primaryKey)) { - $objsIds[$key] = $mapper::delete(db: $this->db)->execute($value); - - continue; - } - - /** - * @todo Orange-Management/phpOMS#233 - * On delete the relations and relation tables need to be deleted first - * The exception is of course the belongsTo relation. - */ + $objIds[$key] = $mapper::getObjectId($value, $relReflectionClass); } - $this->deleteRelationTable($propertyName, $objsIds, $objId); + // delete relation tables + if (isset($rel['external'])) { + $this->deleteRelationTable($member, $objIds, $objId); + } else { + // only delete related obj if it is NOT in a relation table + // if it is not in a relation table it must be directly related + // this means it CAN ONLY be related to this object and not others + foreach ($objIds as $id) { + $mapper::delete(db: $this->db)->execute($id); + } + } } } - public function deleteRelation(string $member, mixed $id1, mixed $id2) : bool + public function deleteRelationTable(string $member, array $objIds = null, mixed $objId) : void { - if (!isset($this->mapper::HAS_MANY[$member]) || !isset($this->mapper::HAS_MANY[$member]['external'])) { - return false; - } - - $this->mapper::removeInitialized(static::class, $id1); - $this->deleteRelationTable($member, \is_array($id2) ? $id2 : [$id2], $id1); - - return true; - } - - public function deleteRelationTable(string $propertyName, array $objsIds, mixed $objId) : void - { - if (empty($objsIds) - || $this->mapper::HAS_MANY[$propertyName]['table'] === $this->mapper::TABLE - || $this->mapper::HAS_MANY[$propertyName]['table'] === $this->mapper::HAS_MANY[$propertyName]['mapper']::TABLE + if ((empty($objIds) && $objIds !== null) + || $this->mapper::HAS_MANY[$member]['table'] === $this->mapper::TABLE + || $this->mapper::HAS_MANY[$member]['table'] === $this->mapper::HAS_MANY[$member]['mapper']::TABLE ) { return; } - foreach ($objsIds as $src) { - $relQuery = new Builder($this->db); - $relQuery->delete() - ->from($this->mapper::HAS_MANY[$propertyName]['table']) - ->where($this->mapper::HAS_MANY[$propertyName]['table'] . '.' . $this->mapper::HAS_MANY[$propertyName]['external'], '=', $src) - ->where($this->mapper::HAS_MANY[$propertyName]['table'] . '.' . $this->mapper::HAS_MANY[$propertyName]['self'], '=', $objId, 'and'); + $relQuery = new Builder($this->db); + $relQuery->delete() + ->from($this->mapper::HAS_MANY[$member]['table']) + ->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['self'], '=', $objId); - $sth = $this->db->con->prepare($relQuery->toSql()); - if ($sth !== false) { - $sth->execute(); - } + if ($objIds !== null) { + $relQuery->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['external'], 'in', $objIds); + } + + $sth = $this->db->con->prepare($relQuery->toSql()); + if ($sth !== false) { + $sth->execute(); } } } diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index 5ed7b2c17..c7b2406d5 100644 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -78,7 +78,7 @@ class ReadMapper extends DataMapperAbstract return $this; } - public function execute(array ...$options) : mixed + public function execute(...$options) : mixed { switch($this->type) { case MapperType::GET: @@ -116,15 +116,6 @@ class ReadMapper extends DataMapperAbstract // Get initialized objects from memory cache. $obj = []; - foreach ($primaryKeys as $index => $value) { - if (!$this->mapper::isInitialized($this->mapper::class, $value)) { - continue; - } - - $obj[$value] = $this->mapper::getInitialized($this->mapper::class, $value); - unset($this->where[$memberOfPrimaryField]); - unset($primaryKeys[$index]); - } // Get remaining objects (not available in memory cache) or remaining where clauses. if (!empty($primaryKeys) || (!empty($this->where) || $emptyWhere)) { @@ -133,7 +124,6 @@ class ReadMapper extends DataMapperAbstract foreach ($dbData as $row) { $value = $row[$this->mapper::PRIMARYFIELD . '_d' . $this->depth]; $obj[$value] = $this->mapper::createBaseModel(); - $this->mapper::addInitialized($this->mapper::class, $value, $obj[$value]); $obj[$value] = $this->populateAbstract($row, $obj[$value]); $this->loadHasManyRelations($obj[$value]); @@ -153,7 +143,7 @@ class ReadMapper extends DataMapperAbstract public function executeGetRaw(Builder $query = null) : array { - $query ??= $this->getQuery($query); + $query ??= $this->getQuery(); try { $results = false; @@ -192,8 +182,7 @@ class ReadMapper extends DataMapperAbstract */ public function executeCount() : int { - $query = $this->getQuery(); - $query->select('COUNT(*)'); + $query = $this->getQuery(null, ['COUNT(*)' => 'count']); return (int) $query->execute()->fetchColumn(); } @@ -225,14 +214,18 @@ class ReadMapper extends DataMapperAbstract */ public function getQuery(Builder $query = null, array $columns = []) : Builder { - $query ??= new Builder($this->db); + $query ??= $this->query ?? new Builder($this->db, true); $columns = empty($columns) ? (empty($this->columns) ? $this->mapper::COLUMNS : $this->columns) : $columns; foreach ($columns as $key => $values) { - if ($values['writeonly'] ?? false === false) { - $query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth); + if (\is_string($values)) { + $query->selectAs($key, $values); + } else { + if (($values['writeonly'] ?? false) === false) { + $query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth); + } } } @@ -325,7 +318,7 @@ class ReadMapper extends DataMapperAbstract ) { $rel = $this->mapper::OWNS_ONE[$member] ?? ($this->mapper::BELONGS_TO[$member] ?? ($this->mapper::HAS_MANY[$member] ?? null)); } else { - break; + continue; } foreach ($data as $index => $with) { @@ -644,11 +637,6 @@ class ReadMapper extends DataMapperAbstract return $mapper::createNullModel(); } - $obj = $mapper::getInitialized($mapper, $result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1)]); - if ($obj !== null) { - return $obj; - } - /** @var class-string $ownsOneMapper */ $ownsOneMapper = $this->createRelationMapper($mapper::get($this->db), $member); $ownsOneMapper->depth = $this->depth + 1; @@ -703,16 +691,11 @@ class ReadMapper extends DataMapperAbstract /** @var class-string $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], '='); + $belongsToMapper->where($this->mapper::BELONGS_TO[$member]['by'], $result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['by']) . '_d' . $this->depth + 1], '='); return $belongsToMapper->execute(); } - $obj = $mapper::getInitialized($mapper, $result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1)]); - if ($obj !== null) { - return $obj; - } - /** @var class-string $belongsToMapper */ $belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member); $belongsToMapper->depth = $this->depth + 1; @@ -731,7 +714,7 @@ class ReadMapper extends DataMapperAbstract */ public function loadHasManyRelations(mixed $obj) : void { - if (empty($this->with) || empty($this->mapper::HAS_MANY)) { + if (empty($this->with)) { return; } @@ -740,52 +723,91 @@ class ReadMapper extends DataMapperAbstract return; } - $refClass = new \ReflectionClass($obj); + $refClass = null; // @todo: check if there are more cases where the relation is already loaded with joins etc. // there can be pseudo has many elements like localizations. They are has manies but these are already loaded with joins! - foreach ($this->mapper::HAS_MANY as $member => $many) { - if (isset($many['column']) || !isset($this->with[$member])) { + foreach ($this->with as $member => $withData) { + if (isset($this->mapper::HAS_MANY[$member])) { + $many = $this->mapper::HAS_MANY[$member]; + if (isset($many['column'])) { + continue; + } + + $query = new Builder($this->db, true); + $src = $many['external'] ?? $many['mapper']::PRIMARYFIELD; + + // @todo: what if a specific column name is defined instead of primaryField for the join? Fix, it should be stored in 'column' + $query->select($many['table'] . '.' . $src) + ->from($many['table']) + ->where($many['table'] . '.' . $many['self'], '=', $primaryKey); + + if ($many['mapper']::TABLE !== $many['table']) { + $query->leftJoin($many['mapper']::TABLE) + ->on($many['table'] . '.' . $src, '=', $many['mapper']::TABLE . '.' . $many['mapper']::PRIMARYFIELD); + } + + $sth = $this->db->con->prepare($query->toSql()); + if ($sth === false) { + continue; + } + + $sth->execute(); + $result = $sth->fetchAll(\PDO::FETCH_COLUMN); + + if (empty($result)) { + continue; + } + + $objects = $this->createRelationMapper($many['mapper']::get(db: $this->db), $member) + ->where($many['mapper']::COLUMNS[$many['mapper']::PRIMARYFIELD]['internal'], $result, 'in') + ->execute(); + + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } + + $refProp = $refClass->getProperty($member); + if (!$refProp->isPublic()) { + $refProp->setAccessible(true); + $refProp->setValue($obj, !\is_array($objects) + ? [$many['mapper']::getObjectId($objects) => $objects] + : $objects + ); + $refProp->setAccessible(false); + } else { + $obj->{$member} = !\is_array($objects) + ? [$many['mapper']::getObjectId($objects) => $objects] + : $objects; + } + continue; - } + } elseif (isset($this->mapper::OWNS_ONE[$member]) + || isset($this->mapper::BELONGS_TO[$member]) + ) { + $relation = isset($this->mapper::OWNS_ONE[$member]) + ? $this->mapper::OWNS_ONE[$member] + : $this->mapper::BELONGS_TO[$member]; - $query = new Builder($this->db); - $src = $many['external'] ?? $many['mapper']::PRIMARYFIELD; + if (\count($withData) < 2) { + continue; + } - // @todo: what if a specific column name is defined instead of primaryField for the join? Fix, it should be stored in 'column' - $query->select($many['table'] . '.' . $src) - ->from($many['table']) - ->where($many['table'] . '.' . $many['self'], '=', $primaryKey); + if ($refClass === null) { + $refClass = new \ReflectionClass($obj); + } - if ($many['mapper']::TABLE !== $many['table']) { - $query->leftJoin($many['mapper']::TABLE) - ->on($many['table'] . '.' . $src, '=', $many['mapper']::TABLE . '.' . $many['mapper']::PRIMARYFIELD); - } + /** @var ReadMapper $relMapper */ + $relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member); - $sth = $this->db->con->prepare($query->toSql()); - if ($sth === false) { - continue; - } - - $sth->execute(); - $result = $sth->fetchAll(\PDO::FETCH_COLUMN); - - $objects = $this->createRelationMapper($many['mapper']::get(db: $this->db), $member) - ->where($many['mapper']::COLUMNS[$many['mapper']::PRIMARYFIELD]['internal'], $result, 'in') - ->execute(); - - $refProp = $refClass->getProperty($member); - if (!$refProp->isPublic()) { - $refProp->setAccessible(true); - $refProp->setValue($obj, !\is_array($objects) && !isset($this->mapper::HAS_MANY[$member]['conditional']) - ? [$many['mapper']::getObjectId($objects) => $objects] - : $objects - ); - $refProp->setAccessible(false); - } else { - $obj->{$member} = !\is_array($objects) && !isset($this->mapper::HAS_MANY[$member]['conditional']) - ? [$many['mapper']::getObjectId($objects) => $objects] - : $objects; + $refProp = $refClass->getProperty($member); + if (!$refProp->isPublic()) { + $refProp->setAccessible(true); + $relMapper->loadHasManyRelations($refProp->getValue($obj)); + $refProp->setAccessible(false); + } else { + $relMapper->loadHasManyRelations($obj->{$member}); + } } } } diff --git a/DataStorage/Database/Mapper/UpdateMapper.php b/DataStorage/Database/Mapper/UpdateMapper.php index fa9e76310..a96cc3413 100644 --- a/DataStorage/Database/Mapper/UpdateMapper.php +++ b/DataStorage/Database/Mapper/UpdateMapper.php @@ -38,7 +38,7 @@ class UpdateMapper extends DataMapperAbstract return $this; } - public function execute(array ...$options) : mixed + public function execute(...$options) : mixed { switch($this->type) { case MapperType::UPDATE: @@ -61,8 +61,6 @@ class UpdateMapper extends DataMapperAbstract return $objId === 0 ? null : $objId; } - $this->mapper::addInitialized(static::class, $objId, $obj); - $this->updateHasMany($refClass, $obj, $objId); if (empty($objId)) { @@ -184,6 +182,7 @@ class UpdateMapper extends DataMapperAbstract 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; } diff --git a/DataStorage/Database/Mapper/WriteMapper.php b/DataStorage/Database/Mapper/WriteMapper.php index 67c27502a..1315cda18 100644 --- a/DataStorage/Database/Mapper/WriteMapper.php +++ b/DataStorage/Database/Mapper/WriteMapper.php @@ -36,7 +36,7 @@ class WriteMapper extends DataMapperAbstract return $this; } - public function execute(array ...$options) : mixed + public function execute(...$options) : mixed { switch($this->type) { case MapperType::CREATE: @@ -181,16 +181,12 @@ class WriteMapper extends DataMapperAbstract } else { $obj = $obj->{$this->mapper::BELONGS_TO[$propertyName]['by']}; } - - /** @var class-string $mapper */ - $mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper']::getBelongsTo($this->mapper::BELONGS_TO[$propertyName]['by'])['mapper']; - $primaryKey = $mapper::getObjectId($obj); - } else { - /** @var class-string $mapper */ - $mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper']; - $primaryKey = $mapper::getObjectId($obj); } + /** @var class-string $mapper */ + $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. return empty($primaryKey) ? $mapper::create(db: $this->db)->execute($obj) : $primaryKey; } @@ -241,7 +237,7 @@ class WriteMapper extends DataMapperAbstract $property->setAccessible(false); } - // conditionals + // @todo: conditionals??? continue; } @@ -274,28 +270,28 @@ class WriteMapper extends DataMapperAbstract if (!isset($this->mapper::HAS_MANY[$propertyName]['external'])) { $relProperty = $relReflectionClass->getProperty($internalName); - if (!$isPublic) { + if (!($isRelPublic = $relProperty->isPublic())) { $relProperty->setAccessible(true); } // todo maybe consider to just set the column type to object, and then check for that (might be faster) - if (isset($mapper::$belongsTo[$internalName]) - || isset($mapper::$ownsOne[$internalName]) + if (isset($mapper::BELONGS_TO[$internalName]) + || isset($mapper::OWNS_ONE[$internalName]) ) { - if (!$isPublic) { + if (!$isRelPublic) { $relProperty->setValue($value, $this->mapper::createNullModel($objId)); } else { $value->{$internalName} = $this->mapper::createNullModel($objId); } } else { - if (!$isPublic) { + if (!$isRelPublic) { $relProperty->setValue($value, $objId); } else { $value->{$internalName} = $objId; } } - if (!$isPublic) { + if (!$isRelPublic) { $relProperty->setAccessible(false); } } diff --git a/Localization/Defaults/CityMapper.php b/Localization/Defaults/CityMapper.php index 0986d641a..576035362 100644 --- a/Localization/Defaults/CityMapper.php +++ b/Localization/Defaults/CityMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\Localization\Defaults; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -class CityMapper extends DataMapperAbstract +class CityMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ class CityMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'city_id' => ['name' => 'city_id', 'type' => 'int', 'internal' => 'id'], 'city_city' => ['name' => 'city_city', 'type' => 'string', 'internal' => 'name'], 'city_country' => ['name' => 'city_country', 'type' => 'string', 'internal' => 'countryCode'], @@ -48,7 +48,7 @@ class CityMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'city'; + public const TABLE = 'city'; /** * Primary field name. @@ -56,5 +56,5 @@ class CityMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'city_id'; + public const PRIMARYFIELD ='city_id'; } diff --git a/Localization/Defaults/CountryMapper.php b/Localization/Defaults/CountryMapper.php index e7a8fe7c4..87a25e756 100644 --- a/Localization/Defaults/CountryMapper.php +++ b/Localization/Defaults/CountryMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\Localization\Defaults; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -class CountryMapper extends DataMapperAbstract +class CountryMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ class CountryMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + 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'], @@ -48,7 +48,7 @@ class CountryMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'country'; + public const TABLE = 'country'; /** * Primary field name. @@ -56,5 +56,5 @@ class CountryMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'country_id'; + public const PRIMARYFIELD ='country_id'; } diff --git a/Localization/Defaults/CurrencyMapper.php b/Localization/Defaults/CurrencyMapper.php index 2aa068c11..974686f25 100644 --- a/Localization/Defaults/CurrencyMapper.php +++ b/Localization/Defaults/CurrencyMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\Localization\Defaults; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -final class CurrencyMapper extends DataMapperAbstract +final class CurrencyMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ final class CurrencyMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'currency_id' => ['name' => 'currency_id', 'type' => 'int', 'internal' => 'id'], 'currency_name' => ['name' => 'currency_name', 'type' => 'string', 'internal' => 'name'], 'currency_code' => ['name' => 'currency_code', 'type' => 'string', 'internal' => 'code'], @@ -49,7 +49,7 @@ final class CurrencyMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'currency'; + public const TABLE = 'currency'; /** * Primary field name. @@ -57,5 +57,5 @@ final class CurrencyMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'currency_id'; + public const PRIMARYFIELD ='currency_id'; } diff --git a/Localization/Defaults/IbanMapper.php b/Localization/Defaults/IbanMapper.php index 6d23a7afc..c70a2993d 100644 --- a/Localization/Defaults/IbanMapper.php +++ b/Localization/Defaults/IbanMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\Localization\Defaults; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -class IbanMapper extends DataMapperAbstract +class IbanMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ class IbanMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'iban_id' => ['name' => 'iban_id', 'type' => 'int', 'internal' => 'id'], 'iban_country' => ['name' => 'iban_country', 'type' => 'string', 'internal' => 'country'], 'iban_chars' => ['name' => 'iban_chars', 'type' => 'int', 'internal' => 'chars'], @@ -46,7 +46,7 @@ class IbanMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'iban'; + public const TABLE = 'iban'; /** * Primary field name. @@ -54,5 +54,5 @@ class IbanMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'iban_id'; + public const PRIMARYFIELD ='iban_id'; } diff --git a/Localization/Defaults/LanguageMapper.php b/Localization/Defaults/LanguageMapper.php index f11120305..59196a659 100644 --- a/Localization/Defaults/LanguageMapper.php +++ b/Localization/Defaults/LanguageMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\Localization\Defaults; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -class LanguageMapper extends DataMapperAbstract +class LanguageMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ class LanguageMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'language_id' => ['name' => 'language_id', 'type' => 'int', 'internal' => 'id'], 'language_name' => ['name' => 'language_name', 'type' => 'string', 'internal' => 'name'], 'language_native' => ['name' => 'language_native', 'type' => 'string', 'internal' => 'native'], @@ -47,7 +47,7 @@ class LanguageMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'language'; + public const TABLE = 'language'; /** * Primary field name. @@ -55,5 +55,5 @@ class LanguageMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'language_id'; + public const PRIMARYFIELD ='language_id'; } diff --git a/Localization/NullLocalization.php b/Localization/NullLocalization.php index a81c1c93b..c3280400a 100644 --- a/Localization/NullLocalization.php +++ b/Localization/NullLocalization.php @@ -24,4 +24,8 @@ namespace phpOMS\Localization; */ final class NullLocalization extends Localization { + public function __construct(int $id = 0) + { + $this->id = $id; + } } diff --git a/Module/ModuleAbstract.php b/Module/ModuleAbstract.php index 9a32e1738..3910a3bed 100644 --- a/Module/ModuleAbstract.php +++ b/Module/ModuleAbstract.php @@ -265,7 +265,7 @@ abstract class ModuleAbstract $id = 0; if (\is_string($mapper)) { - $id = $mapper::create($obj); + $id = $mapper::create()->execute($obj); } else { $mapper(); } @@ -305,7 +305,7 @@ abstract class ModuleAbstract $id = 0; if (\is_string($mapper)) { - $id = $mapper::create($obj); + $id = $mapper::create()->execute($obj); } else { $mapper(); } @@ -344,7 +344,7 @@ abstract class ModuleAbstract $id = 0; if (\is_string($mapper)) { - $id = $mapper::update($new); + $id = $mapper::update()->execute($new); } else { $mapper(); } @@ -383,7 +383,7 @@ abstract class ModuleAbstract $id = 0; if (\is_string($mapper)) { - $id = $mapper::delete($obj); + $id = $mapper::delete()->execute($obj); } else { $mapper(); } @@ -421,7 +421,7 @@ abstract class ModuleAbstract $trigger = static::NAME . '-' . $trigger . '-relation-create'; $this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1); - $mapper::createRelation($field, $rel1, $rel2); + $mapper::writer()->createRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1); $this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', [ $account, @@ -455,7 +455,7 @@ abstract class ModuleAbstract $trigger = static::NAME . '-' . $trigger . '-relation-delete'; $this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1); - $mapper::deleteRelation($field, $rel1, $rel2); + $mapper::remover()->deleteRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1); $this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', [ $account, diff --git a/Router/SocketRouter.php b/Router/SocketRouter.php index b8154f7b7..6a0e4f045 100644 --- a/Router/SocketRouter.php +++ b/Router/SocketRouter.php @@ -137,8 +137,8 @@ final class SocketRouter implements RouterInterface foreach ($destination as $d) { // if permission check is invalid - if ((isset($d['permission']) && $account === null) - || (isset($d['permission']) + if ((isset($d['permission']) && !empty($d['permission']) && $account === null) + || (isset($d['permission']) && !empty($d['permission']) && !$account?->hasPermission( $d['permission']['type'] ?? null, $orgId, $app, $d['permission']['module'] ?? null, $d['permission']['state'] ?? null ) diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index bb343e0f6..cafae4a4a 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -15,7 +15,7 @@ if (\is_file('vendor/autoload.php')) { require_once __DIR__ . '/../Autoloader.php'; use phpOMS\DataStorage\Database\DatabasePool; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; use phpOMS\DataStorage\Session\HttpSession; use phpOMS\Log\FileLogger; @@ -358,7 +358,7 @@ $GLOBALS['dbpool']->create('update', $CONFIG['db']['core']['masters']['update']) $GLOBALS['dbpool']->create('delete', $CONFIG['db']['core']['masters']['delete']); $GLOBALS['dbpool']->create('schema', $CONFIG['db']['core']['masters']['schema']); -DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); +DataMapperFactory::db($GLOBALS['dbpool']->get()); $GLOBALS['frameworkpath'] = '/'; diff --git a/tests/DataStorage/Database/DataMapperAbstractTest.php b/tests/DataStorage/Database/DataMapperAbstractTest.php index 0d4dfd419..1822784d5 100644 --- a/tests/DataStorage/Database/DataMapperAbstractTest.php +++ b/tests/DataStorage/Database/DataMapperAbstractTest.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace phpOMS\tests\DataStorage\Database; +use phpOMS\DataStorage\Database\Query\OrderType; use phpOMS\tests\DataStorage\Database\TestModel\BaseModel; use phpOMS\tests\DataStorage\Database\TestModel\BaseModelMapper; use phpOMS\tests\DataStorage\Database\TestModel\Conditional; @@ -23,7 +24,7 @@ use phpOMS\tests\DataStorage\Database\TestModel\ManyToManyRelModelMapper; use phpOMS\tests\DataStorage\Database\TestModel\NullBaseModel; /** - * @testdox phpOMS\tests\DataStorage\Database\DataMapperAbstractTest: Datamapper for database models + * @testdox phpOMS\tests\DataStorage\Database\Mapper\DataMapperAbstractTest: Datamapper for database models * * @internal */ @@ -161,7 +162,6 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase protected function tearDown() : void { - BaseModelMapper::clearCache(); $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_conditional')->execute(); $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_base')->execute(); $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_belongs_to_one')->execute(); @@ -171,83 +171,70 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_has_many_rel_relations')->execute(); } - /** - * @testdox The datamapper has the expected default values after initialization - * @covers phpOMS\DataStorage\Database\DataMapperAbstract - * @group framework - */ - public function testDefault() : void - { - self::assertEquals('test_base_id', BaseModelMapper::getPrimaryField()); - self::assertEquals('test_base', BaseModelMapper::getTable()); - self::assertEquals('test_base_datetime', BaseModelMapper::getCreatedAt()); - } - /** * @testdox The datamapper successfully creates a database entry of a model - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testCreate() : void { - self::assertGreaterThan(0, BaseModelMapper::create($this->model)); + self::assertGreaterThan(0, BaseModelMapper::create()->execute($this->model)); self::assertGreaterThan(0, $this->model->getId()); } public function testCreateNullModel() : void { $nullModel1 = new NullBaseModel(); - self::assertNull(BaseModelMapper::create($nullModel1)); + self::assertNull(BaseModelMapper::create()->execute($nullModel1)); $nullModel2 = new NullBaseModel(77); - self::assertEquals(77, BaseModelMapper::create($nullModel2)); + self::assertEquals(77, BaseModelMapper::create()->execute($nullModel2)); } public function testCreateAlreadyCreatedModel() : void { - self::assertGreaterThan(0, $id = BaseModelMapper::create($this->model)); + self::assertGreaterThan(0, $id = BaseModelMapper::create()->execute($this->model)); self::assertGreaterThan(0, $this->model->getId()); - self::assertEquals($id, BaseModelMapper::create($this->model)); + self::assertEquals($id, BaseModelMapper::create()->execute($this->model)); self::assertEquals($id, $this->model->getId()); } - /** - * @testdox The datamapper successfully creates a database entry of array data - * @covers phpOMS\DataStorage\Database\DataMapperAbstract - * @group framework - */ - public function testCreateArray() : void - { - self::assertGreaterThan(0, BaseModelMapper::createArray($this->modelArray)); - self::assertGreaterThan(0, $this->modelArray['id']); - } - public function testCreateHasManyRelation() : void { - $id1 = BaseModelMapper::create($this->model); + $id1 = BaseModelMapper::create()->execute($this->model); $count1 = \count($this->model->hasManyRelations); $hasMany = new ManyToManyRelModel(); - $id2 = ManyToManyRelModelMapper::create($hasMany); + $id2 = ManyToManyRelModelMapper::create()->execute($hasMany); - BaseModelMapper::createRelation('hasManyRelations', $id1, $id2); + BaseModelMapper::writer()->createRelationTable('hasManyRelations', [$id2], $id1); - BaseModelMapper::clearCache(); - - $base = BaseModelMapper::get($id1); + $base = BaseModelMapper::get()->with('hasManyRelations')->where('id', $id1)->execute(); self::assertCount($count1 + 1, $base->hasManyRelations); } /** * @testdox The datamapper successfully returns a database entry as model - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testRead() : void { - $id = BaseModelMapper::create($this->model); - $modelR = BaseModelMapper::get($id); + $id = BaseModelMapper::create()->execute($this->model); + + /** @var BaseModel $modelR */ + $modelR = BaseModelMapper::get()->where('id', $id)->execute(); self::assertEquals($this->model->getId(), $modelR->getId()); self::assertEquals($this->model->string, $modelR->string); @@ -275,14 +262,14 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase public function testGetAll() : void { - BaseModelMapper::create($this->model); - self::assertCount(1, BaseModelMapper::getAll()); + BaseModelMapper::create()->execute($this->model); + self::assertCount(1, BaseModelMapper::getAll()->execute()); } public function testGetFor() : void { - $id = BaseModelMapper::create($this->model); - $for = ManyToManyDirectModelMapper::getFor($id, 'to'); + $id = BaseModelMapper::create()->execute($this->model); + $for = ManyToManyDirectModelMapper::getAll()->where('to', $id)->execute(); self::assertEquals( \reset($this->model->hasManyDirect)->string, @@ -298,18 +285,22 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $model2 = new BaseModel(); $model2->string = '456'; - $id1 = BaseModelMapper::create($model1); - $id2 = BaseModelMapper::create($model2); + $id1 = BaseModelMapper::create()->execute($model1); + $id2 = BaseModelMapper::create()->execute($model2); - $by = BaseModelMapper::getBy('456', 'string'); + $by = BaseModelMapper::get()->where('string', '456')->execute(); self::assertEquals($model2->getId(), $by->getId()); } public function testGetCached() : void { - $id = BaseModelMapper::create($this->model); - $modelR = BaseModelMapper::get($id); - $modelR2 = BaseModelMapper::get($id); + $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()); } @@ -318,23 +309,28 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase { $model1 = new BaseModel(); $model1->datetime = new \DateTime('now'); - $id1 = BaseModelMapper::create($model1); + $id1 = BaseModelMapper::create()->execute($model1); \sleep(1); $model2 = new BaseModel(); $model2->datetime = new \DateTime('now'); - $id2 = BaseModelMapper::create($model2); + $id2 = BaseModelMapper::create()->execute($model2); - $newest = BaseModelMapper::getNewest(); + $newest = BaseModelMapper::getAll()->sort('id', OrderType::DESC)->limit(1)->execute(); self::assertEquals($id2, \reset($newest)->getId()); } public function testGetNullModel() : void { - self::assertEquals(NullBaseModel::class, \get_class(BaseModelMapper::get(99))); + self::assertEquals(NullBaseModel::class, \get_class(BaseModelMapper::get()->where('id', 99)->execute())); } /** - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testFind() : void @@ -347,18 +343,23 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $model2->string = 'hallo sir'; $model3->string = 'seasiren'; - BaseModelMapper::create($model1); - BaseModelMapper::create($model2); - BaseModelMapper::create($model3); + BaseModelMapper::create()->execute($model1); + BaseModelMapper::create()->execute($model2); + BaseModelMapper::create()->execute($model3); - $found = BaseModelMapper::find('sir'); + $found = BaseModelMapper::getAll()->where('string', '%sir%' , 'LIKE')->execute(); self::assertCount(2, $found); self::assertEquals($model2->string, \reset($found)->string); self::assertEquals($model3->string, \end($found)->string); } /** - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testWithConditional() : void @@ -371,29 +372,29 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $model2->string = 'hallo sir'; $model3->string = 'seasiren'; - $id1 = BaseModelMapper::create($model1); - $id2 = BaseModelMapper::create($model2); - $id3 = BaseModelMapper::create($model3); + $id1 = BaseModelMapper::create()->execute($model1); + $id2 = BaseModelMapper::create()->execute($model2); + $id3 = BaseModelMapper::create()->execute($model3); $cond1 = new Conditional(); $cond1->language = 'de'; $cond1->title = 'cond1_de'; $cond1->base = $id1; - ConditionalMapper::create($cond1); + ConditionalMapper::create()->execute($cond1); $cond2 = new Conditional(); $cond2->language = 'en'; $cond2->title = 'cond1_en'; $cond2->base = $id1; - ConditionalMapper::create($cond2); + ConditionalMapper::create()->execute($cond2); $cond3 = new Conditional(); $cond3->language = 'de'; $cond3->title = 'cond2_de'; $cond3->base = $id2; - ConditionalMapper::create($cond3); + ConditionalMapper::create()->execute($cond3); - $found = BaseModelMapper::with('language', 'de')::getAll(); + $found = BaseModelMapper::getAll()->with('conditional')->where('conditional/language', 'de')->execute(); self::assertCount(2, $found); self::assertEquals($model1->string, \reset($found)->string); self::assertEquals($model2->string, \end($found)->string); @@ -401,50 +402,20 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertEquals('cond2_de', \end($found)->conditional); } - /** - * @testdox The datamapper successfully returns a database entry as array - * @covers phpOMS\DataStorage\Database\DataMapperAbstract - * @group framework - */ - public function testReadArray() : void - { - $id = BaseModelMapper::createArray($this->modelArray); - $modelR = BaseModelMapper::getArray($id); - - self::assertEquals($this->modelArray['id'], $modelR['id']); - self::assertEquals($this->modelArray['string'], $modelR['string']); - self::assertEquals($this->modelArray['int'], $modelR['int']); - self::assertEquals($this->modelArray['bool'], $modelR['bool']); - self::assertEquals($this->modelArray['float'], $modelR['float']); - self::assertEquals($this->modelArray['null'], $modelR['null']); - self::assertEquals($this->modelArray['datetime']->format('Y-m-d'), $modelR['datetime']->format('Y-m-d')); - self::assertNull($modelR['datetime_null']); - - self::assertCount(2, $modelR['hasManyDirect']); - self::assertCount(2, $modelR['hasManyRelations']); - self::assertEquals(\reset($this->modelArray['hasManyDirect'])['string'], \reset($modelR['hasManyDirect'])['string']); - self::assertEquals(\reset($this->modelArray['hasManyRelations'])['string'], \reset($modelR['hasManyRelations'])['string']); - self::assertEquals($this->modelArray['ownsOneSelf']['string'], $modelR['ownsOneSelf']['string']); - self::assertEquals($this->modelArray['belongsToOne']['string'], $modelR['belongsToOne']['string']); - - $for = ManyToManyDirectModelMapper::getForArray($id, 'to'); - self::assertEquals( - \reset($this->modelArray['hasManyDirect'])['string'], - \reset($for)['string'] - ); - - self::assertCount(1, BaseModelMapper::getAllArray()); - } - /** * @testdox The datamapper successfully updates a database entry from a model - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testUpdate() : void { - $id = BaseModelMapper::create($this->model); - $modelR = BaseModelMapper::get($id); + $id = BaseModelMapper::create()->execute($this->model); + $modelR = BaseModelMapper::get()->where('id', $id)->execute(); $modelR->string = 'Update'; $modelR->int = '321'; @@ -454,8 +425,8 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase $modelR->datetime = new \DateTime('now'); $modelR->datetime_null = null; - $id2 = BaseModelMapper::update($modelR); - $modelR2 = BaseModelMapper::get($id2); + $id2 = BaseModelMapper::update()->execute($modelR); + $modelR2 = BaseModelMapper::get()->where('id', $id2)->execute(); self::assertEquals($modelR->string, $modelR2->string); self::assertEquals($modelR->int, $modelR2->int); @@ -471,52 +442,22 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase */ } - /** - * @testdox The datamapper successfully updates a database entry from an array - * @covers phpOMS\DataStorage\Database\DataMapperAbstract - * @group framework - */ - public function testUpdateArray() : void - { - $id = BaseModelMapper::createArray($this->modelArray); - $modelR = BaseModelMapper::getArray($id); - - $modelR['string'] = 'Update'; - $modelR['int'] = '321'; - $modelR['bool'] = true; - $modelR['float'] = 3.15; - $modelR['null'] = null; - $modelR['datetime'] = new \DateTime('now'); - $modelR['datetime_null'] = null; - - $id2 = BaseModelMapper::updateArray($modelR); - $modelR2 = BaseModelMapper::getArray($id2); - - self::assertEquals($modelR['string'], $modelR2['string']); - self::assertEquals($modelR['int'], $modelR2['int']); - self::assertEquals($modelR['bool'], $modelR2['bool']); - self::assertEquals($modelR['float'], $modelR2['float']); - 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). - */ - } - /** * @testdox The datamapper successfully deletes a database entry from a model - * @covers phpOMS\DataStorage\Database\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperAbstract + * @covers phpOMS\DataStorage\Database\Mapper\DataMapperFactory + * @covers phpOMS\DataStorage\Database\Mapper\ReadMapper + * @covers phpOMS\DataStorage\Database\Mapper\WriteMapper + * @covers phpOMS\DataStorage\Database\Mapper\UpdateMapper + * @covers phpOMS\DataStorage\Database\Mapper\DeleteMapper * @group framework */ public function testDelete() : void { /* - $id = BaseModelMapper::create($this->model); + $id = BaseModelMapper::create()->execute($this->model); BaseModelMapper::delete($this->model); - $modelR = BaseModelMapper::get($id); + $modelR = BaseModelMapper::get()->where('id', $id)->execute(); self::assertInstanceOf('phpOMS\tests\DataStorage\Database\TestModel\NullBaseModel', $modelR); */ @@ -528,14 +469,16 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase public function testDeleteHasManyRelation() : void { - $id1 = BaseModelMapper::create($this->model); + $id1 = BaseModelMapper::create()->execute($this->model); $count1 = \count($this->model->hasManyRelations); - BaseModelMapper::deleteRelation('hasManyRelations', $id1, \reset($this->model->hasManyRelations)->id); + /** @var ManyToManyRel $rel */ + $rel = \reset($this->model->hasManyRelations); - BaseModelMapper::clearCache(); - $base = BaseModelMapper::get($id1); + BaseModelMapper::remover()->deleteRelationTable('hasManyRelations', [$rel->id], $id1); + + $base = BaseModelMapper::get()->with('hasManyRelations')->where('id', $id1)->execute(); self::assertCount($count1 - 1, $base->hasManyRelations); } diff --git a/tests/DataStorage/Database/TestModel/BaseModelMapper.php b/tests/DataStorage/Database/TestModel/BaseModelMapper.php index b2cc520c2..8335d2d37 100644 --- a/tests/DataStorage/Database/TestModel/BaseModelMapper.php +++ b/tests/DataStorage/Database/TestModel/BaseModelMapper.php @@ -11,11 +11,12 @@ * @link https://orange-management.org */ declare(strict_types=1); + namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; -class BaseModelMapper extends DataMapperAbstract +class BaseModelMapper extends DataMapperFactory { /** * Columns. @@ -23,7 +24,7 @@ class BaseModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_base_id' => ['name' => 'test_base_id', 'type' => 'int', 'internal' => 'id'], 'test_base_string' => ['name' => 'test_base_string', 'type' => 'string', 'internal' => 'string', 'autocomplete' => true], 'test_base_int' => ['name' => 'test_base_int', 'type' => 'int', 'internal' => 'int'], @@ -44,14 +45,14 @@ class BaseModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $belongsTo = [ + public const BELONGS_TO = [ 'belongsToOne' => [ 'mapper' => BelongsToModelMapper::class, 'external' => 'test_base_belongs_to_one', ], ]; - protected static array $ownsOne = [ + public const OWNS_ONE = [ 'ownsOneSelf' => [ 'mapper' => OwnsOneModelMapper::class, 'external' => 'test_base_owns_one_self', @@ -64,7 +65,7 @@ class BaseModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $hasMany = [ + public const HAS_MANY = [ 'hasManyDirect' => [ 'mapper' => ManyToManyDirectModelMapper::class, 'table' => 'test_has_many_direct', @@ -82,14 +83,13 @@ class BaseModelMapper extends DataMapperAbstract 'table' => 'test_conditional', 'self' => 'test_conditional_base', 'column' => 'title', - 'conditional' => true, 'external' => null, ], ]; - protected static string $table = 'test_base'; + public const TABLE = 'test_base'; - protected static string $createdAt = 'test_base_datetime'; + public const CREATED_AT = 'test_base_datetime'; - protected static string $primaryField = 'test_base_id'; + public const PRIMARYFIELD ='test_base_id'; } diff --git a/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php b/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php index b2bf5a0b1..cd577dea2 100644 --- a/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php +++ b/tests/DataStorage/Database/TestModel/BelongsToModelMapper.php @@ -11,11 +11,12 @@ * @link https://orange-management.org */ declare(strict_types=1); + namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; -class BelongsToModelMapper extends DataMapperAbstract +class BelongsToModelMapper extends DataMapperFactory { /** * Columns. @@ -23,12 +24,12 @@ class BelongsToModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_belongs_to_one_id' => ['name' => 'test_belongs_to_one_id', 'type' => 'int', 'internal' => 'id'], 'test_belongs_to_one_string' => ['name' => 'test_belongs_to_one_string', 'type' => 'string', 'internal' => 'string'], ]; - protected static string $table = 'test_belongs_to_one'; + public const TABLE = 'test_belongs_to_one'; - protected static string $primaryField = 'test_belongs_to_one_id'; + public const PRIMARYFIELD ='test_belongs_to_one_id'; } diff --git a/tests/DataStorage/Database/TestModel/ConditionalMapper.php b/tests/DataStorage/Database/TestModel/ConditionalMapper.php index 46226da39..35af93aec 100644 --- a/tests/DataStorage/Database/TestModel/ConditionalMapper.php +++ b/tests/DataStorage/Database/TestModel/ConditionalMapper.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; /** * Tag mapper class. @@ -24,7 +24,7 @@ use phpOMS\DataStorage\Database\DataMapperAbstract; * @link https://orange-management.org * @since 1.0.0 */ -final class ConditionalMapper extends DataMapperAbstract +final class ConditionalMapper extends DataMapperFactory { /** * Columns. @@ -32,7 +32,7 @@ final class ConditionalMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_conditional_id' => ['name' => 'test_conditional_id', 'type' => 'int', 'internal' => 'id'], 'test_conditional_title' => ['name' => 'test_conditional_title', 'type' => 'string', 'internal' => 'title', 'autocomplete' => true], 'test_conditional_base' => ['name' => 'test_conditional_base', 'type' => 'int', 'internal' => 'base'], @@ -45,7 +45,7 @@ final class ConditionalMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $table = 'test_conditional'; + public const TABLE = 'test_conditional'; /** * Primary field name. @@ -53,5 +53,5 @@ final class ConditionalMapper extends DataMapperAbstract * @var string * @since 1.0.0 */ - protected static string $primaryField = 'test_conditional_id'; + public const PRIMARYFIELD ='test_conditional_id'; } diff --git a/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php b/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php index 305273b50..ed65c4fe0 100644 --- a/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php +++ b/tests/DataStorage/Database/TestModel/ManyToManyDirectModelMapper.php @@ -13,9 +13,9 @@ declare(strict_types=1); namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; -class ManyToManyDirectModelMapper extends DataMapperAbstract +class ManyToManyDirectModelMapper extends DataMapperFactory { /** * Columns. @@ -23,13 +23,13 @@ class ManyToManyDirectModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_has_many_direct_id' => ['name' => 'test_has_many_direct_id', 'type' => 'int', 'internal' => 'id'], 'test_has_many_direct_string' => ['name' => 'test_has_many_direct_string', 'type' => 'string', 'internal' => 'string'], 'test_has_many_direct_to' => ['name' => 'test_has_many_direct_to', 'type' => 'int', 'internal' => 'to'], ]; - protected static string $table = 'test_has_many_direct'; + public const TABLE = 'test_has_many_direct'; - protected static string $primaryField = 'test_has_many_direct_id'; + 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 dc81362c8..8ef9271ec 100644 --- a/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php +++ b/tests/DataStorage/Database/TestModel/ManyToManyRelModelMapper.php @@ -11,11 +11,12 @@ * @link https://orange-management.org */ declare(strict_types=1); + namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; -class ManyToManyRelModelMapper extends DataMapperAbstract +class ManyToManyRelModelMapper extends DataMapperFactory { /** * Columns. @@ -23,12 +24,12 @@ class ManyToManyRelModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_has_many_rel_id' => ['name' => 'test_has_many_rel_id', 'type' => 'int', 'internal' => 'id'], 'test_has_many_rel_string' => ['name' => 'test_has_many_rel_string', 'type' => 'string', 'internal' => 'string'], ]; - protected static string $table = 'test_has_many_rel'; + public const TABLE = 'test_has_many_rel'; - protected static string $primaryField = 'test_has_many_rel_id'; + 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 57e769087..0a181fc34 100644 --- a/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php +++ b/tests/DataStorage/Database/TestModel/OwnsOneModelMapper.php @@ -11,11 +11,12 @@ * @link https://orange-management.org */ declare(strict_types=1); + namespace phpOMS\tests\DataStorage\Database\TestModel; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; -class OwnsOneModelMapper extends DataMapperAbstract +class OwnsOneModelMapper extends DataMapperFactory { /** * Columns. @@ -23,12 +24,12 @@ class OwnsOneModelMapper extends DataMapperAbstract * @var array * @since 1.0.0 */ - protected static array $columns = [ + public const COLUMNS = [ 'test_owns_one_id' => ['name' => 'test_owns_one_id', 'type' => 'int', 'internal' => 'id'], 'test_owns_one_string' => ['name' => 'test_owns_one_string', 'type' => 'string', 'internal' => 'string'], ]; - protected static string $table = 'test_owns_one'; + public const TABLE = 'test_owns_one'; - protected static string $primaryField = 'test_owns_one_id'; + public const PRIMARYFIELD ='test_owns_one_id'; } diff --git a/tests/Localization/Defaults/CityMapperTest.php b/tests/Localization/Defaults/CityMapperTest.php index df51a8dfa..03c3f5b35 100644 --- a/tests/Localization/Defaults/CityMapperTest.php +++ b/tests/Localization/Defaults/CityMapperTest.php @@ -17,7 +17,8 @@ namespace phpOMS\tests\Localization\Defaults; require_once __DIR__ . '/../../Autoloader.php'; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; +use phpOMS\Localization\Defaults\City; use phpOMS\Localization\Defaults\CityMapper; /** @@ -35,7 +36,7 @@ final class CityMapperTest extends \PHPUnit\Framework\TestCase 'database' => \realpath(__DIR__ . '/../../../Localization/Defaults/localization.sqlite'), ]); - DataMapperAbstract::setConnection($con); + DataMapperFactory::db($con); } /** @@ -45,7 +46,8 @@ final class CityMapperTest extends \PHPUnit\Framework\TestCase */ public function testR() : void { - $obj = CityMapper::get(101079); + /** @var City $obj */ + $obj = CityMapper::get()->where('id', 101079)->execute(); self::assertEquals('DE', $obj->getCountryCode()); self::assertEquals('Frankfurt', $obj->getName()); self::assertEquals(60322, $obj->getPostal()); @@ -57,6 +59,6 @@ final class CityMapperTest extends \PHPUnit\Framework\TestCase public static function tearDownAfterClass() : void { - DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); + DataMapperFactory::db($GLOBALS['dbpool']->get()); } } diff --git a/tests/Localization/Defaults/CountryMapperTest.php b/tests/Localization/Defaults/CountryMapperTest.php index c5c83d755..f0b9869ff 100644 --- a/tests/Localization/Defaults/CountryMapperTest.php +++ b/tests/Localization/Defaults/CountryMapperTest.php @@ -17,7 +17,8 @@ namespace phpOMS\tests\Localization\Defaults; require_once __DIR__ . '/../../Autoloader.php'; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; +use phpOMS\Localization\Defaults\Country; use phpOMS\Localization\Defaults\CountryMapper; /** @@ -35,7 +36,7 @@ final class CountryMapperTest extends \PHPUnit\Framework\TestCase 'database' => \realpath(__DIR__ . '/../../../Localization/Defaults/localization.sqlite'), ]); - DataMapperAbstract::setConnection($con); + DataMapperFactory::db($con); } /** @@ -45,7 +46,8 @@ final class CountryMapperTest extends \PHPUnit\Framework\TestCase */ public function testR() : void { - $obj = CountryMapper::get(83); + /** @var Country $obj */ + $obj = CountryMapper::get()->where('id', 83)->execute(); self::assertEquals('Germany', $obj->getName()); self::assertEquals('DE', $obj->getCode2()); self::assertEquals('DEU', $obj->getCode3()); @@ -54,6 +56,6 @@ final class CountryMapperTest extends \PHPUnit\Framework\TestCase public static function tearDownAfterClass() : void { - DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); + DataMapperFactory::db($GLOBALS['dbpool']->get()); } } diff --git a/tests/Localization/Defaults/CurrencyMapperTest.php b/tests/Localization/Defaults/CurrencyMapperTest.php index fa409d193..a9542c733 100644 --- a/tests/Localization/Defaults/CurrencyMapperTest.php +++ b/tests/Localization/Defaults/CurrencyMapperTest.php @@ -17,7 +17,8 @@ namespace phpOMS\tests\Localization\Defaults; require_once __DIR__ . '/../../Autoloader.php'; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; +use phpOMS\Localization\Defaults\Currency; use phpOMS\Localization\Defaults\CurrencyMapper; /** @@ -35,7 +36,7 @@ final class CurrencyMapperTest extends \PHPUnit\Framework\TestCase 'database' => \realpath(__DIR__ . '/../../../Localization/Defaults/localization.sqlite'), ]); - DataMapperAbstract::setConnection($con); + DataMapperFactory::db($con); } /** @@ -45,7 +46,8 @@ final class CurrencyMapperTest extends \PHPUnit\Framework\TestCase */ public function testR() : void { - $obj = CurrencyMapper::get(50); + /** @var Currency $obj */ + $obj = CurrencyMapper::get()->where('id', 50)->execute(); self::assertEquals('Euro', $obj->getName()); self::assertEquals('EUR', $obj->getCode()); self::assertEquals('978', $obj->getNumber()); @@ -57,6 +59,6 @@ final class CurrencyMapperTest extends \PHPUnit\Framework\TestCase public static function tearDownAfterClass() : void { - DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); + DataMapperFactory::db($GLOBALS['dbpool']->get()); } } diff --git a/tests/Localization/Defaults/IbanMapperTest.php b/tests/Localization/Defaults/IbanMapperTest.php index 1a7b58b4e..752ecb8fd 100644 --- a/tests/Localization/Defaults/IbanMapperTest.php +++ b/tests/Localization/Defaults/IbanMapperTest.php @@ -17,7 +17,8 @@ namespace phpOMS\tests\Localization\Defaults; require_once __DIR__ . '/../../Autoloader.php'; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; +use phpOMS\Localization\Defaults\Iban; use phpOMS\Localization\Defaults\IbanMapper; /** @@ -35,7 +36,7 @@ final class IbanMapperTest extends \PHPUnit\Framework\TestCase 'database' => \realpath(__DIR__ . '/../../../Localization/Defaults/localization.sqlite'), ]); - DataMapperAbstract::setConnection($con); + DataMapperFactory::db($con); } /** @@ -45,7 +46,8 @@ final class IbanMapperTest extends \PHPUnit\Framework\TestCase */ public function testR() : void { - $obj = IbanMapper::get(22); + /** @var Iban $obj */ + $obj = IbanMapper::get()->where('id', 22)->execute(); self::assertEquals('DE', $obj->getCountry()); self::assertEquals(22, $obj->getChars()); self::assertEquals('18n', $obj->getBban()); @@ -54,6 +56,6 @@ final class IbanMapperTest extends \PHPUnit\Framework\TestCase public static function tearDownAfterClass() : void { - DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); + DataMapperFactory::db($GLOBALS['dbpool']->get()); } } diff --git a/tests/Localization/Defaults/LanguageMapperTest.php b/tests/Localization/Defaults/LanguageMapperTest.php index 2191a0f88..a54cdd545 100644 --- a/tests/Localization/Defaults/LanguageMapperTest.php +++ b/tests/Localization/Defaults/LanguageMapperTest.php @@ -17,7 +17,8 @@ namespace phpOMS\tests\Localization\Defaults; require_once __DIR__ . '/../../Autoloader.php'; use phpOMS\DataStorage\Database\Connection\SQLiteConnection; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; +use phpOMS\Localization\Defaults\Language; use phpOMS\Localization\Defaults\LanguageMapper; /** @@ -35,7 +36,7 @@ final class LanguageMapperTest extends \PHPUnit\Framework\TestCase 'database' => \realpath(__DIR__ . '/../../../Localization/Defaults/localization.sqlite'), ]); - DataMapperAbstract::setConnection($con); + DataMapperFactory::db($con); } /** @@ -45,7 +46,8 @@ final class LanguageMapperTest extends \PHPUnit\Framework\TestCase */ public function testR() : void { - $obj = LanguageMapper::get(53); + /** @var Language $obj */ + $obj = LanguageMapper::get()->where('id', 53)->execute(); self::assertEquals('German', $obj->getName()); self::assertEquals('Deutsch', $obj->getNative()); self::assertEquals('de', $obj->getCode2()); @@ -55,6 +57,6 @@ final class LanguageMapperTest extends \PHPUnit\Framework\TestCase public static function tearDownAfterClass() : void { - DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); + DataMapperFactory::db($GLOBALS['dbpool']->get()); } } diff --git a/tests/Module/ModuleAbstractTest.php b/tests/Module/ModuleAbstractTest.php index bf45291f9..1c9128edd 100644 --- a/tests/Module/ModuleAbstractTest.php +++ b/tests/Module/ModuleAbstractTest.php @@ -92,21 +92,21 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase public function createRelationModel() : void { $model = new ManyToManyRelModel(); - ManyToManyRelModelMapper::create($model); + ManyToManyRelModelMapper::create()->execute($model); } public function createRelationDB() : void { - $model1 = BaseModelMapper::get(1); - $model2 = ManyToManyRelModelMapper::get(1); + $model1 = BaseModelMapper::get()->where('id', 1)->execute(); + $model2 = ManyToManyRelModelMapper::get()->where('id', 1)->execute(); $this->createModelRelation(1, $model1->getId(), $model2->id, BaseModelMapper::class, 'hasManyRelations', '', '127.0.0.1'); } public function deleteRelationDB() : void { - $model1 = BaseModelMapper::get(1); - $model2 = ManyToManyRelModelMapper::get(1); + $model1 = BaseModelMapper::get()->where('id', 1)->execute(); + $model2 = ManyToManyRelModelMapper::get()->where('id', 1)->execute(); $this->deleteModelRelation(1, $model1->getId(), $model2->id, BaseModelMapper::class, 'hasManyRelations', '', '127.0.0.1'); } @@ -121,7 +121,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase public function update() : void { $old = new BaseModel(); - BaseModelMapper::create($old); + BaseModelMapper::create()->execute($old); $new = clone $old; $new->string = 'Updated'; @@ -131,7 +131,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase public function delete() : void { - $model = BaseModelMapper::get(1); + $model = BaseModelMapper::get()->where('id', 1)->execute(); $this->deleteModel(1, $model, BaseModelMapper::class, '', '127.0.0.1'); } @@ -283,8 +283,6 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase */ private function dbSetup() : void { - BaseModelMapper::clearCache(); - $GLOBALS['dbpool']->get()->con->prepare( 'CREATE TABLE `test_base` ( `test_base_id` int(11) NOT NULL AUTO_INCREMENT, @@ -368,7 +366,6 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_has_many_direct')->execute(); $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_has_many_rel')->execute(); $GLOBALS['dbpool']->get()->con->prepare('DROP TABLE test_has_many_rel_relations')->execute(); - BaseModelMapper::clearCache(); } /** @@ -381,7 +378,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $this->dbSetup(); $this->module->create(); - self::assertCount(1, BaseModelMapper::getAll()); + self::assertCount(1, BaseModelMapper::getAll()->execute()); $this->dbTeardown(); } @@ -396,7 +393,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $this->dbSetup(); $this->module->createMultiple(); - self::assertCount(2, BaseModelMapper::getAll()); + self::assertCount(2, BaseModelMapper::getAll()->execute()); $this->dbTeardown(); } @@ -411,7 +408,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $this->dbSetup(); $this->module->update(); - $updated = BaseModelMapper::get(1); + $updated = BaseModelMapper::get()->where('id', 1)->execute(); self::assertEquals('Updated', $updated->string); @@ -428,9 +425,9 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $this->dbSetup(); $this->module->create(); - self::assertCount(1, BaseModelMapper::getAll()); + self::assertCount(1, BaseModelMapper::getAll()->execute()); $this->module->delete(); - self::assertCount(0, BaseModelMapper::getAll()); + self::assertCount(0, BaseModelMapper::getAll()->execute()); $this->dbTeardown(); } @@ -448,14 +445,12 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase $this->module->createRelationModel(); $this->module->createRelationDB(); - $model = BaseModelMapper::get(1); + $model = BaseModelMapper::get()->with('hasManyRelations')->where('id', 1)->execute(); self::assertCount(1, $model->hasManyRelations); - BaseModelMapper::clearCache(); $this->module->deleteRelationDB(); - BaseModelMapper::clearCache(); - $model = BaseModelMapper::get(1); + $model = BaseModelMapper::get()->with('hasManyRelations')->where('id', 1)->execute(); // count = 2 because the moduel automatically initializes 2 hasMany relationships in the __construct() // This actually means that the delete was successful, otherwise the hasManyRelations would have been overwritten with 1 relation (see above before the delete) diff --git a/tests/Module/ModuleManagerTest.php b/tests/Module/ModuleManagerTest.php index 8fbd0ae73..af8794910 100644 --- a/tests/Module/ModuleManagerTest.php +++ b/tests/Module/ModuleManagerTest.php @@ -172,7 +172,7 @@ final class ModuleManagerTest extends \PHPUnit\Framework\TestCase $module->id = 'TestModule'; $module->name = 'TestModule'; $module->path = 'TestModule'; - ModuleMapper::create($module); + ModuleMapper::create()->execute($module); self::assertTrue($this->moduleManager->deactivate('TestModule')); self::assertFalse($this->moduleManager->isActive('TestModule')); @@ -181,7 +181,7 @@ final class ModuleManagerTest extends \PHPUnit\Framework\TestCase // this is normally done in the ApiController $module->setStatus(ModuleStatus::ACTIVE); - ModuleMapper::update($module); + ModuleMapper::update()->execute($module); $queryLoad = new Builder($this->app->dbPool->get('insert')); $queryLoad->insert('module_load_pid', 'module_load_type', 'module_load_from', 'module_load_for', 'module_load_file') @@ -328,9 +328,8 @@ final class ModuleManagerTest extends \PHPUnit\Framework\TestCase self::assertFalse($this->moduleManager->uninstall('TestModule')); - $module = ModuleMapper::get('TestModule'); - ModuleMapper::delete($module); - ModuleMapper::clearCache(); + $module = ModuleMapper::get()->where('id', 'TestModule')->execute(); + ModuleMapper::delete()->execute($module); self::assertFalse($this->moduleManager->isActive('TestModule')); self::assertFalse($this->moduleManager->isRunning('TestModule')); diff --git a/tests/Socket/Client/ClientTestHelper.php b/tests/Socket/Client/ClientTestHelper.php index 251214c36..3dd59c1f3 100644 --- a/tests/Socket/Client/ClientTestHelper.php +++ b/tests/Socket/Client/ClientTestHelper.php @@ -19,7 +19,7 @@ use phpOMS\Account\AccountManager; use phpOMS\Application\ApplicationAbstract; use phpOMS\DataStorage\Cache\CachePool; use phpOMS\DataStorage\Database\DatabasePool; -use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\Mapper\DataMapperFactory; use phpOMS\DataStorage\Session\HttpSession; use phpOMS\Dispatcher\Dispatcher; use phpOMS\Event\EventManager; @@ -42,7 +42,7 @@ $GLOBALS['dbpool']->create('schema', $config['db']['core']['masters']['schema']) $httpSession = new HttpSession(); $GLOBALS['session'] = $httpSession; -DataMapperAbstract::setConnection($GLOBALS['dbpool']->get()); +DataMapperFactory::db($GLOBALS['dbpool']->get()); $app = new class() extends ApplicationAbstract {