ulmus/src/EntityTrait.php

284 lines
8.3 KiB
PHP

<?php
namespace Ulmus;
use Notes\Attribute\Ignore;
use Psr\Http\Message\ServerRequestInterface;
use Ulmus\{Attribute\Property\Join,
Attribute\Property\Relation,
Attribute\Property\ResettablePropertyInterface,
Attribute\Property\Virtual,
Common\EntityResolver,
Common\EntityField,
Entity\DatasetHandler,
Entity\EntityInterface,
QueryBuilder\QueryBuilderInterface};
use Ulmus\SearchRequest\{Attribute\SearchParameter,
SearchMethodEnum,
SearchRequestInterface,
SearchRequestFromRequestTrait,
SearchRequestPaginationTrait};
trait EntityTrait {
use EventTrait;
#[Ignore]
public array $entityLoadedDataset = [];
#[Ignore]
protected bool $entityStrictFieldsDeclaration = false;
#[Ignore]
protected array $entityDatasetUnmatchedFields = [];
#[Ignore]
protected DatasetHandler $datasetHandler;
#[Ignore]
public function __construct(iterable|null $dataset = null)
{
$this->initializeEntity($dataset);
}
#[Ignore]
public function initializeEntity(iterable|null $dataset = null) : void
{
$this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration);
if ($dataset) {
$this->fromArray($dataset);
}
$this->resetVirtualProperties();
}
#[Ignore]
public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self
{
$loaded = $this->isLoaded();
$handler = $this->datasetHandler->push($dataset);
foreach($handler as $field => $value) {
$this->$field = $value;
}
$this->entityDatasetUnmatchedFields = $handler->getReturn();
# Keeping original data to diff on UPDATE query
if ( ! $loaded ) {
$this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER);
}
elseif ($overwriteDataset) {
$this->entityLoadedDataset = array_change_key_case(is_array($dataset) ? $dataset : iterator_to_array($dataset), \CASE_LOWER) + $this->entityLoadedDataset;
}
return $this;
}
#[Ignore]
public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false, bool $rewriteValue = true) : array
{
if ( $returnSource ) {
return $this->entityLoadedDataset;
}
$dataset = [];
foreach($this->datasetHandler->pull($this) as $field => $value) {
$dataset[$field] = $rewriteValue ? static::repository()->adapter->adapter()->writableValue($value) : $value;
}
if ($includeRelations) {
foreach($this->datasetHandler->pullRelation($this) as $field => $object) {
$dataset[$field] = $object;
}
}
return $dataset;
}
#[Ignore]
public function resetVirtualProperties() : self
{
foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) {
foreach($property->attributes as $tag) {
if ( $tag->object instanceof ResettablePropertyInterface ) {
unset($this->$field);
}
}
}
return $this;
}
#[Ignore]
public function fromArray(iterable|EntityInterface $dataset) : static
{
if ($dataset instanceof EntityInterface) {
$dataset = $dataset->toArray();
}
return $this->entityFillFromDataset($dataset);
}
#[Ignore]
public function toArray($includeRelations = false, array $filterFields = null, bool $rewriteValue = true) : array
{
$dataset = $this->entityGetDataset($includeRelations, false, $rewriteValue);
return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset;
}
#[Ignore]
public function toCollection() : EntityCollection
{
return static::entityCollection([ $this ]);
}
#[Ignore]
public function isLoaded() : bool
{
if (empty($this->entityLoadedDataset)) {
return false;
}
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField() ) {
if ( null !== $compoundKeyFields = $this->resolveEntity()->getCompoundKeyFields() ) {
$loaded = false;
foreach ($compoundKeyFields->column as $column) {
$field = $this->resolveEntity()->field($column);
if (! $field->allowsNull() ) {
$loaded |= isset($this->{$field->name});
}
}
return $loaded;
};
}
else {
$key = key($pkField);
return isset($this->$key);
}
throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class));
}
#[Ignore]
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));
}
#[Ignore]
public function __isset(string $name) : bool
{
$rel = static::resolveEntity()->searchFieldAnnotation($name, [ Attribute\Property\Relation::class ]);
if ( $this->isLoaded() && $rel ) {
return true;
}
return isset($this->$name);
}
#[Ignore]
public function __sleep()
{
return array_merge(array_keys($this->resolveEntity()->fieldList()), [ 'entityLoadedDataset' ] );
}
#[Ignore]
public function __wakeup()
{
$this->resetVirtualProperties();
}
#[Ignore]
public function __clone()
{
if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
$key = key($pkField);
unset($this->$key);
}
}
#[Ignore]
public function jsonSerialize() : mixed
{
return $this->entityGetDataset(true, false, false);
}
#[Ignore]
public static function resolveEntity() : EntityResolver
{
return Ulmus::resolveEntity(static::class);
}
#[Ignore]
public static function repository(string $alias = Repository::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) : Repository
{
return Ulmus::repository(static::class, $alias, $adapter);
}
#[Ignore]
public static function entityCollection(...$arguments) : EntityCollection
{
$collection = new EntityCollection(...$arguments);
$collection->entityClass = static::class;
return $collection;
}
#[Ignore]
public static function queryBuilder() : QueryBuilderInterface
{
return Ulmus::queryBuilder(static::class);
}
#[Ignore]
public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField
{
$default = ( $alias === false ? '' : static::repository()::DEFAULT_ALIAS ); # bw compatibility, to be deprecated
$alias = $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default;
return new EntityField(static::class, $name, $alias, Ulmus::resolveEntity(static::class));
}
#[Ignore]
public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : string
{
return implode($separator, array_map(function($item) use ($alias){
return static::field($item, $alias);
}, $fields));
}
#[Ignore]
public static function searchRequest(...$arguments) : SearchRequest\SearchRequestInterface
{
return new /* #[SearchRequest\Attribute\SearchRequestParameter(YourEntityClass::class)] */ class(... $arguments) extends SearchRequest\SearchRequest {
# Define searchable properties here, some ex:
# #[SearchParameter(method: SearchMethodEnum::Where)]
# public ? string $username = null;
# #[SearchParameter(method: SearchMethodEnum::Where, toggle: true)]
# public ? bool $hidden = null;
# #[SearchParameter(method: SearchMethodEnum::Like)]
# public ? string $word = null;
};
}
}