261 lines
8.0 KiB
PHP
261 lines
8.0 KiB
PHP
<?php
|
|
|
|
namespace Ulmus\Common;
|
|
|
|
use Notes\Common\ReflectedClass;
|
|
use Notes\Common\ReflectedProperty;
|
|
use Psr\SimpleCache\CacheInterface;
|
|
use Ulmus\Attribute\Obj\Index;
|
|
use Ulmus\Ulmus,
|
|
Ulmus\Attribute\Obj\Table,
|
|
Ulmus\Attribute\Obj\AdapterAttributeInterface,
|
|
Ulmus\Attribute\Property\Field,
|
|
Ulmus\Attribute\Property\Relation,
|
|
Ulmus\Attribute\Property\Virtual;
|
|
|
|
use Notes\Common\ReflectedAttribute;
|
|
|
|
use Notes\ObjectReflection;
|
|
|
|
class EntityResolver {
|
|
|
|
const KEY_ENTITY_NAME = 01;
|
|
|
|
const KEY_COLUMN_NAME = 02;
|
|
|
|
const KEY_LC_ENTITY_NAME = 03;
|
|
|
|
public string $entityClass;
|
|
|
|
public ReflectedClass $reflectedClass;
|
|
|
|
protected array $fieldList = [];
|
|
|
|
public function __construct(string $entityClass, ? CacheInterface $cache = null)
|
|
{
|
|
$this->entityClass = $entityClass;
|
|
|
|
$this->reflectedClass = ObjectReflection::fromClass($entityClass, $cache)->reflectClass();
|
|
}
|
|
|
|
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : null|ReflectedProperty
|
|
{
|
|
try{
|
|
return $this->fieldList($fieldKey)[$name] ?? null;
|
|
}
|
|
catch(\Throwable $e) {
|
|
if ( $throwException) {
|
|
throw new \InvalidArgumentException("Can't find entity field's column named `$name` from entity {$this->entityClass}");
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function searchField($name) : null|ReflectedProperty
|
|
{
|
|
return $this->field($name, self::KEY_ENTITY_NAME, false) ?: $this->field($name, self::KEY_COLUMN_NAME, false);
|
|
}
|
|
|
|
public function fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array
|
|
{
|
|
$fieldList = [];
|
|
|
|
foreach($this->reflectedClass->getProperties(true) as $item) {
|
|
foreach($item->getAttributes() as $tag) {
|
|
if ( $tag->object instanceof Field ) {
|
|
if ( $skipVirtual && $tag->object instanceof Virtual ) {
|
|
break;
|
|
}
|
|
|
|
switch($fieldKey) {
|
|
case static::KEY_LC_ENTITY_NAME:
|
|
$key = strtolower($item->name);
|
|
break;
|
|
|
|
case static::KEY_ENTITY_NAME:
|
|
$key = $item->name;
|
|
break;
|
|
|
|
case static::KEY_COLUMN_NAME:
|
|
$key = strtolower($tag->object->name ?? $item->name);
|
|
break;
|
|
|
|
default:
|
|
throw new \InvalidArgumentException("Given `fieldKey` is unknown to the EntityResolver");
|
|
}
|
|
|
|
$fieldList[$key] = $item;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $fieldList;
|
|
}
|
|
|
|
public function relation(string $name) : array
|
|
{
|
|
$property = $this->reflectedClass->getProperties(true)[$name] ?? false;
|
|
|
|
try{
|
|
if ( $property ) {
|
|
foreach($property->getAttributes() as $tag) {
|
|
if ( $tag->object instanceof Relation ) {
|
|
return $property;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
catch(\Throwable $e) {
|
|
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
|
|
}
|
|
}
|
|
|
|
public function getPropertyHavingAttribute(array|object|string $type) : array
|
|
{
|
|
return array_filter(array_map(
|
|
fn($e) => $e->getAttributes(is_object($type) ? $type::class : $type), $this->reflectedClass->getProperties(true)
|
|
));
|
|
}
|
|
|
|
public function getPropertyEntityType(string $name) : false|string
|
|
{
|
|
return $this->reflectedClass->getProperties(true)[$name]->getTypes()[0]->type ?? false;
|
|
}
|
|
|
|
public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
|
|
{
|
|
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
|
|
}
|
|
|
|
public function searchFieldAnnotationList(string $field, array|object|string $attributeType, bool $caseSensitive = true) : false|array
|
|
{
|
|
$list = [];
|
|
|
|
$properties = $this->reflectedClass->getProperties(true);
|
|
|
|
$search = $caseSensitive ? $properties : array_change_key_case($properties, \CASE_LOWER);
|
|
|
|
if ( null !== ( $search[$field] ?? null ) ) {
|
|
return array_map(fn(ReflectedAttribute $e) => $e->object, $search[$field]->getAttributes((array) $attributeType));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function tableName($required = false) : string
|
|
{
|
|
$table = $this->tableAnnotation($required);
|
|
|
|
if ( ( $table->name ?? "" ) === "") {
|
|
if ($required) {
|
|
throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `name` argument for your @Table() annotation");
|
|
}
|
|
}
|
|
|
|
return $table->name ?? "";
|
|
}
|
|
|
|
public function tableAnnotation($required = false) : null|Table
|
|
{
|
|
if ( null === $table = $this->getTableAttribute() ) {
|
|
if ($required) {
|
|
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
|
|
}
|
|
}
|
|
|
|
return $table;
|
|
}
|
|
|
|
public function databaseName() : null|string
|
|
{
|
|
return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->databaseName() ?? null;
|
|
}
|
|
|
|
public function sqlAdapter() : \Ulmus\ConnectionAdapter
|
|
{
|
|
if ( $adapterObj = $this->getAdapterInterfaceAttribute()) {
|
|
if ( false !== $adapterName = $adapterObj->adapter() ) {
|
|
if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$adapterName] ?? null ) ) {
|
|
throw new \Exception("Requested database adapter `$adapterName` is not registered.");
|
|
}
|
|
else {
|
|
return $adapter;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Ulmus::$defaultAdapter;
|
|
}
|
|
|
|
/**
|
|
* Alias of sqlAdapter
|
|
*/
|
|
public function databaseAdapter() : \Ulmus\ConnectionAdapter
|
|
{
|
|
return $this->sqlAdapter();
|
|
}
|
|
|
|
public function schemaName(bool $required = false) : null|string
|
|
{
|
|
if ( null === $table = $this->getTableAttribute() ) {
|
|
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
|
|
}
|
|
|
|
if ( $required && ( ( $table->schema ?? "" ) === "" ) ) {
|
|
throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `schema` argument for your @Table() annotation");
|
|
}
|
|
|
|
return $table->schema ?? null;
|
|
}
|
|
|
|
public function getPrimaryKeyField() : null|array
|
|
{
|
|
foreach($this->fieldList() as $key => $value) {
|
|
$field = $this->searchFieldAnnotation($key, [ Field::class ]);
|
|
if ( null !== $field ) {
|
|
if ( false !== ( $field->attributes['primary_key'] ?? false ) ) {
|
|
return [ $key => $field ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCompoundKeyFields() : ? Index
|
|
{
|
|
return $this->getAttributeImplementing(Index::class);
|
|
}
|
|
|
|
public function getUniqueFields() : ? array
|
|
{
|
|
return null;
|
|
}
|
|
|
|
protected function getAdapterInterfaceAttribute() : null|object
|
|
{
|
|
return $this->getAttributeImplementing(AdapterAttributeInterface::class);
|
|
}
|
|
|
|
protected function getTableAttribute()
|
|
{
|
|
return $this->getAttributeImplementing(Table::class);
|
|
}
|
|
|
|
public function getAttributeImplementing(string $interface) : null|object
|
|
{
|
|
foreach ($this->reflectedClass->getAttributes(true) as $item) {
|
|
if ($item->object instanceof $interface) {
|
|
return $item->object;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|