373 lines
12 KiB
PHP
373 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Ulmus;
|
|
|
|
use Notes\Attribute\Ignore;
|
|
|
|
use Ulmus\{Attribute\Obj\JsonSerialize,
|
|
Attribute\Property\ResettablePropertyInterface,
|
|
Common\EntityResolver,
|
|
Common\EntityField,
|
|
Entity\DatasetHandler,
|
|
Entity\EntityInterface,
|
|
QueryBuilder\QueryBuilderInterface};
|
|
|
|
use Ulmus\{Attribute\RegisterEntityEventInterface, Entity\EntityValueModifier, };
|
|
|
|
#[JsonSerialize(includeRelations: true)]
|
|
trait EntityTrait {
|
|
use EventTrait;
|
|
|
|
#[Ignore]
|
|
public array $entityLoadedDataset = [];
|
|
|
|
#[Ignore]
|
|
protected bool $entityStrictFieldsDeclaration = false;
|
|
|
|
#[Ignore]
|
|
protected array $entityDatasetUnmatchedFields = [];
|
|
|
|
#[Ignore]
|
|
public DatasetHandler $datasetHandler;
|
|
|
|
#[Ignore]
|
|
public string $loadedFromAdapter;
|
|
|
|
#[Ignore]
|
|
public function __construct(iterable|null $dataset = null)
|
|
{
|
|
$this->initializeEntity($dataset);
|
|
}
|
|
|
|
#[Ignore]
|
|
public function initializeEntity(iterable|null $dataset = null) : void
|
|
{
|
|
# Attributs can attach themselves on events thrown by this trait
|
|
$this->entityRegisterAttributes();
|
|
|
|
$this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration);
|
|
|
|
if ($dataset) {
|
|
$this->fromArray($dataset);
|
|
}
|
|
|
|
$this->resetVirtualProperties();
|
|
}
|
|
|
|
#[Ignore]
|
|
public function entityRegisterAttributes() : void
|
|
{
|
|
foreach($this->resolveEntity()->reflectedClass->getProperties(true) as $field => $property) {
|
|
foreach($property->attributes as $tag) {
|
|
if ( $tag->object instanceof RegisterEntityEventInterface ) {
|
|
$tag->object->attach($this, $field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[Ignore]
|
|
public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self
|
|
{
|
|
$properties = $this->resolveEntity()->reflectedClass->getProperties(true);
|
|
$loaded = $this->isLoaded();
|
|
|
|
$generator = $this->datasetHandler->push($dataset);
|
|
|
|
foreach($generator as $field => $value) {
|
|
foreach($properties[$field]->attributes as $tag) {
|
|
if ( $tag->object instanceof EntityValueModifier ) {
|
|
try {
|
|
$value = $tag->object->push($value, $properties[$field]);
|
|
}
|
|
catch(\Throwable $ex) {
|
|
throw new \LogicException(
|
|
message: sprintf("An error occured trying to push an EntityValueModifier results : %s", $ex->getMessage()),
|
|
code: $ex->getCode(),
|
|
previous: $ex,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->$field = $value;
|
|
}
|
|
|
|
$this->eventExecute(Event\Entity\DatasetFill::class, $this);
|
|
|
|
$this->entityDatasetUnmatchedFields = $generator->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;
|
|
}
|
|
|
|
return iterator_to_array($this->entityYieldDataset($includeRelations, $rewriteValue));
|
|
}
|
|
|
|
#[Ignore]
|
|
protected function entityYieldDataset(bool $includeRelations = false, bool $rewriteValue = true) : \Generator
|
|
{
|
|
foreach($this->datasetHandler->pull($this) as $field => $value) {
|
|
yield $field => $rewriteValue ? static::repository()->adapter->adapter()->writableValue($value) : $value;
|
|
}
|
|
|
|
if ($includeRelations) {
|
|
foreach($this->datasetHandler->pullRelation($this) as $field => $object) {
|
|
yield $field => $object;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[Ignore]
|
|
public function entityGetDiffDataset() : array
|
|
{
|
|
$return = [];
|
|
|
|
foreach($this->datasetHandler->pull($this, false) as $field => $value) {
|
|
$return[$field] = static::repository()->adapter->adapter()->writableValue($value);
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
#[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 = [], 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) : mixed
|
|
{
|
|
$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 ( $rel && $this->isLoaded() ) {
|
|
return true;
|
|
}
|
|
|
|
if (isset($this->$name)) {
|
|
return true;
|
|
}
|
|
|
|
# Unexisting variable
|
|
if (! new \ReflectionClass($this)->hasProperty($name)) {
|
|
return false;
|
|
}
|
|
|
|
# Handling undefined 'nullable'
|
|
return new \ReflectionProperty($this, $name)->isInitialized($this);
|
|
}
|
|
|
|
#[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
|
|
{
|
|
# Allows overridding of this method
|
|
return $this->_jsonSerialize();
|
|
}
|
|
|
|
#[Ignore]
|
|
public function _jsonSerialize() : mixed
|
|
{
|
|
$resolver = static::resolveEntity();
|
|
$objectAttribute = $resolver->getAttributeImplementing(JsonSerialize::class);
|
|
|
|
$dataset = [];
|
|
|
|
foreach($this->entityYieldDataset($objectAttribute->includeRelations ?? true, false, false) as $key => $value) {
|
|
$field = $resolver->searchField($key, EntityResolver::KEY_COLUMN_NAME); # ?: $resolver->relation($key, false);
|
|
|
|
if ($field) {
|
|
$jsonSerialize = $field->getAttribute(\Ulmus\Attribute\Property\JsonSerialize::class);
|
|
|
|
if ($jsonSerialize) {
|
|
# Should we ignore the field ?
|
|
if ($jsonSerialize->object->ignoreField) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_object($value) && ($value instanceof \JsonSerializable) ) {
|
|
$dataset[$key] = $value->jsonSerialize();
|
|
}
|
|
else {
|
|
$dataset[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $dataset;
|
|
}
|
|
|
|
#[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, bool $select = false) : 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(
|
|
name: $name, entityClass: static::class, alias: $alias, entityResolver: Ulmus::resolveEntity(static::class), select: $select
|
|
);
|
|
}
|
|
|
|
#[Ignore]
|
|
public static function fields(array $fields, null|string|false $alias = Repository::DEFAULT_ALIAS, string $separator = ', ', bool $select = false) : string
|
|
{
|
|
return implode($separator, array_map(fn($e) => static::field($e, $alias, $select), $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;
|
|
};
|
|
}
|
|
}
|