ulmus/src/EntityTrait.php

296 lines
10 KiB
PHP

<?php
namespace Ulmus;
use Ulmus\{ Repository, Query, Common\EntityResolver, Common\EntityField };
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, };
use Ulmus\Annotation\Property\Field\{ PrimaryKey, Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, Blob, Mediumblob, Longblob, };
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
trait EntityTrait {
use EventTrait;
/**
* @Ignore
*/
protected bool $entityStrictFieldsDeclaration = false;
/**
* @Ignore
*/
protected array $entityDatasetUnmatchedFields = [];
/**
* @Ignore
*/
public array $entityLoadedDataset = [];
public function __construct() {
$this->resetVirtualProperties();
}
/**entityLoadedDataset
* @Ignore
*/
public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self
{
$loaded = $this->isLoaded();
$entityResolver = $this->resolveEntity();
foreach($dataset as $key => $value) {
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null;
$field ??= $entityResolver->field(strtolower($key), EntityResolver::KEY_LC_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'], [ Attribute\Property\Field::class, Field::class ] );
if ( $annotation->length ?? null ) {
$value = mb_substr($value, 0, $annotation->length);
}
}
elseif ( $field['type'] === 'bool' ) {
$value = (bool) $value;
}
$this->{$field['name']} = $value;
}
elseif ( $value instanceof \UnitEnum ) {
$this->{$field['name']} = $value;
}
elseif (enum_exists($field['type'])) {
$this->{$field['name']} = $field['type']::from($value);
}
elseif ( ! $field['builtin'] ) {
try {
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
}
catch(\Error $e) {
$f = $field['type'];
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name']));
}
}
# 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);
}
elseif ($overwriteDataset) {
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset;
}
}
return $this;
}
public function resetVirtualProperties() : self
{
foreach($this->resolveEntity()->properties as $prop => $property) {
if ( empty($property['builtin']) ) {
foreach($property['tags'] as $tag) {
if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) {
unset($this->$prop);
}
}
}
}
return $this;
}
public function fromArray(iterable $dataset) : self
{
return $this->entityFillFromDataset($dataset);
}
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, [ Attribute\Property\Field::class, Field::class ]);
if ( isset($this->$key) ) {
$dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key);
}
elseif ( $field['nullable'] ) {
$dataset[$annotation->name ?? $key] = null;
}
}
# @TODO Must fix recursive bug !
if ($includeRelations) {
foreach($entityResolver->properties as $name => $field){
$relation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Relation::class. Relation::class ] );
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;
}
public function toArray($includeRelations = false, array $filterFields = null) : array
{
$dataset = $this->entityGetDataset($includeRelations);
return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset;
}
public function toCollection() : EntityCollection
{
return static::entityCollection([ $this ]);
}
public function isLoaded() : bool
{
if (empty($this->entityLoadedDataset)) {
return false;
}
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);
}
public function __get(string $name)
{
$relation = new Repository\RelationBuilder($this);
if ( false !== $data = $relation->searchRelation($name) ) {
return $this->$name = $data;
}
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
}
public function __isset(string $name) : bool
{
#if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
# return isset($this->{$relation->key});
#}
$rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ]);
if ( $this->isLoaded() && $rel ) {
return true;
}
return isset($this->$name);
}
public function __sleep()
{
return array_merge(array_keys($this->resolveEntity()->fieldList()), [ 'entityLoadedDataset' ] );
}
public function __wakeup()
{
$this->resetVirtualProperties();
}
public function __clone()
{
foreach($this as $prop) {
}
if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
$key = key($pkField);
unset($this->$key);
}
}
public function jsonSerialize() : mixed
{
return $this->entityGetDataset();
}
public static function resolveEntity() : EntityResolver
{
return Ulmus::resolveEntity(static::class);
}
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository
{
return Ulmus::repository(static::class, $alias, $adapter);
}
public static function entityCollection(...$arguments) : EntityCollection
{
$collection = new EntityCollection(...$arguments);
$collection->entityClass = static::class;
return $collection;
}
public static function queryBuilder() : QueryBuilder
{
return Ulmus::queryBuilder(static::class);
}
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));
}
public static function fields(array $fields, ? string $alias = Repository::DEFAULT_ALIAS) : string
{
return implode(', ', array_map(function($item) use ($alias){
return static::field($item, $alias);
}, $fields));
}
}