ulmus/src/EntityTrait.php

435 lines
16 KiB
PHP

<?php
namespace Ulmus;
use Ulmus\Repository,
Ulmus\Query,
Ulmus\Common\EntityResolver,
Ulmus\Common\EntityField;
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join, Virtual };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, };
trait EntityTrait {
use EventTrait;
/**
* @Ignore
*/
protected bool $entityStrictFieldsDeclaration = false;
/**
* @Ignore
*/
protected array $entityDatasetUnmatchedFields = [];
/**
* @Ignore
*/
public array $entityLoadedDataset = [];
/**
* @Ignore
*/
public function __get(string $name)
{
$entityResolver = $this->resolveEntity();
# Resolve relations here if one is called
# @TODO REFACTOR THIS CODE ASAP !
if ( $this->isLoaded() ) {
if ( null !== ( $join= $entityResolver->searchFieldAnnotation($name, new Join() ) ) ) {
$vars = [];
$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;
}
}
if ( [] !== $data = (array_values(array_unique($vars)) !== [ null ] ? $vars : []) ) {
return ( new $entity() )->fromArray($data);
}
}
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, is_callable($condition->value) ? $f = call_user_func_array($condition->value, [ $this ]) : $condition->value, $condition->operator);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$field = $relation->key;
}
switch( $relationType ) {
case 'onetoone':
if ($relation->foreignKey) {
$repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), is_callable($field) ? $field($this) : $this->$field );
}
$this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
$result = call_user_func([$repository, $relation->function]);
if ( count($result) === 0 ) {
return $baseEntity;
}
return $this->$name = $result[0];
case 'onetomany':
if ($relation->foreignKey) {
$repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), is_callable($field) ? $field($this) : $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() );
if ($relationRelation === null) {
throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}");
}
$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} );
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->$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;
}
}
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);
}
/**
* @Ignore
*/
public function entityFillFromDataset(iterable $dataset, bool $isLoadedDataset = false) : self
{
$loaded = $this->isLoaded();
$entityResolver = $this->resolveEntity();
foreach($dataset as $key => $value) {
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null;
if ( $field === null ) {
$field = $entityResolver->field($key, EntityResolver::KEY_ENTITY_NAME, false);
}
if ( $field === null ) {
if ($this->entityStrictFieldsDeclaration ) {
throw new \Exception("Field `$key` can not be found within your entity ".static::class);
}
else {
$this->entityDatasetUnmatchedFields[$key] = $value;
}
}
elseif ( is_null($value) ) {
$this->{$field['name']} = null;
}
elseif ( $field['type'] === 'array' ) {
if ( is_string($value)) {
$this->{$field['name']} = substr($value, 0, 1) === "a" ? unserialize($value) : json_decode($value, true);
}
elseif ( is_array($value) ) {
$this->{$field['name']} = $value;
}
}
elseif ( EntityField::isScalarType($field['type']) ) {
if ( $field['type'] === 'string' ) {
$annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
if ( $annotation->length ?? null ) {
$value = substr($value, 0, $annotation->length);
}
}
$this->{$field['name']} = $value;
}
elseif ( ! $field['builtin'] ) {
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
}
# Keeping original data to diff on UPDATE query
if ( ! $loaded || $isLoadedDataset ) {
#if ( $field !== null ) {
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
#}
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
}
}
return $this;
}
public function resetVirtualProperties() : self
{
foreach($this->resolveEntity()->properties as $prop => $property) {
if ( ! $property['builtin'] ) {
foreach($property['tags'] as $tag) {
if ( in_array(strtolower($tag['tag']), [ 'relation', 'join' ] ) ) {
unset($this->$prop);
}
}
}
}
return $this;
}
/**
* @Ignore
*/
public function fromArray(iterable $dataset) : self
{
return $this->entityFillFromDataset($dataset);
}
/**
* @Ignore
*/
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array
{
if ( $returnSource ) {
return $this->entityLoadedDataset;
}
$dataset = [];
$entityResolver = $this->resolveEntity();
foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) {
$annotation = $entityResolver->searchFieldAnnotation($key, new Field() );
if ( isset($this->$key) ) {
$realKey = $annotation->name ?? $key;
switch (true) {
case is_object($this->$key):
$dataset[$realKey] = Ulmus::convertObject($this->$key);
break;
case is_array($this->$key):
$dataset[$realKey] = Ulmus::encodeArray($this->$key);
break;
default:
$dataset[$realKey] = $this->$key;
}
}
elseif ( $field['nullable'] ) {
$dataset[ $annotation->name ?? $key ] = null;
}
}
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(false);
}
$dataset[$name] = $list;
}
elseif ( is_object($value) ) {
$dataset[$name] = $value->entityGetDataset(false);
}
}
}
}
}
return $dataset;
}
/**
* @Ignore
*/
public function toArray($includeRelations = false) : array
{
return $this->entityGetDataset($includeRelations);
}
/**
* @Ignore
*/
public function toCollection() : EntityCollection
{
return static::entityCollection([ $this->toArray() ]);
}
/**
* @Ignore
*/
public function isLoaded() : bool
{
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class));
}
$key = key($pkField);
return isset($this->$key);
}
/**
* @Ignore
*/
public function __sleep()
{
return array_keys($this->resolveEntity()->fieldList());
}
/**
* @Ignore
*/
public function jsonSerialize()
{
return $this->entityGetDataset();
}
/**
* @Ignore
*/
public static function resolveEntity() : EntityResolver
{
return Ulmus::resolveEntity(static::class);
}
/**
* @Ignore
*/
public static function repository(string $alias = Repository::DEFAULT_ALIAS) : Repository
{
return Ulmus::repository(static::class, $alias);
}
/**
* @Ignore
*/
public static function entityCollection(...$arguments) : EntityCollection
{
$collection = new EntityCollection(...$arguments);
$collection->entityClass = static::class;
return $collection;
}
/**
* @Ignore
*/
public static function queryBuilder() : QueryBuilder
{
return Ulmus::queryBuilder(static::class);
}
/**
* @Ignore
*/
public static function field($name, ? string $alias = null) : EntityField
{
return new EntityField(static::class, $name, $alias ?: Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class));
}
/**
* @Ignore
*/
public static function fields(array $fields, ? string $alias = null) : string
{
return implode(', ', array_map(function($item) use ($alias){
return static::field($item, $alias);
}, $fields));
}
}