- Added Events to repository and entitytrait objects. Some more work to be done on this.
- Diverses bug fixes linked to join and relations - Field date and datetime are now outputed using given locale.
This commit is contained in:
parent
1839726dc0
commit
4d221859fc
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
class Join implements \Ulmus\Annotation\Annotation {
|
||||
|
||||
public string $type;
|
||||
|
||||
public /*string|Stringable*/ $key;
|
||||
|
||||
public /*string|Stringable*/ $foreignKey;
|
||||
|
||||
public string $entity;
|
||||
|
||||
public string $alias;
|
||||
|
||||
public function __construct(? string $type = null, /*? string|Stringable*/ $key = null, /*? string|Stringable*/ $foreignKey = null)
|
||||
{
|
||||
if ($type !== null) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
if ($key !== null) {
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
if ($foreignKey !== null) {
|
||||
$this->foreignKey = $foreignKey;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,12 +55,17 @@ class PdoObject extends PDO {
|
|||
else {
|
||||
throw new \PDOException($statement->errorCode() . " - " . json_encode($statement->errorInfo()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
$this->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
debogueur($statement, $parameters, $commit);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ abstract class Sql {
|
|||
|
||||
protected array $arguments;
|
||||
|
||||
public function __construct(string $name, ...$arguments) {
|
||||
public function __construct( $name, ...$arguments) {
|
||||
$this->name = $name;
|
||||
$this->arguments = $arguments;
|
||||
$this->parseArguments();
|
||||
|
@ -39,6 +39,22 @@ abstract class Sql {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static function identifier(string $identifier) : object
|
||||
{
|
||||
return new class($identifier) {
|
||||
|
||||
protected string $identifier;
|
||||
|
||||
public function __construct(string $identifier) {
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->identifier;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static function escape($value)
|
||||
{
|
||||
|
|
|
@ -9,4 +9,8 @@ class Date extends Datetime {
|
|||
return $this->format("Y-m-d");
|
||||
}
|
||||
|
||||
public function formatLocale(string $format) : string
|
||||
{
|
||||
return strftime($format, $this->getTimestamp());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,8 @@ class Datetime extends \DateTime implements EntityObjectInterface {
|
|||
return $this->format($this->format);
|
||||
}
|
||||
|
||||
public function formatLocale(string $format) : string
|
||||
{
|
||||
return strftime($format, $this->getTimestamp());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,23 +47,39 @@ class EntityCollection extends \ArrayObject {
|
|||
}
|
||||
}
|
||||
|
||||
public function buildArray(string $keyColumn, /* string|callable */ $value) : array
|
||||
public function buildArray(string $keyColumn, /* string|callable|null */ $value = null) : array
|
||||
{
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach($this as $key => $item) {
|
||||
foreach($this as $item) {
|
||||
switch (true) {
|
||||
case is_string($value):
|
||||
$list[$item->$keyColumn] = $item->$value;
|
||||
case is_null($value):
|
||||
$list[] = $item->$keyColumn;
|
||||
break;
|
||||
|
||||
case is_callable($value):
|
||||
$list[$item->$keyColumn] = $value($item);
|
||||
break;
|
||||
|
||||
case is_object($value):
|
||||
case is_string($value):
|
||||
$value = (string) $value;
|
||||
|
||||
$list[$item->$keyColumn] = $item->$value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function toArray(bool $includeRelations = false) : array {
|
||||
$list = [];
|
||||
|
||||
foreach($this as $entity) {
|
||||
$list[] = $entity->toArray($includeRelations);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,25 +8,26 @@ use Ulmus\Repository,
|
|||
Ulmus\Common\EntityField;
|
||||
|
||||
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
|
||||
use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, };
|
||||
use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join };
|
||||
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, };
|
||||
|
||||
trait EntityTrait {
|
||||
use EventTrait;
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
protected bool $strictEntityFieldsDeclaration = false;
|
||||
protected bool $entityStrictFieldsDeclaration = false;
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
protected array $unmatchedEntityDatasetFields = [];
|
||||
protected array $entityDatasetUnmatchedFields = [];
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public array $datasetSource = [];
|
||||
public array $entityLoadedDataset = [];
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
|
@ -37,94 +38,144 @@ trait EntityTrait {
|
|||
|
||||
# Resolve relations here if one is called
|
||||
|
||||
# @TODO REFACTOR THIS CODE URGENTLY !
|
||||
if ( null !== ( $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ) ) ) {
|
||||
$relationType = strtolower(str_replace(['-', '_'], '', $relation->type));
|
||||
|
||||
$order = $entityResolver->searchFieldAnnotationList($name, new OrderBy() );
|
||||
$where = $entityResolver->searchFieldAnnotationList($name, new Where() );
|
||||
# @TODO REFACTOR THIS CODE ASAP !
|
||||
if ( $this->isLoaded() ) {
|
||||
|
||||
if ( $relation->entity ?? false ) {
|
||||
$baseEntity = $relation->entity();
|
||||
|
||||
$repository = $baseEntity->repository();
|
||||
if ( null !== ( $join= $entityResolver->searchFieldAnnotation($name, new Join() ) ) ) {
|
||||
$vars = [];
|
||||
|
||||
foreach($where as $condition) {
|
||||
$repository->where($condition->field, $condition->value, $condition->operator);
|
||||
$entity = $join->entity ?? $entityResolver->properties[$name]['type'];
|
||||
|
||||
foreach($this->entityDatasetUnmatchedFields as $key => $value) {
|
||||
$len = strlen( $name ) + 1;
|
||||
|
||||
if ( substr($key, 0, $len ) === "{$name}$" ) {
|
||||
$vars[substr($key, $len)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($order as $item) {
|
||||
$repository->orderBy($item->field, $item->order);
|
||||
}
|
||||
|
||||
$field = $relation->key;
|
||||
|
||||
if ( method_exists($this, $filterMethod = "filterRelation$name") ) {
|
||||
$this->$filterMethod($repository);
|
||||
if ( [] !== $data = (array_values(array_unique($vars)) !== [ null ] ? $vars : []) ) {
|
||||
return ( new $entity() )->fromArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
switch( $relationType ) {
|
||||
case 'onetoone':
|
||||
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field );
|
||||
$result = call_user_func([$repository, $relation->function]);
|
||||
|
||||
if ( count($result) === 0 ) {
|
||||
return $baseEntity;
|
||||
if ( null !== ( $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ) ) ) {
|
||||
$relationType = strtolower(str_replace(['-', '_'], '', $relation->type));
|
||||
|
||||
$order = $entityResolver->searchFieldAnnotationList($name, new OrderBy() );
|
||||
$where = $entityResolver->searchFieldAnnotationList($name, new Where() );
|
||||
|
||||
if ( $relation->entity ?? false ) {
|
||||
$baseEntity = $relation->entity();
|
||||
|
||||
$repository = $baseEntity->repository();
|
||||
|
||||
foreach($where as $condition) {
|
||||
$repository->where($condition->field, $condition->value, $condition->operator);
|
||||
}
|
||||
|
||||
return $this->$name = $result[0];
|
||||
|
||||
case 'onetomany':
|
||||
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field); # <<<<<<<<< CHANGE $THIS->ID WITH PROPER NOMENCLATURE
|
||||
|
||||
return $this->$name = call_user_func([$repository, $relation->function]);
|
||||
|
||||
case 'manytomany':
|
||||
if ( false === $relation->bridge ?? false ) {
|
||||
throw new \Exception("Your many-to-many @Relation() from variable `$name` is missing a 'bridge' value.");
|
||||
|
||||
foreach($order as $item) {
|
||||
$repository->orderBy($item->field, $item->order);
|
||||
}
|
||||
|
||||
$bridgeEntity = Ulmus::resolveEntity($relation->bridge);
|
||||
$bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, new Relation() );
|
||||
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, new Relation() );
|
||||
|
||||
$repository = $relationRelation->entity()->repository();
|
||||
|
||||
$bridgeAlias = uniqid("bridge_");
|
||||
$relationAlias = uniqid("relation_");
|
||||
$field = $relation->key;
|
||||
}
|
||||
|
||||
$repository->select("{$repository->alias}.*")
|
||||
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
|
||||
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
|
||||
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
|
||||
switch( $relationType ) {
|
||||
case 'onetoone':
|
||||
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field );
|
||||
|
||||
$this->$name = call_user_func([ $repository, $relationRelation->function ]);
|
||||
|
||||
if ($relation->bridgeField ?? false) {
|
||||
$repository = $relationRelation->entity::repository();
|
||||
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
|
||||
|
||||
$repository->select("$bridgeAlias.*")
|
||||
$result = call_user_func([$repository, $relation->function]);
|
||||
|
||||
if ( count($result) === 0 ) {
|
||||
return $baseEntity;
|
||||
}
|
||||
|
||||
return $this->$name = $result[0];
|
||||
|
||||
case 'onetomany':
|
||||
$repository->where( $baseEntity->field($relation->foreignKey), $this->$field);
|
||||
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
|
||||
|
||||
return $this->$name = call_user_func([$repository, $relation->function]);
|
||||
|
||||
case 'manytomany':
|
||||
if ( false === $relation->bridge ?? false ) {
|
||||
throw new \Exception("Your many-to-many @Relation() from variable `$name` is missing a 'bridge' value.");
|
||||
}
|
||||
|
||||
$bridgeEntity = Ulmus::resolveEntity($relation->bridge);
|
||||
$bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, new Relation() );
|
||||
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, new Relation() );
|
||||
|
||||
$repository = $relationRelation->entity()->repository();
|
||||
|
||||
$bridgeAlias = uniqid("bridge_");
|
||||
$relationAlias = uniqid("relation_");
|
||||
|
||||
# @TODO Rewrite to be done here, this code must move somewhere else...
|
||||
$repository->select("{$repository->alias}.*")
|
||||
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
|
||||
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
|
||||
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
|
||||
|
||||
$bridgeName = $relation->bridgeField;
|
||||
foreach($where as $condition) {
|
||||
$repository->where($condition->field, $condition->value, $condition->operator);
|
||||
}
|
||||
|
||||
foreach($order as $item) {
|
||||
$repository->orderBy($item->field, $item->order);
|
||||
}
|
||||
|
||||
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
|
||||
|
||||
$this->$bridgeName = $repository->collectionFromQuery($relation->bridge);
|
||||
}
|
||||
|
||||
return $this->$name;
|
||||
}
|
||||
$this->$name = call_user_func([ $repository, $relationRelation->function ]);
|
||||
|
||||
if ($relation->bridgeField ?? false) {
|
||||
$repository = $relationRelation->entity::repository();
|
||||
|
||||
$repository->select("$bridgeAlias.*")
|
||||
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias)
|
||||
->join(Query\Join::TYPE_INNER, $this->resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), static::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias)
|
||||
->where( static::field($bridgeRelation->foreignKey, $relationAlias), $this->{$bridgeRelation->foreignKey} );
|
||||
|
||||
foreach($where as $condition) {
|
||||
$repository->where($condition->field, $condition->value, $condition->operator);
|
||||
}
|
||||
|
||||
foreach($order as $item) {
|
||||
$repository->orderBy($item->field, $item->order);
|
||||
}
|
||||
|
||||
$bridgeName = $relation->bridgeField;
|
||||
|
||||
$this->$bridgeName = $repository->collectionFromQuery($relation->bridge);
|
||||
}
|
||||
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function __isset(string $name) : bool
|
||||
{
|
||||
if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isset($this->$name);
|
||||
}
|
||||
|
||||
|
@ -145,11 +196,11 @@ trait EntityTrait {
|
|||
}
|
||||
|
||||
if ( $field === null ) {
|
||||
if ($this->strictEntityFieldsDeclaration ) {
|
||||
if ($this->entityStrictFieldsDeclaration ) {
|
||||
throw new \Exception("Field `$key` can not be found within your entity ".static::class);
|
||||
}
|
||||
else {
|
||||
$this->unmatchedEntityDatasetFields[$key] = $value;
|
||||
$this->entityDatasetUnmatchedFields[$key] = $value;
|
||||
}
|
||||
}
|
||||
elseif ( is_null($value) ) {
|
||||
|
@ -178,24 +229,30 @@ trait EntityTrait {
|
|||
if ( ! $loaded ) {
|
||||
#if ( $field !== null ) {
|
||||
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
|
||||
# $this->datasetSource[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
|
||||
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
|
||||
#}
|
||||
$this->datasetSource = array_change_key_case($dataset, \CASE_LOWER);
|
||||
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function fromArray(iterable $dataset) : self
|
||||
{
|
||||
return $this->entityFillFromDataset($dataset);
|
||||
}
|
||||
|
||||
public function entityGetDataset(bool $returnSource = false) : array
|
||||
{
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array
|
||||
{
|
||||
if ( $returnSource ) {
|
||||
return $this->datasetSource;
|
||||
return $this->entityLoadedDataset;
|
||||
}
|
||||
|
||||
$dataset = [];
|
||||
|
@ -226,19 +283,52 @@ trait EntityTrait {
|
|||
}
|
||||
}
|
||||
|
||||
# @TODO Must fix recursive bug !
|
||||
if ($includeRelations) {
|
||||
foreach($entityResolver->properties as $name => $field){
|
||||
$relation = $entityResolver->searchFieldAnnotation($name, new Relation() );
|
||||
|
||||
if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
|
||||
if ( null !== $value = $this->$name ?? null ) {
|
||||
if ( is_iterable($value) ) {
|
||||
$list = [];
|
||||
|
||||
foreach($value as $entity) {
|
||||
$list[] = $entity->entityGetDataset(true);
|
||||
}
|
||||
|
||||
$dataset[$name] = $list;
|
||||
}
|
||||
elseif ( is_object($value) ) {
|
||||
$dataset[$name] = $value->entityGetDataset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dataset;
|
||||
}
|
||||
|
||||
public function toArray() : array
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function toArray($includeRelations = false) : array
|
||||
{
|
||||
return $this->entityGetDataset();
|
||||
return $this->entityGetDataset($includeRelations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function toCollection() : array
|
||||
{
|
||||
return new EntityCollection($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function isLoaded() : bool
|
||||
{
|
||||
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
|
||||
|
@ -250,12 +340,17 @@ trait EntityTrait {
|
|||
return isset($this->$key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
return array_keys($this->resolveEntity()->fieldList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->entityGetDataset();
|
||||
|
@ -264,7 +359,7 @@ trait EntityTrait {
|
|||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function resolveEntity() : EntityResolver
|
||||
public static function resolveEntity() : EntityResolver
|
||||
{
|
||||
return Ulmus::resolveEntity(static::class);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Event;
|
||||
|
||||
use Ulmus\Repository;
|
||||
|
||||
interface EntityRelationLoadInterface {
|
||||
public function execute(string $name, Repository $repository) : Repository;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Event;
|
||||
|
||||
use Ulmus\EntityCollection;
|
||||
|
||||
interface RepositoryCollectionFromQueryInterface {
|
||||
public function execute(EntityCollection $collection) : EntityCollection;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus;
|
||||
|
||||
trait EventTrait
|
||||
{
|
||||
public array $eventList = [];
|
||||
|
||||
public function eventRegister(object $event) : void
|
||||
{
|
||||
$this->eventList[] = $event;
|
||||
}
|
||||
|
||||
public function eventFromType(string $type) : array
|
||||
{
|
||||
return array_filter($this->eventList, fn($ev) => $ev instanceof $type);
|
||||
}
|
||||
|
||||
public function eventExecute(string $type, ...$arguments) : void
|
||||
{
|
||||
foreach($this->eventFromType($type) as $event) {
|
||||
call_user_func_array([ $event, 'execute'], $arguments);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,6 @@ class Join extends Fragment {
|
|||
|
||||
public function render() : string
|
||||
{
|
||||
return $this->renderSegments([ $this->side, static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value ]);
|
||||
return $this->renderSegments([ strtoupper($this->side), static::SQL_TOKEN, $this->table, $this->alias ?? "", $this->attachment, $this->field, "=", $this->value ]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ class Where extends Fragment {
|
|||
return (in_array($this->operator, [ '!=', '<>' ]) ? Where::CONDITION_NOT . " " : "") . Where::COMPARISON_IN;
|
||||
}
|
||||
|
||||
return $this->operator;
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
protected function value()
|
||||
|
@ -140,7 +140,7 @@ class Where extends Fragment {
|
|||
return $value->name();
|
||||
}
|
||||
else {
|
||||
return $this->queryBuilder->addParameter($this->value);
|
||||
return $this->queryBuilder->addParameter($value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -174,6 +174,11 @@ class QueryBuilder
|
|||
|
||||
public function where($field, $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self
|
||||
{
|
||||
# Empty IN case
|
||||
if ( [] === $value ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ( $this->where ?? false ) {
|
||||
$where = $this->where;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ use Ulmus\Common\EntityResolver;
|
|||
|
||||
class Repository
|
||||
{
|
||||
use EventTrait;
|
||||
|
||||
const DEFAULT_ALIAS = "this";
|
||||
|
||||
public ? ConnectionAdapter $adapter;
|
||||
|
@ -17,7 +19,9 @@ class Repository
|
|||
public string $alias;
|
||||
|
||||
public string $entityClass;
|
||||
|
||||
|
||||
public array $events = [];
|
||||
|
||||
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) {
|
||||
$this->entityClass = $entity;
|
||||
$this->alias = $alias;
|
||||
|
@ -36,9 +40,9 @@ class Repository
|
|||
return $this->where($field, $value)->loadOne();
|
||||
}
|
||||
|
||||
public function loadFromPk($value, $primaryKey = "id") : ? object
|
||||
public function loadFromPk($value, /* ? stringable */ $primaryKey = null) : ? object
|
||||
{
|
||||
return $this->loadOneFromField($primaryKey, $value);
|
||||
return $primaryKey ? $this->loadOneFromField($primaryKey, $value) : $this->wherePrimaryKey($value)->loadOne();
|
||||
}
|
||||
|
||||
public function loadAll() : EntityCollection
|
||||
|
@ -53,6 +57,10 @@ class Repository
|
|||
|
||||
public function count() : int
|
||||
{
|
||||
if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$this->queryBuilder->removeFragment($select);
|
||||
}
|
||||
|
||||
$this->select("COUNT(*)")->selectSqlQuery();
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
@ -79,8 +87,39 @@ class Repository
|
|||
return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount();
|
||||
}
|
||||
|
||||
public function destroy(object $entity) : bool
|
||||
{
|
||||
if ( ! $this->matchEntity($entity) ) {
|
||||
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
||||
}
|
||||
|
||||
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if ( $primaryKeyDefinition === null ) {
|
||||
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
else {
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
|
||||
return $this->deleteFromPk($entity->$pkField);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function destroyAll(EntityCollection $collection) : void
|
||||
{
|
||||
foreach($collection as $entity) {
|
||||
$this->destroy($entity);
|
||||
}
|
||||
}
|
||||
|
||||
public function save(object $entity) : bool
|
||||
{
|
||||
if ( ! $this->matchEntity($entity) ) {
|
||||
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
||||
}
|
||||
|
||||
$dataset = $entity->toArray();
|
||||
|
||||
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
@ -124,7 +163,7 @@ class Repository
|
|||
|
||||
public function generateDatasetDiff(object $entity) : array
|
||||
{
|
||||
return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(true)) );
|
||||
return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) );
|
||||
}
|
||||
|
||||
public function yieldAll() : \Generator
|
||||
|
@ -294,9 +333,9 @@ class Repository
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function orNotHaving($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self
|
||||
public function orNotHaving($field, $value) : self
|
||||
{
|
||||
$this->queryBuilder->having($field, $value, $operator, Query\Having::CONDITION_OR, true);
|
||||
$this->queryBuilder->having($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Having::CONDITION_OR, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -322,9 +361,9 @@ class Repository
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function orNotIn($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
|
||||
public function orNotIn($field, $value) : self
|
||||
{
|
||||
return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR);
|
||||
return $this->orNot($field, $value, Query\Where::OPERATOR_NOT_EQUAL, Query\Where::CONDITION_OR, true);
|
||||
}
|
||||
|
||||
public function like($field, $value) : self
|
||||
|
@ -436,11 +475,14 @@ class Repository
|
|||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* @TODO */
|
||||
public function commit() : self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* @TODO */
|
||||
public function rollback() : self
|
||||
{
|
||||
return $this;
|
||||
|
@ -457,6 +499,33 @@ class Repository
|
|||
return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
|
||||
}
|
||||
|
||||
public function withJoin(/*string|array*/ $fields) : self
|
||||
{
|
||||
$resolvedEntity = Ulmus::resolveEntity($this->entityClass);
|
||||
$this->select("{$this->alias}.*");
|
||||
|
||||
foreach((array) $fields as $item) {
|
||||
if ( null !== $join = $resolvedEntity->searchFieldAnnotation($item, new Annotation\Property\Join) ) {
|
||||
$alias = $join->alias ?? $item;
|
||||
$entity = $join->entity ?? $resolvedEntity->properties[$item]['type'];
|
||||
|
||||
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME) as $key => $field) {
|
||||
$this->select("$alias.$key as {$alias}\${$field['name']}");
|
||||
}
|
||||
|
||||
$key = is_string($join->key) ? $this->entityClass::field($join->key) : $join->key;
|
||||
$foreignKey = is_string($join->foreignKey) ? $entity::field($join->foreignKey, $alias) : $join->foreignKey;
|
||||
|
||||
$this->join($join->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias);
|
||||
}
|
||||
else {
|
||||
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation.");
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self
|
||||
{
|
||||
$searchRequest->count = $searchRequest->filter( clone $this )
|
||||
|
@ -473,12 +542,12 @@ class Repository
|
|||
->offset($searchRequest->offset())
|
||||
->limit($searchRequest->limit());
|
||||
}
|
||||
|
||||
|
||||
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
|
||||
{
|
||||
$class = $entityClass ?: $this->entityClass;
|
||||
|
||||
$entityCollection = new EntityCollection();
|
||||
$entityCollection = $this->instanciateEntityCollection();
|
||||
|
||||
$this->selectSqlQuery();
|
||||
|
||||
|
@ -487,9 +556,20 @@ class Repository
|
|||
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
||||
$entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) );
|
||||
}
|
||||
|
||||
$this->eventExecute(Event\RepositoryCollectionFromQueryInterface::class, $entityCollection);
|
||||
|
||||
return $entityCollection;
|
||||
}
|
||||
|
||||
public function arrayFromQuery() : array
|
||||
{
|
||||
$this->selectSqlQuery();
|
||||
|
||||
$this->finalizeQuery();
|
||||
|
||||
return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter);
|
||||
}
|
||||
|
||||
public function runQuery() : ? \PDOStatement
|
||||
{
|
||||
|
@ -556,9 +636,9 @@ class Repository
|
|||
|
||||
}
|
||||
|
||||
public function table()
|
||||
public function instanciateEntityCollection() : EntityCollection
|
||||
{
|
||||
return "REFLECT TABLE";
|
||||
return new EntityCollection();
|
||||
}
|
||||
|
||||
public function escapeTable(string $identifier) : string
|
||||
|
@ -576,5 +656,9 @@ class Repository
|
|||
return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA);
|
||||
}
|
||||
|
||||
protected function matchEntity(object $entity) {
|
||||
return get_class($entity) === $this->entityClass;
|
||||
}
|
||||
|
||||
protected function finalizeQuery() : void {}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,25 @@ abstract class Ulmus
|
|||
];
|
||||
}
|
||||
|
||||
public static function datasetQueryBuilder(QueryBuilder $queryBuilder, ? ConnectionAdapter $adapter = null) : array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
$sql = $queryBuilder->render();
|
||||
|
||||
$statement = ( $adapter ?: static::$defaultAdapter )->pdo()->select($sql, $queryBuilder->parameters ?? []);
|
||||
|
||||
while ( $row = $statement->fetch() ) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$statement->closeCursor();
|
||||
|
||||
$queryBuilder->reset();
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public static function pdo(? ConnectionAdapter $adapter = null) : Common\PdoObject
|
||||
{
|
||||
return ( $adapter ?: static::$defaultAdapter )->pdo();
|
||||
|
|
Loading…
Reference in New Issue