284 lines
8.3 KiB
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;
|
|
};
|
|
}
|
|
}
|