- WIP on attributes ; on testing phase

This commit is contained in:
Dave M. 2023-01-26 13:25:59 +00:00
parent 5295a335b3
commit e6d0279c1a
44 changed files with 763 additions and 142 deletions

View File

@ -6,9 +6,9 @@ class Join implements \Notes\Annotation {
public string $type; public string $type;
public /*string|Stringable*/ $key; public string|Stringable $key;
public /*string|Stringable*/ $foreignKey; public string|Stringable $foreignKey;
public string $entity; public string $entity;

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Attribute;
use Ulmus\Common\EntityField;
class Attribute
{
public static function handleArrayField(null|\Stringable|string|array $field) : mixed
{
if ( is_array($field) ) {
$class = array_shift($field);
return $class::field(...$field);
}
return $field;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Attribute\Obj;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Collation {
}

View File

@ -0,0 +1,8 @@
<?php
namespace Ulmus\Attribute\Obj;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Method {
}

View File

@ -0,0 +1,14 @@
<?php
namespace Ulmus\Attribute\Obj;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Table {
public function __construct(
public string $name,
public ? string $database = null,
public ? string $schema = null,
public ? string $adapter = null,
public ? string $engine = null,
) {}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Field {
public function __construct(
public ? string $name = null,
public ? string $type = null,
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Bigint extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "bigint",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Blob extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "blob",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class CreatedAt extends \Ulmus\Attribute\Property\Field {
public function __construct(
public ? string $name = null,
public ? string $type = "timestamp",
public array $attributes = [
'default' => "CURRENT_TIMESTAMP",
],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Date extends \Ulmus\Attribute\Property\Field {
public function __construct(
public ? string $name = null,
public ? string $type = "date",
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Datetime extends \Ulmus\Attribute\Property\Field {
public function __construct(
public ? string $name = null,
public ? string $type = "datetime",
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Ulmus\Attribute\Property\Field;
/**
* Since we need consistancy between the declaration of our ID and FK fields, it
* was decided to extend the PK class instead of Field for this case.
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class ForeignKey extends PrimaryKey {
public function __construct(
public ? string $name = null,
public ? string $type = 'bigint',
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [
'primary_key' => false,
'auto_increment' => false
],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Id extends \Ulmus\Attribute\Property\Field {
public function __construct(
public ? string $name = null,
public ? string $type = 'bigint',
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [
'unsigned' => true,
'auto_increment' => true,
'primary_key' => true,
],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Longblob extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "longblob",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Longtext extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "longtext",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Mediumblob extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "mediumblob",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Mediumtext extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "mediumtext",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class PrimaryKey extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = 'bigint',
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [
'primary_key' => true,
],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Text extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "text",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Time extends \Ulmus\Attribute\Property\Field
{
public function __construct(? string $type = "time", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Blob extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "tinyblob",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Tinyint extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "tinyint",
public ? int $length = null,
public ? int $precision = null,
public array $attributes = [],
public bool $nullable = false,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Attribute\Property\Field;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class UpdatedAt extends \Ulmus\Attribute\Property\Field
{
public function __construct(
public ? string $name = null,
public ? string $type = "timestamp",
public array $attributes = [
'update' => "CURRENT_TIMESTAMP",
'default' => null,
],
public bool $nullable = true,
public mixed $default = null,
public bool $readonly = false,
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Filter {
public function __construct(
public string $method = ""
)
{}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class FilterJoin {
public function __construct(
public string $method = ""
) {}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class GroupBy {
public array $fields = [];
public function __construct(...$field)
{
if ( $field ) {
$this->fields = $field;
}
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Having extends Where {}

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
#[\Attribute]
class Join {
public function __construct(
public string $type,
public null|string|\Stringable $key = null,
public null|string|\Stringable $foreignKey = null,
public null|string $entity = null,
public null|string $alias = null,
) {
$this->key = Attribute::handleArrayField($this->key);
$this->foreignKey = Attribute::handleArrayField($this->foreignKey);
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class On extends Where {}

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
use Ulmus\Query;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class OrWhere extends Where {
public function __construct(
public string|\Stringable|array $field,
public mixed $value = null,
public null|string $operator = null,
public null|string $condition = Query\Where::CONDITION_OR,
) {
$this->key = Attribute::handleArrayField($this->key);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class OrderBy {
public function __construct(
public string|\Stringable|array $field,
public string $order = "ASC",
) {
$this->field = Attribute::handleArrayField($this->field);
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Relation {
public function __construct(
public string $type,
public \Stringable|string|array $key = "",
public null|\Closure $generateKey = null,
public null|\Stringable|string|array $foreignKey = null,
public null|\Stringable|string|array $foreignField = null,
public array $foreignKeys = [],
public null|string $bridge = null,
public null|\Stringable|string|array $bridgeKey = null,
public null|\Stringable|string|array $bridgeField = null,
public null|\Stringable|string|array $bridgeForeignKey = null,
public null|\Stringable|string|array $field = null,
public null|string $entity = null,
public null|string $join = null,
public string $function = "loadAll",
) {
$this->key = Attribute::handleArrayField($this->key);
$this->foreignKey = Attribute::handleArrayField($this->foreignKey);
$this->foreignField = Attribute::handleArrayField($this->foreignField);
$this->bridgeKey = Attribute::handleArrayField($this->bridgeKey);
$this->bridgeField = Attribute::handleArrayField($this->bridgeField);
$this->bridgeForeignKey = Attribute::handleArrayField($this->bridgeForeignKey);
$this->field = Attribute::handleArrayField($this->field);
}
public function entity() {
try {
$e = $this->entity;
} catch (\Throwable $ex) {
throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry.");
}
return new $e();
}
public function bridge() {
$e = $this->bridge;
return new $e();
}
public function normalizeType() : string
{
return strtolower(str_replace(['-', '_', ' '], '', $this->type));
}
public function isOneToOne() : bool
{
return $this->normalizeType() === 'onetoone';
}
public function isOneToMany() : bool
{
return $this->normalizeType() === 'onetomany';
}
public function isManyToMany() : bool
{
return $this->normalizeType() === 'manytomany';
}
public function function() : string
{
if ($this->function) {
return $this->function;
}
elseif ($this->isOneToOne()) {
return 'load';
}
return 'loadAll';
}
public function hasBridge() : bool
{
return (bool) $this->bridge;
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace Ulmus\Attribute\Property\Relation;
#[\Attribute]
class Ignore {}

View File

@ -0,0 +1,13 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Virtual extends Field {
public bool $readonly = true;
public function __construct(
public ? string $method = null,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Ulmus\Attribute\Property;
use Ulmus\Attribute\Attribute;
use Ulmus\Query;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Where {
public function __construct(
public string|\Stringable|array $field,
public mixed $value = null,
public string $operator = Query\Where::OPERATOR_EQUAL,
public string $condition = Query\Where::CONDITION_AND,
) {
$this->field = Attribute::handleArrayField($field);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Ulmus\Attribute\Property;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class WithJoin {
public function __construct(
array $joins = []
) {}
}

View File

@ -3,6 +3,7 @@
namespace Ulmus\Common; namespace Ulmus\Common;
use Ulmus\Annotation\Annotation; use Ulmus\Annotation\Annotation;
use Ulmus\Attribute;
use Ulmus\Migration\FieldDefinition; use Ulmus\Migration\FieldDefinition;
use Ulmus\Ulmus, use Ulmus\Ulmus,
Ulmus\Adapter\AdapterInterface, Ulmus\Adapter\AdapterInterface,
@ -29,7 +30,7 @@ class EntityField implements WhereRawParameter
public function name($useAlias = true) : string public function name($useAlias = true) : string
{ {
$name = $this->entityResolver->searchFieldAnnotation($this->name, new Field() )->name ?? $this->name; $name = $this->entityResolver->searchFieldAnnotation($this->name, [ Attribute\Property\Field::class, Field::class ] )->name ?? $this->name;
$name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD); $name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD);

View File

@ -2,11 +2,13 @@
namespace Ulmus\Common; namespace Ulmus\Common;
use Psr\SimpleCache\CacheInterface;
use Ulmus\Ulmus, use Ulmus\Ulmus,
Ulmus\Annotation\Classes\Table, Ulmus\Annotation\Classes\Table,
Ulmus\Annotation\Property\Field, Ulmus\Annotation\Property\Field,
Ulmus\Annotation\Property\Virtual, Ulmus\Annotation\Property\Virtual,
Ulmus\Annotation\Property\Relation; Ulmus\Annotation\Property\Relation,
Ulmus\Attribute;
use Notes\Annotation; use Notes\Annotation;
@ -32,12 +34,12 @@ class EntityResolver {
protected array $fieldList = []; protected array $fieldList = [];
public function __construct(string $entityClass) public function __construct(string $entityClass, ? CacheInterface $cache = null)
{ {
$this->entityClass = $entityClass; $this->entityClass = $entityClass;
list($this->uses, $this->class, $this->methods, $this->properties) = array_values( list($this->uses, $this->class, $this->methods, $this->properties) = array_values(
ObjectReflection::fromClass($entityClass)->read() ObjectReflection::fromClass($entityClass, $cache)->read()
); );
$this->resolveAnnotations(); $this->resolveAnnotations();
@ -57,14 +59,28 @@ class EntityResolver {
return null; return null;
} }
public function searchField($name) : null|array
{
try{
return $this->field($name, self::KEY_ENTITY_NAME, false) ?: $this->field($name, self::KEY_COLUMN_NAME, false);
}
catch(\Throwable $e) {
if ( $throwException) {
throw new \InvalidArgumentException("Can't find entity field's column named `$name` from entity {$this->entityClass}");
}
}
return null;
}
public function fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array public function fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array
{ {
$fieldList = []; $fieldList = [];
foreach($this->properties as $item) { foreach($this->properties as $item) {
foreach($item['tags'] ?? [] as $tag) { foreach($item['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof Field ) { if ( $tag['object'] instanceof Field or $tag['object'] instanceof Attribute\Property\Field ) {
if ( $skipVirtual && ($tag['object'] instanceof Virtual )) { if ( $skipVirtual && ($tag['object'] instanceof Virtual or $tag['object'] instanceof Attribute\Property\Virtual )) {
break; break;
} }
@ -101,7 +117,7 @@ class EntityResolver {
try{ try{
if ( null !== ( $this->properties[$name] ?? null ) ) { if ( null !== ( $this->properties[$name] ?? null ) ) {
foreach($this->properties[$name]['tags'] ?? [] as $tag) { foreach($this->properties[$name]['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof Relation ) { if ( $tag['object'] instanceof Relation or $tag['object'] instanceof Attribute\Property\Relation ) {
return $this->properties[$name]; return $this->properties[$name];
} }
} }
@ -118,22 +134,25 @@ class EntityResolver {
return null; return null;
} }
public function searchFieldAnnotation(string $field, Annotation $annotationType, bool $caseSensitive = true) : ? Annotation public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
{ {
$found = $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive); return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
return $found ? $found[0] : null;
} }
public function searchFieldAnnotationList(string $field, Annotation $annotationType, bool $caseSensitive = true) : array public function searchFieldAnnotationList(string $field, array|object|string $annotationType, bool $caseSensitive = true) : array
{ {
$list = []; $list = [];
$search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER); $search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER);
$annotations = is_array($annotationType) ? $annotationType : [ $annotationType ];
if ( null !== ( $search[$field] ?? null ) ) { if ( null !== ( $search[$field] ?? null ) ) {
foreach($search[$field]['tags'] ?? [] as $tag) { foreach($search[$field]['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof $annotationType ) { foreach($annotations as $annotation) {
$list[] = $tag['object']; if ( $tag['object'] instanceof $annotation ) {
$list[] = $tag['object'];
}
} }
} }
} }
@ -154,9 +173,9 @@ class EntityResolver {
return $table->name ?? ""; return $table->name ?? "";
} }
public function tableAnnotation($required = false) : Table public function tableAnnotation($required = false) : Table|Attribute\Obj\Table
{ {
if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { if ( null === $table = $this->getTableAttribute() ) {
if ($required) { if ($required) {
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
} }
@ -172,7 +191,7 @@ class EntityResolver {
public function sqlAdapter() : \Ulmus\ConnectionAdapter public function sqlAdapter() : \Ulmus\ConnectionAdapter
{ {
if ( null !== $table = $this->getAnnotationFromClassname( Table::class ) ) { if ( null !== $table = $this->getTableAttribute() ) {
if ( $table->adapter ?? null ) { if ( $table->adapter ?? null ) {
if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$table->adapter] ?? null ) ) { if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$table->adapter] ?? null ) ) {
throw new \Exception("Requested database adapter `{$table->adapter}` is not registered."); throw new \Exception("Requested database adapter `{$table->adapter}` is not registered.");
@ -196,7 +215,7 @@ class EntityResolver {
public function schemaName(bool $required = false) : ? string public function schemaName(bool $required = false) : ? string
{ {
if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { if ( null === $table = $this->getTableAttribute() ) {
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
} }
@ -210,7 +229,8 @@ class EntityResolver {
public function getPrimaryKeyField() : ? array public function getPrimaryKeyField() : ? array
{ {
foreach($this->fieldList() as $key => $value) { foreach($this->fieldList() as $key => $value) {
if ( null !== ( $field = $this->searchFieldAnnotation($key, new Field() ) ) ) { $field = $this->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]);
if ( null !== $field ) {
if ( false !== ( $field->attributes['primary_key'] ?? false ) ) { if ( false !== ( $field->attributes['primary_key'] ?? false ) ) {
return [ $key => $field ]; return [ $key => $field ];
} }
@ -230,13 +250,17 @@ class EntityResolver {
return null; return null;
} }
protected function getTableAttribute()
{
return $this->getAnnotationFromClassname(Attribute\Obj\Table::class, false) ?: $this->getAnnotationFromClassname( Table::class );
}
/** /**
* Transform an annotation into it's object's counterpart * Transform an annotation into it's object's counterpart
*/ */
public function getAnnotationFromClassname(string $className, bool $throwError = true) : ? object public function getAnnotationFromClassname(string $className, bool $throwError = true) : ? object
{ {
if ( $name = $this->uses[$className] ?? false ) { if ( $name = $this->uses[$className] ?? false ) {
foreach(array_reverse($this->class['tags']) as $item) { foreach(array_reverse($this->class['tags']) as $item) {
if ( $item['tag'] === $name ) { if ( $item['tag'] === $name ) {
return $this->instanciateAnnotationObject($item); return $this->instanciateAnnotationObject($item);
@ -270,18 +294,23 @@ class EntityResolver {
return null; return null;
} }
public function instanciateAnnotationObject(array $tagDefinition) : Annotation public function instanciateAnnotationObject(array|\ReflectionAttribute $tagDefinition) : object
{ {
$arguments = $this->extractArguments($tagDefinition['arguments']); if ($tagDefinition instanceof \ReflectionAttribute) {
$obj = $tagDefinition->newInstance();
if ( false === $class = array_search($tagDefinition['tag'], $this->uses) ) {
throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)");
} }
else {
$arguments = $this->extractArguments($tagDefinition['arguments']);
$obj = new $class(... $arguments['constructor']); if (false === $class = array_search($tagDefinition['tag'], $this->uses)) {
throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)");
}
foreach($arguments['setter'] as $key => $value) { $obj = new $class(... $arguments['constructor']);
$obj->$key = $value;
foreach ($arguments['setter'] as $key => $value) {
$obj->$key = $value;
}
} }
return $obj; return $obj;
@ -309,18 +338,18 @@ class EntityResolver {
protected function resolveAnnotations() protected function resolveAnnotations()
{ {
foreach($this->class['tags'] as &$tag) { foreach($this->class['tags'] as &$tag) {
$tag['object'] = $this->instanciateAnnotationObject($tag); $tag['object'] ??= $this->instanciateAnnotationObject($tag);
} }
foreach($this->properties as &$property) { foreach($this->properties as &$property) {
foreach($property['tags'] as &$tag){ foreach($property['tags'] as &$tag){
$tag['object'] = $this->instanciateAnnotationObject($tag); $tag['object'] ??= $this->instanciateAnnotationObject($tag);
} }
} }
foreach($this->methods as &$method) { foreach($this->methods as &$method) {
foreach($method['tags'] as &$tag){ foreach($method['tags'] as &$tag){
$tag['object'] = $this->instanciateAnnotationObject($tag); $tag['object'] ??= $this->instanciateAnnotationObject($tag);
} }
} }
} }

View File

@ -64,7 +64,7 @@ class ConnectionAdapter
public function pdo() : PdoObject public function pdo() : PdoObject
{ {
return $this->pdo ?? $this->pdo = $this->connect()->pdo; return $this->pdo ?? $this->connect()->pdo;
} }
public function connector() : object public function connector() : object

View File

@ -66,7 +66,7 @@ trait EntityTrait {
elseif ( EntityField::isScalarType($field['type']) ) { elseif ( EntityField::isScalarType($field['type']) ) {
if ( $field['type'] === 'string' ) { if ( $field['type'] === 'string' ) {
$annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); $annotation = $entityResolver->searchFieldAnnotation($field['name'], [ Attribute\Property\Field::class, Field::class ] );
if ( $annotation->length ?? null ) { if ( $annotation->length ?? null ) {
$value = mb_substr($value, 0, $annotation->length); $value = mb_substr($value, 0, $annotation->length);
@ -90,7 +90,6 @@ trait EntityTrait {
} }
catch(\Error $e) { catch(\Error $e) {
$f = $field['type']; $f = $field['type'];
dump($f, $f::from($value));
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name'])); throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name']));
} }
} }
@ -126,17 +125,11 @@ trait EntityTrait {
return $this; return $this;
} }
/**
* @Ignore
*/
public function fromArray(iterable $dataset) : self public function fromArray(iterable $dataset) : self
{ {
return $this->entityFillFromDataset($dataset); return $this->entityFillFromDataset($dataset);
} }
/**
* @Ignore
*/
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array
{ {
if ( $returnSource ) { if ( $returnSource ) {
@ -148,7 +141,7 @@ trait EntityTrait {
$entityResolver = $this->resolveEntity(); $entityResolver = $this->resolveEntity();
foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) { foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) {
$annotation = $entityResolver->searchFieldAnnotation($key, new Field() ); $annotation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]);
if ( isset($this->$key) ) { if ( isset($this->$key) ) {
$dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key); $dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key);
@ -161,7 +154,7 @@ trait EntityTrait {
# @TODO Must fix recursive bug ! # @TODO Must fix recursive bug !
if ($includeRelations) { if ($includeRelations) {
foreach($entityResolver->properties as $name => $field){ foreach($entityResolver->properties as $name => $field){
$relation = $entityResolver->searchFieldAnnotation($name, new Relation() ); $relation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Relation::class. Relation::class ] );
if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) { if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) {
if ( null !== $value = $this->$name ?? null ) { if ( null !== $value = $this->$name ?? null ) {
@ -185,9 +178,6 @@ trait EntityTrait {
return $dataset; return $dataset;
} }
/**
* @Ignore
*/
public function toArray($includeRelations = false, array $filterFields = null) : array public function toArray($includeRelations = false, array $filterFields = null) : array
{ {
$dataset = $this->entityGetDataset($includeRelations); $dataset = $this->entityGetDataset($includeRelations);
@ -195,17 +185,11 @@ trait EntityTrait {
return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset; return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset;
} }
/**
* @Ignore
*/
public function toCollection() : EntityCollection public function toCollection() : EntityCollection
{ {
return static::entityCollection([ $this ]); return static::entityCollection([ $this ]);
} }
/**
* @Ignore
*/
public function isLoaded() : bool public function isLoaded() : bool
{ {
if (empty($this->entityLoadedDataset)) { if (empty($this->entityLoadedDataset)) {
@ -221,9 +205,6 @@ trait EntityTrait {
return isset($this->$key); return isset($this->$key);
} }
/**
* @Ignore
*/
public function __get(string $name) public function __get(string $name)
{ {
$relation = new Repository\RelationBuilder($this); $relation = new Repository\RelationBuilder($this);
@ -235,32 +216,25 @@ trait EntityTrait {
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name)); throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
} }
/**
* @Ignore
*/
public function __isset(string $name) : bool public function __isset(string $name) : bool
{ {
#if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) { #if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
# return isset($this->{$relation->key}); # return isset($this->{$relation->key});
#} #}
if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) { $rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ]);
if ( $this->isLoaded() && $rel ) {
return true; return true;
} }
return isset($this->$name); return isset($this->$name);
} }
/**
* @Ignore
*/
public function __sleep() public function __sleep()
{ {
return array_keys($this->resolveEntity()->fieldList()); return array_keys($this->resolveEntity()->fieldList());
} }
/**
* @Ignore
*/
public function __clone() public function __clone()
{ {
foreach($this as $prop) { foreach($this as $prop) {
@ -274,33 +248,21 @@ trait EntityTrait {
} }
} }
/**
* @Ignore
*/
public function jsonSerialize() public function jsonSerialize()
{ {
return $this->entityGetDataset(); return $this->entityGetDataset();
} }
/**
* @Ignore
*/
public static function resolveEntity() : EntityResolver public static function resolveEntity() : EntityResolver
{ {
return Ulmus::resolveEntity(static::class); return Ulmus::resolveEntity(static::class);
} }
/**
* @Ignore
*/
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository
{ {
return Ulmus::repository(static::class, $alias, $adapter); return Ulmus::repository(static::class, $alias, $adapter);
} }
/**
* @Ignore
*/
public static function entityCollection(...$arguments) : EntityCollection public static function entityCollection(...$arguments) : EntityCollection
{ {
$collection = new EntityCollection(...$arguments); $collection = new EntityCollection(...$arguments);
@ -309,26 +271,16 @@ trait EntityTrait {
return $collection; return $collection;
} }
/**
* @Ignore
*/
public static function queryBuilder() : QueryBuilder public static function queryBuilder() : QueryBuilder
{ {
return Ulmus::queryBuilder(static::class); return Ulmus::queryBuilder(static::class);
} }
/**
* @Ignore
*/
public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField
{ {
return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
} }
/**
* @Ignore
*/
public static function fields(array $fields, ? string $alias = Repository::DEFAULT_ALIAS) : string public static function fields(array $fields, ? string $alias = Repository::DEFAULT_ALIAS) : string
{ {
return implode(', ', array_map(function($item) use ($alias){ return implode(', ', array_map(function($item) use ($alias){

View File

@ -4,6 +4,7 @@ namespace Ulmus\Migration;
use Ulmus\Adapter\AdapterInterface; use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation\Property\Field; use Ulmus\Annotation\Property\Field;
use Ulmus\Attribute;
use Ulmus\Entity; use Ulmus\Entity;
class FieldDefinition { class FieldDefinition {
@ -40,7 +41,7 @@ class FieldDefinition {
$this->type = $field->type ?? $data['type']; $this->type = $field->type ?? $data['type'];
$this->length = $field->length ?? null; $this->length = $field->length ?? null;
$this->precision = $field->precision ?? null; $this->precision = $field->precision ?? null;
$this->nullable = isset($field->nullable) ? $field->nullable : $data['nullable']; $this->nullable = $data['nullable'] ?: $field->nullable;
$this->update = $field->attributes['update'] ?? null; $this->update = $field->attributes['update'] ?? null;
} }
@ -69,9 +70,9 @@ class FieldDefinition {
])); ]));
} }
public function getFieldTag() : ? Field public function getFieldTag() : Field|Attribute\Property\Field|null
{ {
$field = array_filter($this->tags, fn($item) => $item['object'] instanceof Field); $field = array_filter($this->tags, fn($item) => $item['object'] instanceof Field || $item['object'] instanceof Attribute\Property\Field);
return array_pop($field)['object']; return array_pop($field)['object'];
} }

View File

@ -2,7 +2,16 @@
namespace Ulmus; namespace Ulmus;
use Ulmus\Annotation\Property\{Field, Where, Having, Relation, Filter, Join, FilterJoin, WithJoin, Relation\Ignore as RelationIgnore}; use Ulmus\Annotation\Property\{Field,
OrderBy,
Where,
Having,
Relation,
Filter,
Join,
FilterJoin,
WithJoin,
Relation\Ignore as RelationIgnore};
use Ulmus\Common\EntityResolver; use Ulmus\Common\EntityResolver;
class Repository class Repository
@ -332,7 +341,7 @@ class Repository
$dataset = $this->generateDatasetDiff($entity, $oldValues); $dataset = $this->generateDatasetDiff($entity, $oldValues);
foreach($dataset as $field => $value) { foreach($dataset as $field => $value) {
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false ) ) { if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Attribute\Property\Field::class, Field::class ], false)->readonly ?? false ) ) {
$intersect[$field] = $field; $intersect[$field] = $field;
} }
} }
@ -367,7 +376,7 @@ class Repository
$prependField and ($prependField .= "$"); $prependField and ($prependField .= "$");
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore)) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
$this->select(sprintf("%s.$key as {$prependField}{$field['name']}", $this->escapeIdentifier($alias))); $this->select(sprintf("%s.$key as {$prependField}{$field['name']}", $this->escapeIdentifier($alias)));
} }
} }
@ -380,7 +389,7 @@ class Repository
$fieldlist = []; $fieldlist = [];
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore)) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
$fieldlist[] = $key; $fieldlist[] = $key;
$fieldlist[] = $entity::field($field['name'], $this->escapeIdentifier($alias)); $fieldlist[] = $entity::field($field['name'], $this->escapeIdentifier($alias));
} }
@ -574,7 +583,7 @@ class Repository
return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value); return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
} }
public function withJoin(/*string|array*/ $fields) : self public function withJoin(string|array $fields) : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) { if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
$this->select("{$this->alias}.*"); $this->select("{$this->alias}.*");
@ -582,10 +591,12 @@ class Repository
# @TODO Apply FILTER annotation to this too ! # @TODO Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) { foreach(array_filter((array) $fields) as $item) {
$annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?: $annotation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Join::class, Join::class ]) ?:
$this->entityResolver->searchFieldAnnotation($item, new Relation); $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class, Relation::class ]);
if (( $annotation instanceof Relation ) && ( $annotation->normalizeType() === 'manytomany' )) { $isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation);
if ($isRelation && ( $annotation->normalizeType() === 'manytomany' )) {
throw new Exception("Many-to-many relation can not be preloaded within joins."); throw new Exception("Many-to-many relation can not be preloaded within joins.");
} }
@ -595,10 +606,10 @@ class Repository
$entity = $annotation->entity ?? $this->entityResolver->properties[$item]['type']; $entity = $annotation->entity ?? $this->entityResolver->properties[$item]['type'];
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], new RelationIgnore) ) { if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ]) ) {
$escAlias = $this->escapeIdentifier($alias); $escAlias = $this->escapeIdentifier($alias);
$name = $entity::resolveEntity()->searchFieldAnnotation($field['name'], new Field())->name ?? $field['name']; $name = $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Field::class, Field::class ])->name ?? $field['name'];
$this->select("$escAlias.$key as $alias\${$name}"); $this->select("$escAlias.$key as $alias\${$name}");
} }
@ -606,17 +617,17 @@ class Repository
$this->open(); $this->open();
foreach($this->entityResolver->searchFieldAnnotationList($item, new Where() ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ] ) as $condition) {
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) { if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); $this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
} }
} }
foreach($this->entityResolver->searchFieldAnnotationList($item, new Having() ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Having::class, Having::class ] ) as $condition) {
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); $this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
} }
foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Filter::class, Filter::class ] ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item, true ]); call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item, true ]);
} }
@ -627,8 +638,7 @@ class Repository
$foreignKey = is_string($annotation->foreignKey) ? $entity::field($annotation->foreignKey, $alias) : $annotation->foreignKey; $foreignKey = is_string($annotation->foreignKey) ? $entity::field($annotation->foreignKey, $alias) : $annotation->foreignKey;
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias) { $this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ]) as $condition) {
foreach($this->entityResolver->searchFieldAnnotationList($item, new Where() ) as $condition) {
if ( ! is_object($condition->field) ) { if ( ! is_object($condition->field) ) {
$field = $this->entityClass::field($condition->field); $field = $this->entityClass::field($condition->field);
} }
@ -644,7 +654,7 @@ class Repository
} }
} }
foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\FilterJoin::class, FilterJoin::class ]) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item, true ]); call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item, true ]);
} }
}); });
@ -670,7 +680,7 @@ class Repository
# Apply FILTER annotation to this too ! # Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) { foreach(array_filter((array) $fields) as $item) {
if ( $relation = $this->entityResolver->searchFieldAnnotation($item, new Relation) ) { if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class, Relation::class ]) ) {
$alias = $relation->alias ?? $item; $alias = $relation->alias ?? $item;
if ( $relation->isManyToMany() ) { if ( $relation->isManyToMany() ) {
@ -691,11 +701,11 @@ class Repository
# $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true); # $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true);
foreach($this->entityResolver->searchFieldAnnotationList($item, new Where() ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ]) as $condition) {
$repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); $repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
} }
foreach($this->entityResolver->searchFieldAnnotationList($item, new Having() ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Having::class, Having::class ] ) as $condition) {
$repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); $repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
} }
@ -722,9 +732,9 @@ class Repository
public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void
{ {
foreach ((array)$fields as $name) { foreach ((array)$fields as $name) {
if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation()))) { if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ))) {
$order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy()); $order = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ]);
$where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where()); $where = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\Where::class, Where::class ]);
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
$baseEntityResolver = $baseEntity::resolveEntity(); $baseEntityResolver = $baseEntity::resolveEntity();
@ -735,7 +745,7 @@ class Repository
$repository = $baseEntity::repository(); $repository = $baseEntity::repository();
foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $baseEntityResolver->searchFieldAnnotation($field['name'], new RelationIgnore)) { if (null === $baseEntityResolver->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
$repository->select($baseEntityResolver->entityClass::field($key)); $repository->select($baseEntityResolver->entityClass::field($key));
} }
} }

