This commit is contained in:
Dennis Eichhorn 2022-01-22 21:53:38 +01:00
parent d20da74b77
commit 169b5535d3
24 changed files with 149 additions and 100 deletions

View File

@ -192,6 +192,26 @@ class Account implements \JsonSerializable, ArrayableInterface
return $this->groups;
}
/**
* Get ids of groups
*
* @return int[]
*
* @since 1.0.0
*/
public function getGroupIds() : array
{
/*
$ids = [];
foreach ($this->groups as $group) {
$ids[] = $group->getId();
}
return $ids;
*/
return \array_keys($this->groups);
}
/**
* Add group.
*

View File

@ -123,16 +123,16 @@ abstract class GrammarAbstract
*/
public function compileQuery(BuilderAbstract $query) : string
{
return \trim(
\implode(' ',
\array_filter(
$this->compileComponents($query),
function ($value) {
return (string) $value !== '';
$components = $this->compileComponents($query);
$queryString = '';
foreach ($components as $component) {
if ($component !== '') {
$queryString .= $component . ' ';
}
)
)
) . ';';
}
return \substr($queryString, 0, -1) . ';';
}
/**
@ -148,12 +148,11 @@ abstract class GrammarAbstract
*/
protected function compileComponents(BuilderAbstract $query) : array
{
$sql = [];
if ($query->getType() === QueryType::RAW) {
return [$query->raw];
}
$sql = [];
$components = $this->getComponents($query->getType());
/* Loop all possible query components and if they exist compile them. */
@ -247,16 +246,24 @@ abstract class GrammarAbstract
}
}
$split = \explode('.', $system);
$fullSystem = '';
// The following code could have been handled with \explode more elegantly but \explode needs more memory and more time
// Normally this wouldn't be a problem but in this case there are so many function calls to this routine,
// that it makes sense to make this "minor" improvement.
if (($pos = \stripos($system, '.')) !== false) {
$split = [\substr($system, 0, $pos), \substr($system, $pos + 1)];
foreach ($split as $key => $system) {
$fullSystem .= '.'
. ($system !== '*' ? $identifierStart : '')
return ($split[0] !== '*' ? $identifierStart : '')
. $split[0]
. ($split[0] !== '*' ? $identifierEnd : '')
. '.'
. ($split[1] !== '*' ? $identifierStart : '')
. $split[1]
. ($split[1] !== '*' ? $identifierEnd : '');
}
return ($system !== '*' ? $identifierStart : '')
. $system
. ($system !== '*' ? $identifierEnd : '');
}
return \ltrim($fullSystem, '.');
}
}

View File

@ -350,7 +350,7 @@ abstract class DataMapperAbstract
return (bool) $value;
} elseif ($type === 'DateTime' || $type === 'DateTimeImmutable') {
return $value === null ? null : $value->format($this->mapper::$datetimeFormat);
} elseif ($type === 'Json' || $value instanceof \JsonSerializable) {
} elseif ($type === 'Json') {
return (string) \json_encode($value);
} elseif ($type === 'Serializable') {
return $value->serialize();

View File

@ -401,11 +401,6 @@ class DataMapperFactory
{
$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();
}

View File

@ -19,8 +19,6 @@ use phpOMS\DataStorage\Database\Query\Builder;
/**
* Delete mapper (DELETE).
*
* @todo: allow to define where clause if no object is loaded yet
*
* @package phpOMS\DataStorage\Database\Mapper
* @license OMS License 1.0
* @link https://orange-management.org

View File

@ -15,6 +15,7 @@ declare(strict_types=1);
namespace phpOMS\DataStorage\Database\Mapper;
use phpOMS\DataStorage\Database\Query\Builder;
use phpOMS\DataStorage\Database\Query\Where;
use phpOMS\Utils\ArrayUtils;
/**
@ -336,6 +337,13 @@ final class ReadMapper extends DataMapperAbstract
// where
foreach ($this->where as $member => $values) {
// handle where query
if ($member === '' && $values[0]['value'] instanceof Where) {
$query->where($values[0]['value'], boolean: $values[0]['comparison']);
continue;
}
if (($col = $this->mapper::getColumnByMember($member)) !== null) {
/* variable in model */
foreach ($values as $where) {
@ -445,7 +453,7 @@ final class ReadMapper extends DataMapperAbstract
} elseif (!isset($this->mapper::HAS_MANY[$member]['external']) && isset($this->mapper::HAS_MANY[$member]['column'])) {
// get HasManyQuery (but only for elements which have a 'column' defined)
// todo: handle self and self === null
// @todo: handle self and self === null
$query->leftJoin($rel['mapper']::TABLE, $rel['mapper']::TABLE . '_d' . ($this->depth + 1))
->on(
$this->mapper::TABLE . '_d' . $this->depth . '.' . ($rel['external'] ?? $this->mapper::PRIMARYFIELD), '=',
@ -531,7 +539,7 @@ final class ReadMapper extends DataMapperAbstract
if (\stripos($def['internal'], '/') !== false) {
$hasPath = true;
$path = \explode('/', $def['internal']);
$path = \explode('/', \ltrim($def['internal'], '/'));
$member = $path[0];
$refProp = $refClass->getProperty($path[0]);
@ -565,7 +573,7 @@ final class ReadMapper extends DataMapperAbstract
}
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.
// @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($this->mapper::BELONGS_TO[$def['internal']])) {

View File

@ -343,6 +343,10 @@ final class UpdateMapper extends DataMapperAbstract
$sth->execute();
$result = $sth->fetchAll(\PDO::FETCH_COLUMN);
if ($result === false) {
return; // @codeCoverageIgnore
}
$removes = \array_diff($result, \array_values($objsIds[$member] ?? []));
$adds = \array_diff(\array_values($objsIds[$member] ?? []), $result);

View File

@ -109,19 +109,26 @@ final class WriteMapper extends DataMapperAbstract
$query = new Builder($this->db);
$query->into($this->mapper::TABLE);
$publicProperties = \get_object_vars($obj);
foreach ($this->mapper::COLUMNS as $column) {
$propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal'];
if (isset($this->mapper::HAS_MANY[$propertyName])) {
$propertyName = \stripos($column['internal'], '/') !== false
? \explode('/', $column['internal'])[0]
: $column['internal'];
if (isset($this->mapper::HAS_MANY[$propertyName])
|| ($column['name'] === $this->mapper::PRIMARYFIELD && $this->mapper::AUTOINCREMENT)
) {
continue;
}
if (!isset($publicProperties[$propertyName])) {
$property = $refClass->getProperty($propertyName);
if (!$property->isPublic()) {
$property->setAccessible(true);
$tValue = $property->getValue($obj);
$property->setAccessible(false);
} else {
$tValue = $obj->{$propertyName};
$tValue = $publicProperties[$propertyName];
}
if (isset($this->mapper::OWNS_ONE[$propertyName])) {
@ -134,20 +141,12 @@ final class WriteMapper extends DataMapperAbstract
$value = $this->parseValue($column['type'], $id);
$query->insert($column['name'])->value($value);
} elseif ($column['name'] !== $this->mapper::PRIMARYFIELD || !empty($tValue)) {
} else {
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 = $this->parseValue($column['type'], $tValue);
$query->insert($column['name'])->value($value);
@ -265,7 +264,6 @@ final class WriteMapper extends DataMapperAbstract
}
$property = $refClass->getProperty($propertyName);
if (!($isPublic = $property->isPublic())) {
$property->setAccessible(true);
$values = $property->getValue($obj);
@ -281,10 +279,12 @@ final class WriteMapper extends DataMapperAbstract
if (\is_object($values)) {
// conditionals
$publicProperties = \get_object_vars($values);
if (!isset($publicProperties[$internalName])) {
$relReflectionClass = new \ReflectionClass($values);
$relProperty = $relReflectionClass->getProperty($internalName);
if (!$relProperty->isPublic()) {
$relProperty->setAccessible(true);
$relProperty->setValue($values, $objId);
$relProperty->setAccessible(false);

View File

@ -543,7 +543,7 @@ class Builder extends BuilderAbstract
*
* @since 1.0.0
*/
public function where(string | array | Where $columns, string | array $operator = null, mixed $values = null, string | array $boolean = 'and') : self
public function where(string | array | Builder $columns, string | array $operator = null, mixed $values = null, string | array $boolean = 'and') : self
{
if (!\is_array($columns)) {
$columns = [$columns];
@ -598,7 +598,7 @@ class Builder extends BuilderAbstract
*
* @since 1.0.0
*/
public function orWhere(string | array | Where $where, string | array $operator = null, mixed $values = null) : self
public function orWhere(string | array | Builder $where, string | array $operator = null, mixed $values = null) : self
{
return $this->where($where, $operator, $values, 'or');
}

View File

@ -334,13 +334,15 @@ class Grammar extends GrammarAbstract
} elseif (\is_int($value)) {
return (string) $value;
} elseif (\is_array($value)) {
$values = '';
$value = \array_values($value);
$count = \count($value) - 1;
$values = '(';
foreach ($value as $val) {
$values .= $this->compileValue($query, $val) . ', ';
for ($i = 0; $i < $count; ++$i) {
$values .= $this->compileValue($query, $value[$i]) . ', ';
}
return '(' . \rtrim($values, ', ') . ')';
return $values . $this->compileValue($query, $value[$count]) . ')';
} elseif ($value instanceof \DateTime) {
return $query->quote($value->format($this->datetimeFormat));
} elseif ($value === null) {
@ -602,17 +604,18 @@ class Grammar extends GrammarAbstract
*/
protected function compileInserts(Builder $query, array $columns) : string
{
$cols = '';
$count = \count($columns) - 1;
foreach ($columns as $column) {
$cols .= $this->compileSystem($column) . ', ';
}
if ($cols === '') {
if ($count === -1) {
return '';
}
return '(' . \rtrim($cols, ', ') . ')';
$cols = '(';
for ($i = 0; $i < $count; ++$i) {
$cols .= $this->compileSystem($columns[$i]) . ', ';
}
return $cols .= $this->compileSystem($columns[$count]) . ')';
}
/**
@ -627,17 +630,18 @@ class Grammar extends GrammarAbstract
*/
protected function compileValues(Builder $query, array $values) : string
{
$vals = '';
foreach ($values as $value) {
$vals .= $this->compileValue($query, $value) . ', ';
}
if ($vals === '') {
$values = \array_values($values);
$count = \count($values) - 1;
if ($count === -1) {
return '';
}
return 'VALUES ' . \rtrim($vals, ', ');
$vals = 'VALUES ';
for ($i = 0; $i < $count; ++$i) {
$vals .= $this->compileValue($query, $values[$i]) . ', ';
}
return $vals . $this->compileValue($query, $values[$count]);
}
/**

View File

@ -728,7 +728,7 @@ class Matrix implements \ArrayAccess, \Iterator
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
public function offsetExists($offset) : bool
{
$row = (int) ($offset / $this->m);

View File

@ -14,7 +14,9 @@ declare(strict_types=1);
namespace phpOMS\Module;
use Modules\Admin\Models\PermissionAbstractMapper;
use phpOMS\Application\ApplicationAbstract;
use phpOMS\DataStorage\Database\Query\Builder;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\System\MimeType;

View File

@ -141,6 +141,10 @@ class File extends FileAbstract implements FileInterface
}
$fp = \fopen('php://memory', 'r+');
if ($fp === false) {
return false; // @codeCoverageIgnore
}
\fwrite($fp, $content);
\rewind($fp);

View File

@ -31,6 +31,9 @@ final class KmeansTest extends \PHPUnit\Framework\TestCase
*/
public function testKmeans() : void
{
$seed = \mt_rand(\PHP_INT_MIN, \PHP_INT_MAX);
\mt_srand($seed);
$result = false;
// due to the random nature this can be false sometimes?!

View File

@ -53,6 +53,7 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase
`test_base_owns_one_self` int(11) DEFAULT NULL,
`test_base_json` varchar(254) DEFAULT NULL,
`test_base_json_serializable` varchar(254) DEFAULT NULL,
`test_base_serializable` varchar(254) DEFAULT NULL,
`test_base_datetime` datetime DEFAULT NULL,
`test_base_datetime_null` datetime DEFAULT NULL, /* There was a bug where it returned the current date because new \DateTime(null) === current date which is wrong, we want null as value! */
PRIMARY KEY (`test_base_id`)
@ -204,12 +205,9 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase
self::assertEquals($this->model->datetime->format('Y-m-d'), $modelR->datetime->format('Y-m-d'));
self::assertNull($modelR->datetime_null);
/**
* @todo Serializable and JsonSerializable data can be inserted and updated in the database but it's not possible to correctly populate a model with the data in its original format.
*/
//self::assertEquals('123', $modelR->serializable);
//self::assertEquals($this->model->json, $modelR->json);
//self::assertEquals([1, 2, 3], $modelR->jsonSerializable);
self::assertEquals($this->model->json, $modelR->json);
self::assertEquals([1, 2, 3], $modelR->jsonSerializable);
self::assertEquals('123', $modelR->serializable->value);
self::assertCount(2, $modelR->hasManyDirect);
self::assertCount(2, $modelR->hasManyRelations);
@ -364,7 +362,7 @@ final class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase
$modelR = BaseModelMapper::get()->with('hasManyDirect')->with('hasManyRelations')->where('id', $id)->execute();
$modelR->string = 'Update';
$modelR->int = '321';
$modelR->int = 321;
$modelR->bool = true;
$modelR->float = 3.15;
$modelR->null = null;

View File

@ -36,6 +36,7 @@ final class SchemaMapperTest extends \PHPUnit\Framework\TestCase
`test_base_owns_one_self` int(11) DEFAULT NULL,
`test_base_json` varchar(254) DEFAULT NULL,
`test_base_json_serializable` varchar(254) DEFAULT NULL,
`test_base_serializable` varchar(254) DEFAULT NULL,
`test_base_datetime` datetime DEFAULT NULL,
`test_base_datetime_null` datetime DEFAULT NULL, /* There was a bug where it returned the current date because new \DateTime(null) === current date which is wrong, we want null as value! */
PRIMARY KEY (`test_base_id`)
@ -80,7 +81,7 @@ final class SchemaMapperTest extends \PHPUnit\Framework\TestCase
$schema = new SchemaMapper($GLOBALS['dbpool']->get());
self::assertEquals(
12,
13,
\count($schema->getFields('test_base'))
);
}

View File

@ -16,35 +16,35 @@ namespace phpOMS\tests\DataStorage\Database\TestModel;
class BaseModel
{
protected $id = 0;
protected int $id = 0;
public $string = 'Base';
public string $string = 'Base';
public $conditional = '';
public string $conditional = '';
public $int = 11;
public int $int = 11;
public $bool = false;
public bool $bool = false;
public $float = 1.3;
public float $float = 1.3;
public $null = null;
public $datetime = null;
public \DateTime $datetime;
public $datetime_null = null;
public ?\DateTime $datetime_null = null;
public $hasManyDirect = [];
public array $hasManyDirect = [];
public $hasManyRelations = [];
public array $hasManyRelations = [];
public $ownsOneSelf = 0;
public $belongsToOne = 0;
public $serializable = null;
public ?object $serializable = null;
public $json = [1, 2, 3];
public array $json = [1, 2, 3];
public $jsonSerializable = null;
@ -66,6 +66,8 @@ class BaseModel
$this->belongsToOne = new BelongsToModel();
$this->serializable = new class() implements \Serializable {
public $value = '';
public function serialize()
{
return '123';
@ -73,6 +75,7 @@ class BaseModel
public function unserialize($data) : void
{
$this->value = $data;
}
};

View File

@ -33,6 +33,7 @@ class BaseModelMapper extends DataMapperFactory
'test_base_float' => ['name' => 'test_base_float', 'type' => 'float', 'internal' => 'float'],
'test_base_json' => ['name' => 'test_base_json', 'type' => 'Json', 'internal' => 'json'],
'test_base_json_serializable' => ['name' => 'test_base_json_serializable', 'type' => 'Json', 'internal' => 'jsonSerializable'],
'test_base_serializable' => ['name' => 'test_base_serializable', 'type' => 'Serializable', 'internal' => 'serializable'],
'test_base_datetime' => ['name' => 'test_base_datetime', 'type' => 'DateTime', 'internal' => 'datetime'],
'test_base_datetime_null' => ['name' => 'test_base_datetime_null', 'type' => 'DateTime', 'internal' => 'datetime_null'],
'test_base_owns_one_self' => ['name' => 'test_base_owns_one_self', 'type' => 'int', 'internal' => 'ownsOneSelf'],

View File

@ -16,7 +16,7 @@ namespace phpOMS\tests\DataStorage\Database\TestModel;
class BelongsToModel
{
public $id = 0;
public int $id = 0;
public $string = 'BelongsTo';
public string $string = 'BelongsTo';
}

View File

@ -16,9 +16,9 @@ namespace phpOMS\tests\DataStorage\Database\TestModel;
class ManyToManyDirectModel
{
public $id = 0;
public int $id = 0;
public $string = 'ManyToManyDirect';
public string $string = 'ManyToManyDirect';
public int $to = 0;
}

View File

@ -16,7 +16,7 @@ namespace phpOMS\tests\DataStorage\Database\TestModel;
class ManyToManyRelModel
{
public $id = 0;
public int $id = 0;
public $string = 'ManyToManyRel';
public string $string = 'ManyToManyRel';
}

View File

@ -16,7 +16,7 @@ namespace phpOMS\tests\DataStorage\Database\TestModel;
class OwnsOneModel
{
public $id = 0;
public int $id = 0;
public $string = 'OwnsOne';
public string $string = 'OwnsOne';
}

View File

@ -295,6 +295,7 @@ final class ModuleAbstractTest extends \PHPUnit\Framework\TestCase
`test_base_owns_one_self` int(11) DEFAULT NULL,
`test_base_json` varchar(254) DEFAULT NULL,
`test_base_json_serializable` varchar(254) DEFAULT NULL,
`test_base_serializable` varchar(254) DEFAULT NULL,
`test_base_datetime` datetime DEFAULT NULL,
`test_base_datetime_null` datetime DEFAULT NULL, /* There was a bug where it returned the current date because new \DateTime(null) === current date which is wrong, we want null as value! */
PRIMARY KEY (`test_base_id`)