Implement more array functionality

This commit is contained in:
Dennis Eichhorn 2018-09-01 17:23:34 +02:00
parent f1326f4a75
commit acb32ba190
2 changed files with 233 additions and 121 deletions

View File

@ -92,16 +92,6 @@ class DataMapperAbstract implements DataMapperInterface
*/
protected static $hasMany = [];
/**
* Relations.
*
* Relation is defined in the model
*
* @var string[]
* @since 1.0.0
*/
protected static $hasOne = [];
/**
* Relations.
*
@ -173,7 +163,6 @@ class DataMapperAbstract implements DataMapperInterface
'createdAt' => [],
'columns' => [],
'hasMany' => [],
'hasOne' => [],
'ownsOne' => [],
'table' => [],
];
@ -254,7 +243,6 @@ class DataMapperAbstract implements DataMapperInterface
self::$collection['createdAt'][] = $class::$createdAt;
self::$collection['columns'][] = $class::$columns;
self::$collection['hasMany'][] = $class::$hasMany;
self::$collection['hasOne'][] = $class::$hasOne;
self::$collection['ownsOne'][] = $class::$ownsOne;
self::$collection['table'][] = $class::$table;
@ -295,7 +283,6 @@ class DataMapperAbstract implements DataMapperInterface
self::$createdAt = '';
self::$columns = [];
self::$hasMany = [];
self::$hasOne = [];
self::$ownsOne = [];
self::$table = '';
self::$fields = [];
@ -303,7 +290,6 @@ class DataMapperAbstract implements DataMapperInterface
'primaryField' => [],
'createdAt' => [],
'columns' => [],
'hasOne' => [],
'ownsMany' => [],
'ownsOne' => [],
'table' => [],
@ -412,7 +398,7 @@ class DataMapperAbstract implements DataMapperInterface
foreach (static::$columns as $key => $column) {
$propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal'];
if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) {
if (isset(static::$hasMany[$propertyName])) {
continue;
}
@ -477,7 +463,7 @@ class DataMapperAbstract implements DataMapperInterface
$query->prefix(self::$db->getPrefix())->into(static::$table);
foreach (static::$columns as $key => $column) {
if (isset(static::$hasMany[$key]) || isset(static::$hasOne[$key])) {
if (isset(static::$hasMany[$key])) {
continue;
}
@ -506,7 +492,7 @@ class DataMapperAbstract implements DataMapperInterface
$query->insert($column['name'])->value($value, $column['type']);
}
}
}
// 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) {
@ -711,22 +697,6 @@ class DataMapperAbstract implements DataMapperInterface
}
}
/**
* Create has one
*
* @param \ReflectionClass $refClass Property name to initialize
* @param object $obj Object to create
*
* @return mixed
* @todo implement???
*
* @since 1.0.0
*/
private static function createHasOne(\ReflectionClass $refClass, object $obj)
{
throw new \Exception();
}
/**
* Create owns one
*
@ -999,6 +969,69 @@ class DataMapperAbstract implements DataMapperInterface
self::updateRelationTable($objsIds, $objId);
}
/**
* Update has many
*
* @param array $obj Object to create
* @param mixed $objId Id to set
* @param int $relations Create all relations as well
* @param int $depth Depth of relations to update (default = 1 = none)
*
* @return void
*
* @throws InvalidMapperException
*
* @since 1.0.0
*/
private static function updateHasManyArray(array &$obj, $objId, int $relations = RelationType::ALL, $depth = 1) : void
{
$objsIds = [];
foreach (static::$hasMany as $propertyName => $rel) {
$values = $obj[$propertyName];
if (!isset(static::$hasMany[$propertyName]['mapper'])) {
throw new InvalidMapperException();
}
/** @var string $mapper */
$mapper = static::$hasMany[$propertyName]['mapper'];
$objsIds[$propertyName] = [];
foreach ($values as $key => &$value) {
// todo: carefull what if a value is an object or another array?
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, $relations, $depth);
$objsIds[$propertyName][$key] = $value;
continue;
}
// create if not existing
/** @var string $table */
/** @var array $columns */
if (static::$hasMany[$propertyName]['table'] === static::$hasMany[$propertyName]['mapper']::$table) {
$value[$mapper::$columns[static::$hasMany[$propertyName]['dst']]['internal']] = $objId;
}
$objsIds[$propertyName][$key] = $mapper::createArray($value);
}
}
self::updateRelationTable($objsIds, $objId);
}
/**
* Update relation table entry
*
@ -1016,8 +1049,8 @@ class DataMapperAbstract implements DataMapperInterface
$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]);
$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);
@ -1084,6 +1117,30 @@ class DataMapperAbstract implements DataMapperInterface
return $mapper::update($obj, $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 $relations Create all relations as well
* @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 $relations = RelationType::ALL, int $depth = 1)
{
/** @var string $mapper */
$mapper = static::$ownsOne[$propertyName]['mapper'];
// todo: delete owned one object is not recommended since it can be owned by by something else? or does owns one mean that nothing else can have a relation to this one?
return $mapper::updateArray($obj, $relations, $depth);
}
/**
* Update owns one
*
@ -1110,6 +1167,32 @@ class DataMapperAbstract implements DataMapperInterface
return $obj;
}
/**
* 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 $relations Create all relations as well
* @param int $depth Depth of relations to update (default = 1 = none)
*
* @return mixed
*
* @since 1.0.0
*/
private static function updateBelongsToArray(string $propertyName, $obj, int $relations = RelationType::ALL, int $depth = 1)
{
if (\is_array($obj)) {
/** @var string $mapper */
$mapper = static::$belongsTo[$propertyName]['mapper'];
return $mapper::updateArray($obj, $relations, $depth);
}
return $obj;
}
/**
* Update object in db.
*
@ -1133,7 +1216,6 @@ class DataMapperAbstract implements DataMapperInterface
foreach (static::$columns as $key => $column) {
$propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal'];
if (isset(static::$hasMany[$propertyName])
|| isset(static::$hasOne[$propertyName])
|| $column['internal'] === static::$primaryField
) {
continue;
@ -1180,6 +1262,62 @@ class DataMapperAbstract implements DataMapperInterface
self::$db->con->prepare($query->toSql())->execute();
}
/**
* Update object in db.
*
* @param array $obj Model to update
* @param mixed $objId Model id
* @param int $relations Create all relations as well
* @param int $depth Depth of relations to update (default = 1 = none)
*
* @return void
*
* @since 1.0.0
*/
private static function updateModelArray(array $obj, $objId, int $relations = RelationType::ALL, int $depth = 1) : void
{
$query = new Builder(self::$db);
$query->prefix(self::$db->getPrefix())
->update(static::$table)
->where(static::$table . '.' . static::$primaryField, '=', $objId);
foreach (static::$columns as $key => $column) {
if (isset(static::$hasMany[$key])) {
continue;
}
$path = $column['internal'];
if (\stripos($column['internal'], '/') !== false) {
$path = \explode('/', $column['internal']);
\array_shift($path); // todo: why am I doing this?
$path = \implode('/', $path);
}
$property = ArrayUtils::getArray($path, $obj, '/');
if (isset(static::$ownsOne[$path])) {
$id = self::updateOwnsOneArray($column['internal'], $property, $relations, $depth);
$value = self::parseValue($column['type'], $id);
// todo: should not be done if the id didn't change. but for now don't know if id changed
$query->set([static::$table . '.' . $column['name'] => $value], $column['type']);
} elseif (isset(static::$belongsTo[$path])) {
$id = self::updateBelongsToArray($column['internal'], $property, $relations, $depth);
$value = self::parseValue($column['type'], $id);
// todo: should not be done if the id didn't change. but for now don't know if id changed
$query->set([static::$table . '.' . $column['name'] => $value], $column['type']);
} elseif ($column['name'] !== static::$primaryField) {
$value = self::parseValue($column['type'], $property);
$query->set([static::$table . '.' . $column['name'] => $value], $column['type']);
}
}
self::$db->con->prepare($query->toSql())->execute();
}
/**
* Update object in db.
*
@ -1225,6 +1363,50 @@ class DataMapperAbstract implements DataMapperInterface
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)
{
self::extend(__CLASS__);
if (!isset($obj)) {
return null;
}
$objId = $obj[static::$columns[static::$primaryField]['internal']];
$update = true;
if ($depth < 1) {
return $objId;
}
self::addInitializedArray(static::class, $objId, $obj);
if (empty($objId)) {
$update = false;
self::createArray($obj, $relations);
}
if ($relations === RelationType::ALL) {
self::updateHasManyArray($obj, $objId, --$depth);
}
if ($update) {
self::updateModelArray($obj, $objId, --$depth);
}
return $objId;
}
/**
* Delete has many
*
@ -1372,7 +1554,7 @@ class DataMapperAbstract implements DataMapperInterface
foreach ($properties as $property) {
$propertyName = $property->getName();
if (isset(static::$hasMany[$propertyName]) || isset(static::$hasOne[$propertyName])) {
if (isset(static::$hasMany[$propertyName])) {
continue;
}
@ -1575,72 +1757,6 @@ class DataMapperAbstract implements DataMapperInterface
}
}
/**
* Populate data.
*
* @param mixed $obj Object to add the relations to
* @param int $depth Relation depth
*
* @return void
*
* @todo accept reflection class as parameter
*
* @since 1.0.0
*/
public static function populateHasOne(&$obj, int $depth = 3) : void
{
$refClass = new \ReflectionClass($obj);
foreach (static::$hasOne as $member => $one) {
// todo: is that if necessary? performance is suffering for sure!
if ($refClass->hasProperty($member)) {
$refProp = $refClass->getProperty($member);
if (!($accessible = $refProp->isPublic())) {
$refProp->setAccessible(true);
}
/** @var string $mapper */
$mapper = static::$hasOne[$member]['mapper'];
$id = $refProp->getValue($obj);
if (self::isNullObject($id)) {
continue;
}
$id = \is_object($id) ? self::getObjectId($id) : $id;
$value = self::getInitialized($mapper, $id) ?? $mapper::get($id, RelationType::ALL, null, $depth);
$refProp->setValue($obj, $value);
if (!$accessible) {
$refProp->setAccessible(false);
}
}
}
}
/**
* Populate data.
*
* @param array $obj Object to add the relations to
* @param int $depth Relation depth
*
* @return void
*
* @todo accept reflection class as parameter
*
* @since 1.0.0
*/
public static function populateHasOneArray(array &$obj, int $depth = 3) : void
{
foreach (static::$hasOne as $member => $one) {
/** @var string $mapper */
$mapper = static::$hasOne[$member]['mapper'];
$obj[$member] = self::getInitializedArray($mapper, $obj['member']) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth);
}
}
/**
* Populate data.
*
@ -1702,8 +1818,11 @@ class DataMapperAbstract implements DataMapperInterface
{
foreach (static::$ownsOne as $member => $one) {
/** @var string $mapper */
$mapper = static::$ownsOne[$member]['mapper'];
$obj[$member] = self::getInitializedArray($mapper, $obj[$member]) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth);
$mapper = static::$ownsOne[$member]['mapper'];
$id = $obj[$member];
$id = \is_array($id) ? $id[$mapper::$columns[$mapper::$primaryField]['internal']] : $id;
$obj[$member] = self::getInitializedArray($mapper, $id) ?? $mapper::getArray($id, RelationType::ALL, $depth);
}
}
@ -1768,8 +1887,11 @@ class DataMapperAbstract implements DataMapperInterface
{
foreach (static::$belongsTo as $member => $one) {
/** @var string $mapper */
$mapper = static::$belongsTo[$member]['mapper'];
$obj[$member] = self::getInitializedArray($mapper, $obj[$member]) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth);
$mapper = static::$belongsTo[$member]['mapper'];
$id = $obj[$member];
$id = \is_array($id) ? $id[$mapper::$columns[$mapper::$primaryField]['internal']] : $id;
$obj[$member] = self::getInitializedArray($mapper, $id) ?? $mapper::getArray($id, RelationType::ALL, $depth);
}
}
@ -2320,11 +2442,10 @@ class DataMapperAbstract implements DataMapperInterface
}
$hasMany = !empty(static::$hasMany);
$hasOne = !empty(static::$hasOne);
$ownsOne = !empty(static::$ownsOne);
$belongsTo = !empty(static::$belongsTo);
if (!($hasMany || $hasOne || $ownsOne || $belongsTo)) {
if (!($hasMany || $ownsOne || $belongsTo)) {
return;
}
@ -2334,10 +2455,6 @@ class DataMapperAbstract implements DataMapperInterface
self::populateManyToMany(self::getHasManyRaw($key, $relations), $obj[$key], $depth);
}
if ($hasOne) {
self::populateHasOne($obj[$key], $depth);
}
if ($ownsOne) {
self::populateOwnsOne($obj[$key], $depth);
}
@ -2370,11 +2487,10 @@ class DataMapperAbstract implements DataMapperInterface
}
$hasMany = !empty(static::$hasMany);
$hasOne = !empty(static::$hasOne);
$ownsOne = !empty(static::$ownsOne);
$belongsTo = !empty(static::$belongsTo);
if (!($hasMany || $hasOne || $ownsOne || $belongsTo)) {
if (!($hasMany || $ownsOne || $belongsTo)) {
return;
}
@ -2384,10 +2500,6 @@ class DataMapperAbstract implements DataMapperInterface
self::populateManyToManyArray(self::getHasManyRaw($key, $relations), $obj[$key], $depth);
}
if ($hasOne) {
self::populateHasOneArray($obj[$key], $depth);
}
if ($ownsOne) {
self::populateOwnsOneArray($obj[$key], $depth);
}

View File

@ -240,7 +240,7 @@ class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase
// todo test update relations
}
/*public function testUpdateArray()
public function testUpdateArray()
{
$id = BaseModelMapper::createArray($this->modelArray);
$modelR = BaseModelMapper::getArray($id);
@ -263,7 +263,7 @@ class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase
self::assertEquals($modelR['datetime']->format('Y-m-d'), $modelR2['datetime']->format('Y-m-d'));
// todo test update relations
}*/
}
public function testDelete()
{