View File

@ -2,7 +2,17 @@
namespace Ulmus\Repository; namespace Ulmus\Repository;
use Ulmus\{ Ulmus, Annotation, Query, Common, Common\EntityResolver, Repository, Event, EntityCollection, }; use Ulmus\{Attribute\Property\Field,
Ulmus,
Annotation,
Attribute,
Query,
Common,
Common\EntityResolver,
Repository,
Event,
EntityCollection};
use Ulmus\Annotation\Property\{Filter, OrderBy, Relation, Relation\Ignore as RelationIgnore, Where, WithJoin, }; use Ulmus\Annotation\Property\{Filter, OrderBy, Relation, Relation\Ignore as RelationIgnore, Where, WithJoin, };
use Closure; use Closure;
@ -15,7 +25,7 @@ class RelationBuilder
protected Repository $repository; protected Repository $repository;
protected /*object|string*/ $entity; protected object|string $entity;
protected EntityResolver $resolver; protected EntityResolver $resolver;
@ -27,7 +37,7 @@ class RelationBuilder
protected array $joins; protected array $joins;
public function __construct(/*string|object*/ $entity, ? Repository $repository = null) public function __construct(string|object $entity, ? Repository $repository = null)
{ {
$this->entity = $entity; $this->entity = $entity;
$this->resolver = $entity::resolveEntity(); $this->resolver = $entity::resolveEntity();
@ -48,7 +58,7 @@ class RelationBuilder
return $this->resolveRelation($name) ?: $this->resolveVirtual($name) ?: false; return $this->resolveRelation($name) ?: $this->resolveVirtual($name) ?: false;
} }
else { else {
if ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) { if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class , Relation::class ] ) ) {
return $this->instanciateEmptyObject($name, $relation); return $this->instanciateEmptyObject($name, $relation);
} }
elseif ($virtual = $this->resolveVirtual($name)) { elseif ($virtual = $this->resolveVirtual($name)) {
@ -59,9 +69,9 @@ class RelationBuilder
return false; return false;
} }
protected function resolveVirtual(string $name) /* : bool|object|EntityCollection */ protected function resolveVirtual(string $name) : bool|object
{ {
if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, new Annotation\Property\Virtual()))) { if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Virtual::class, Annotation\Property\Virtual::class ]))) {
if ($virtual->closure ?? false) { if ($virtual->closure ?? false) {
return call_user_func_array($virtual->closure, [ $this->entity ]); return call_user_func_array($virtual->closure, [ $this->entity ]);
} }
@ -72,17 +82,18 @@ class RelationBuilder
return false; return false;
} }
protected function resolveRelation(string $name) /* : bool|object|EntityCollection */ protected function resolveRelation(string $name) : bool|object
{ {
if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) ) { if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ) ) ) {
$this->orders = $this->resolver->searchFieldAnnotationList($name, new OrderBy() ); $this->orders = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ] );
$this->wheres = $this->resolver->searchFieldAnnotationList($name, new Where() ); $this->wheres = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\Where::class, Where::class ] );
$this->filters = $this->resolver->searchFieldAnnotationList($name, new Filter() ); $this->filters = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\Filter::class, Filter::class ] );
$this->joins = $this->resolver->searchFieldAnnotationList($name, new WithJoin() ); $this->joins = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\WithJoin::class, WithJoin::class ] );
switch( true ) { switch( true ) {
case $relation->isOneToOne(): case $relation->isOneToOne():
if ( $relation->hasBridge() ) { if ( $relation->hasBridge() ) {
#dump($name, $relation);
# @TODO ! dump($this->relationAnnotations($name, $relation)); # @TODO ! dump($this->relationAnnotations($name, $relation));
} }
else { else {
@ -155,7 +166,7 @@ class RelationBuilder
} }
} }
protected function instanciateEmptyEntity(string $name, Relation $relation) : object protected function instanciateEmptyEntity(string $name, Relation|Attribute\Property\Relation $relation) : object
{ {
$class = $relation->entity ?? $this->resolver->properties[$name]['type']; $class = $relation->entity ?? $this->resolver->properties[$name]['type'];
@ -163,7 +174,7 @@ class RelationBuilder
} }
protected function instanciateEmptyObject(string $name, Relation $relation) : object protected function instanciateEmptyObject(string $name, Relation|Attribute\Property\Relation $relation) : object
{ {
switch( true ) { switch( true ) {
case $relation->isOneToOne(): case $relation->isOneToOne():
@ -181,16 +192,18 @@ class RelationBuilder
return new $class(); return new $class();
} }
protected function fetchFromDataset($name, ? array $data = null) /* object|bool */ protected function fetchFromDataset($name, ? array $data = null) : object|bool
{ {
$annotation = $this->resolver->searchFieldAnnotation($name, new Annotation\Property\Join) ?: $annotation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Join::class, Annotation\Property\Join::class ]) ?:
$this->resolver->searchFieldAnnotation($name, new Annotation\Property\Relation); $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Annotation\Property\Relation::class ]);
if ( $annotation ) { if ( $annotation ) {
$vars = []; $vars = [];
$len = strlen( $name ) + 1; $len = strlen( $name ) + 1;
if ( ( $annotation instanceof Relation ) && $annotation->isManyToMany() ) { $isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation);
if ( $isRelation && $annotation->isManyToMany() ) {
$entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity; $entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity;
} }
else { else {
@ -231,12 +244,12 @@ class RelationBuilder
return false; return false;
} }
public function oneToOne(string $name, Relation $relation) : Repository public function oneToOne(string $name, Relation|Attribute\Property\Relation $relation) : Repository
{ {
return $this->oneToMany($name, $relation)->limit(1); return $this->oneToMany($name, $relation)->limit(1);
} }
public function oneToMany(string $name, Relation $relation) : Repository public function oneToMany(string $name, Relation|Attribute\Property\Relation $relation) : Repository
{ {
$baseEntity = $relation->entity ?? $this->resolver->properties[$name]['type']; $baseEntity = $relation->entity ?? $this->resolver->properties[$name]['type'];
@ -249,13 +262,17 @@ class RelationBuilder
$field = $relation->key; $field = $relation->key;
if ($relation->foreignKey) { 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 ); # dump($baseEntity::resolveEntity()->searchField($field));
# dump($this->resolver->properties[$name], $field);
$value = ! is_string($field) && is_callable($field) ? $field($this->entity) : $this->entity->$field;
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value );
} }
return $this->applyFilter($this->repository, $name); return $this->applyFilter($this->repository, $name);
} }
public function manyToMany(string $name, Relation $relation, ? Relation & $relationRelation = null, bool $selectBridgeField = true) : Repository public function manyToMany(string $name, Relation|Attribute\Property\Relation $relation, Relation|Attribute\Property\Relation & $relationRelation = null, bool $selectBridgeField = true) : Repository
{ {
extract($this->relationAnnotations($name, $relation)); extract($this->relationAnnotations($name, $relation));
@ -272,7 +289,6 @@ class RelationBuilder
})->where( $this->entity::field($bridgeRelation->foreignKey, $relationAlias), is_string($this->entity) ? $this->entity::field($bridgeRelation->foreignKey) : $this->entity->{$bridgeRelation->foreignKey} ); })->where( $this->entity::field($bridgeRelation->foreignKey, $relationAlias), is_string($this->entity) ? $this->entity::field($bridgeRelation->foreignKey) : $this->entity->{$bridgeRelation->foreignKey} );
$this->applyWhere(); $this->applyWhere();
$this->applyOrderBy(); $this->applyOrderBy();
@ -284,7 +300,7 @@ class RelationBuilder
return $this->applyFilter($this->repository, $name); return $this->applyFilter($this->repository, $name);
} }
public static function relationAnnotations(string $name, Relation $relation) : array public static function relationAnnotations(string $name, Relation|Attribute\Property\Relation $relation) : array
{ {
if ( $relation->isOneToOne() || $relation->isManyToMany() ) { if ( $relation->isOneToOne() || $relation->isManyToMany() ) {
if ( ! $relation->hasBridge() ) { if ( ! $relation->hasBridge() ) {
@ -292,8 +308,8 @@ class RelationBuilder
} }
$bridgeEntity = Ulmus::resolveEntity($relation->bridge); $bridgeEntity = Ulmus::resolveEntity($relation->bridge);
$bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, new Relation() ); $bridgeRelation = $bridgeEntity->searchFieldAnnotation($relation->field, [ Attribute\Property\Relation::class, Relation::class ]);
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, new Relation() ); $relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, [ Attribute\Property\Relation::class, Relation::class ]);
if ($relationRelation === null) { if ($relationRelation === null) {
throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}"); throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}");

View File

@ -4,6 +4,8 @@ namespace Ulmus;
use Generator; use Generator;
use Psr\SimpleCache\CacheInterface;
abstract class Ulmus abstract class Ulmus
{ {
public static string $repositoryClass = "\\Ulmus\\Repository"; public static string $repositoryClass = "\\Ulmus\\Repository";
@ -16,6 +18,8 @@ abstract class Ulmus
public static Entity\ObjectInstanciator $objectInstanciator; public static Entity\ObjectInstanciator $objectInstanciator;
public static CacheInterface $cache;
public static array $resolved = []; public static array $resolved = [];
public static function iterateQueryBuilder(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) : Generator public static function iterateQueryBuilder(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null) : Generator
@ -104,7 +108,7 @@ abstract class Ulmus
public static function resolveEntity(string $entityClass) : Common\EntityResolver public static function resolveEntity(string $entityClass) : Common\EntityResolver
{ {
return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass); return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass, static::$cache);
} }
public static function repository(string $entityClass, string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository public static function repository(string $entityClass, string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository