- Added a MssqlRepository
- Adapter some code to allow more flexibility for the new LDAP adapter from ulmus-ldap. - Insert's fields are now escaped by default. - A new RelationBuilder was added, a lot of code from the EntityTrait was moved into this. Some code from the Repository class will need to be moved there too.
This commit is contained in:
1 parent
8f9332fbad
commit
16c6eb57fb
32 files changed
+641
-243
No files matched your search
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{ Repository, Query };
|
||||
|
||||
class MssqlRepository extends Repository {
|
||||
|
||||
protected function finalizeQuery() : void
|
||||
{
|
||||
if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) {
|
||||
if ( null === $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) {
|
||||
throw new \Exception("Your offset query fragment is missing a LIMIT value.");
|
||||
}
|
||||
|
||||
# an order by is mandatory for mssql offset/limit
|
||||
if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) {
|
||||
$this->orderBy("(SELECT 0)");
|
||||
}
|
||||
|
||||
$mssqlOffset = new \Ulmus\Query\MsSQL\Offset();
|
||||
|
||||
$mssqlOffset->set($offset->offset, $limit->limit);
|
||||
|
||||
$this->queryBuilder->removeFragment($offset);
|
||||
$this->queryBuilder->removeFragment($limit);
|
||||
|
||||
$this->queryBuilder->push($mssqlOffset);
|
||||
}
|
||||
elseif ( null !== $limit = $this->queryBuilder->getFragment(Query\Limit::class) ) {
|
||||
if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$select->top = $limit->limit;
|
||||
}
|
||||
elseif ( null !== $delete = $this->queryBuilder->getFragment(Query\Delete::class) ) {
|
||||
$delete->top = $limit->limit;
|
||||
}
|
||||
|
||||
$this->queryBuilder->removeFragment($limit);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{ Ulmus, Annotation, Query, Common, Common\EntityResolver, Repository, Event, EntityCollection, };
|
||||
use Ulmus\Annotation\Property\{Filter, OrderBy, Relation, Relation\Ignore as RelationIgnore, Where, WithJoin, };
|
||||
|
||||
use Closure;
|
||||
|
||||
class RelationBuilder
|
||||
{
|
||||
protected Repository $repository;
|
||||
|
||||
protected /*object|string*/ $entity;
|
||||
|
||||
protected EntityResolver $resolver;
|
||||
|
||||
protected array $orders;
|
||||
|
||||
protected array $wheres;
|
||||
|
||||
protected array $filters;
|
||||
|
||||
protected array $joins;
|
||||
|
||||
public function __construct(/*string|object*/ $entity, ? Repository $repository = null)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
$this->resolver = $entity::resolveEntity();
|
||||
|
||||
if ($repository) {
|
||||
$this->repository = $repository;
|
||||
}
|
||||
}
|
||||
|
||||
public function searchRelation(string $name) /* : object|EntityCollection|bool */
|
||||
{
|
||||
# Resolve relations here if one is called
|
||||
if ( $this->entity->isLoaded() ) {
|
||||
if ( false !== ( $dataset = $this->fetchFromDataset($name) ) ) {
|
||||
return $dataset;
|
||||
}
|
||||
|
||||
return $this->resolveRelation($name);
|
||||
}
|
||||
}
|
||||
|
||||
protected function resolveRelation(string $name) /* : object|EntityCollection */
|
||||
{
|
||||
if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) ) {
|
||||
$this->orders = $this->resolver->searchFieldAnnotationList($name, new OrderBy() );
|
||||
$this->wheres = $this->resolver->searchFieldAnnotationList($name, new Where() );
|
||||
$this->filters = $this->resolver->searchFieldAnnotationList($name, new Filter() );
|
||||
$this->joins = $this->resolver->searchFieldAnnotationList($name, new WithJoin() );
|
||||
|
||||
switch( true ) {
|
||||
case $relation->isOneToOne():
|
||||
if ( $relation->hasBridge() ) {
|
||||
# @TODO ! dump($this->relationAnnotations($name, $relation));
|
||||
}
|
||||
else {
|
||||
$this->oneToOne($name, $relation);
|
||||
}
|
||||
|
||||
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
|
||||
|
||||
$result = call_user_func([ $this->repository, $relation->function ]);
|
||||
|
||||
return count($result) === 0 ? $this->instanciateEmptyEntity($name, $relation): $result[0];
|
||||
|
||||
case $relation->isOneToMany():
|
||||
$this->oneToMany($name, $relation);
|
||||
|
||||
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
|
||||
|
||||
return call_user_func([ $this->repository, $relation->function ]);
|
||||
|
||||
case $relation->isManyToMany():
|
||||
$this->manyToMany($name, $relation, $relationRelation);
|
||||
|
||||
$this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository);
|
||||
|
||||
$results = call_user_func([ $this->repository, $relationRelation->function ]);
|
||||
|
||||
if ($relation->bridgeField ?? false) {
|
||||
$collection = $relation->bridge::entityCollection();
|
||||
|
||||
foreach($results as $entity) {
|
||||
$collection->append($this->fetchFromDataset($relation->bridgeField, $entity->entityLoadedDataset));
|
||||
}
|
||||
|
||||
$this->entity->{$relation->bridgeField} = $collection;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function applyFilter(Repository $repository, string $name) : Repository
|
||||
{
|
||||
foreach($this->filters ?? [] as $filter) {
|
||||
$repository = call_user_func_array([ $this->entity, $filter->method ], [ $repository, $name ]);
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
protected function applyWhere() : void
|
||||
{
|
||||
if ($this->wheres) {
|
||||
$this->repository->open();
|
||||
|
||||
foreach($this->wheres as $condition) {
|
||||
$this->repository->where($condition->field, $condition->value, $condition->operator);
|
||||
}
|
||||
|
||||
$this->repository->close();
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyOrderBy() : void
|
||||
{
|
||||
foreach($this->orders as $item) {
|
||||
$this->repository->orderBy($item->field, $item->order);
|
||||
}
|
||||
}
|
||||
|
||||
protected function instanciateEmptyEntity(string $name, Relation $relation) : object
|
||||
{
|
||||
$class = $relation->entity ?? $this->resolver->properties[$name]['type'];
|
||||
|
||||
return new $class();
|
||||
}
|
||||
|
||||
protected function fetchFromDataset($name, ? array $data = null) /* object|bool */
|
||||
{
|
||||
$annotation = $this->resolver->searchFieldAnnotation($name, new Annotation\Property\Join) ?:
|
||||
$this->resolver->searchFieldAnnotation($name, new Annotation\Property\Relation);
|
||||
|
||||
if ( $annotation ) {
|
||||
$vars = [];
|
||||
$len = strlen( $name ) + 1;
|
||||
|
||||
if ( ( $annotation instanceof Relation ) && $annotation->isManyToMany() ) {
|
||||
$entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity;
|
||||
}
|
||||
else {
|
||||
$entity = $annotation->entity ?? $this->resolver->properties[$name]['type'];
|
||||
}
|
||||
|
||||
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
|
||||
if ( $key === "{$name}\$collection" ) {
|
||||
if ($value) {
|
||||
if ( null === ( $dataset = \json_decode($value, true) ) ) {
|
||||
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
|
||||
}
|
||||
|
||||
return $entity::entityCollection()->fromArray($dataset)->iterate(fn($e) => $e->resetVirtualProperties());
|
||||
}
|
||||
else {
|
||||
return $entity::entityCollection();
|
||||
}
|
||||
}
|
||||
elseif ( substr($key, 0, $len ) === "{$name}\$" ) {
|
||||
$vars[substr($key, $len)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($vars) {
|
||||
if ( [] !== $data = (array_values(array_unique($vars)) !== [ null ] ? $vars : []) ) {
|
||||
return ( new $entity() )->fromArray($data)->resetVirtualProperties();
|
||||
}
|
||||
else {
|
||||
return new $entity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function oneToOne(string $name, Relation $relation) : Repository
|
||||
{
|
||||
return $this->oneToMany($name, $relation)->limit(1);
|
||||
}
|
||||
|
||||
public function oneToMany(string $name, Relation $relation) : Repository
|
||||
{
|
||||
$baseEntity = $relation->entity ?? $this->resolver->properties[$name]['type'];
|
||||
|
||||
$this->repository = $baseEntity::repository();
|
||||
|
||||
$this->applyWhere();
|
||||
|
||||
$this->applyOrderBy();
|
||||
|
||||
$field = $relation->key;
|
||||
|
||||
if ($relation->foreignKey) {
|
||||
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), ! is_string($field) && is_callable($field) ? $field($this->entity) : $this->entity->$field );
|
||||
}
|
||||
|
||||
return $this->applyFilter($this->repository, $name);
|
||||
}
|
||||
|
||||
public function manyToMany(string $name, Relation $relation, ? Relation & $relationRelation = null, bool $selectBridgeField = true) : Repository
|
||||
{
|
||||
extract($this->relationAnnotations($name, $relation));
|
||||
|
||||
$this->repository = $relationRelation->entity::repository();
|
||||
|
||||
$bridgeAlias = $relation->bridgeField ?? uniqid("bridge_");
|
||||
|
||||
$relationAlias = $relation->field;
|
||||
|
||||
$this->repository->select("{$this->repository->alias}.*")
|
||||
->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $bridgeAlias), $relationRelation->entity::field($relationRelation->foreignKey), $bridgeAlias, function($join) {
|
||||
|
||||
})->join(Query\Join::TYPE_INNER, $this->entity::resolveEntity()->tableName(), $relation->bridge::field($bridgeRelation->key, $bridgeAlias), $this->entity::field($bridgeRelation->foreignKey, $relationAlias), $relationAlias, function($join) {
|
||||
|
||||
})->where( $this->entity::field($bridgeRelation->foreignKey, $relationAlias), is_string($this->entity) ? $this->entity::field($bridgeRelation->foreignKey) : $this->entity->{$bridgeRelation->foreignKey} );
|
||||
|
||||
if ($selectBridgeField && $relation->bridgeField) {
|
||||
$this->repository->selectEntity($relation->bridge, $bridgeAlias, $bridgeAlias);
|
||||
}
|
||||
|
||||
return $this->applyFilter($this->repository, $name);
|
||||
}
|
||||
|
||||
public static function relationAnnotations(string $name, Relation $relation) : array
|
||||
{
|
||||
if ( $relation->isOneToOne() || $relation->isManyToMany() ) {
|
||||
if ( ! $relation->hasBridge() ) {
|
||||
throw new \Exception("Your many-to-many @Relation() from variable `$name` is missing a 'bridge' field.");
|
||||
}
|
||||
|
||||
$bridgeEntity = Ulmus::resolveEntity($relation->bridge);
|
||||
$bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, new Relation() );
|
||||
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, new Relation() );
|
||||
|
||||
if ($relationRelation === null) {
|
||||
throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}");
|
||||
}
|
||||
|
||||
$relationRelation->entity ??= $relation->bridge::resolveEntity()->properties[$relation->foreignField]['type'];
|
||||
|
||||
return [
|
||||
'bridgeEntity' => $bridgeEntity,
|
||||
'bridgeRelation' => $bridgeRelation,
|
||||
'relationRelation' => $relationRelation,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\Repository;
|
||||
|
||||
class ServerRequestCountRepository extends Repository {}
|
||||
Reference in new issue
Block a user