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)); } }