331 lines
10 KiB
PHP
331 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Ulmus\Common;
|
|
|
|
use Ulmus\Ulmus,
|
|
Ulmus\Annotation\Annotation,
|
|
Ulmus\Annotation\Classes\Table,
|
|
Ulmus\Annotation\Property\Field,
|
|
Ulmus\Annotation\Property\Virtual,
|
|
Ulmus\Annotation\Property\Relation;
|
|
|
|
class EntityResolver {
|
|
|
|
const KEY_ENTITY_NAME = 01;
|
|
|
|
const KEY_COLUMN_NAME = 02;
|
|
|
|
const KEY_LC_ENTITY_NAME = 03;
|
|
|
|
public string $entityClass;
|
|
|
|
public array $uses;
|
|
|
|
public array $class;
|
|
|
|
public array $properties;
|
|
|
|
public array $methods;
|
|
|
|
protected array $fieldList = [];
|
|
|
|
public function __construct(string $entityClass)
|
|
{
|
|
$this->entityClass = $entityClass;
|
|
|
|
list($this->uses, $this->class, $this->methods, $this->properties) = array_values(
|
|
ObjectReflection::fromClass($entityClass)->read()
|
|
);
|
|
|
|
$this->resolveAnnotations();
|
|
}
|
|
|
|
public function field($name, $fieldKey = self::KEY_ENTITY_NAME, $throwException = true) : ? array
|
|
{
|
|
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 fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array
|
|
{
|
|
$fieldList = [];
|
|
|
|
foreach($this->properties as $item) {
|
|
foreach($item['tags'] ?? [] 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");
|
|
}
|
|
|
|
if ($escape) {
|
|
if ( isset($tag['object']->name) ) {
|
|
$tag['object']->name = 2;
|
|
}
|
|
|
|
if ( isset($item['name']) ) {
|
|
$item['name'] = 2;
|
|
}
|
|
}
|
|
|
|
$fieldList[$key] = $item;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $fieldList;
|
|
}
|
|
|
|
public function relation(string $name) : ? array
|
|
{
|
|
try{
|
|
if ( null !== ( $this->properties[$name] ?? null ) ) {
|
|
foreach($this->properties[$name]['tags'] ?? [] as $tag) {
|
|
if ( $tag['object'] instanceof Relation ) {
|
|
return $this->properties[$name];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
catch(\Throwable $e) {
|
|
# if ( $throwException) {
|
|
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
|
|
# }
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function searchFieldAnnotation(string $field, Annotation $annotationType) : ? Annotation
|
|
{
|
|
$found = $this->searchFieldAnnotationList($field, $annotationType);
|
|
return $found ? $found[0] : null;
|
|
}
|
|
|
|
public function searchFieldAnnotationList(string $field, Annotation $annotationType) : array
|
|
{
|
|
$list = [];
|
|
|
|
if ( null !== ( $this->properties[$field] ?? null ) ) {
|
|
foreach($this->properties[$field]['tags'] ?? [] as $tag) {
|
|
if ( $tag['object'] instanceof $annotationType ) {
|
|
$list[] = $tag['object'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $list;
|
|
}
|
|
|
|
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) : Table
|
|
{
|
|
if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) {
|
|
if ($required) {
|
|
throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation");
|
|
}
|
|
}
|
|
|
|
return $table;
|
|
}
|
|
|
|
public function databaseName() : ? string
|
|
{
|
|
return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->database ?? null;
|
|
}
|
|
|
|
public function sqlAdapter() : \Ulmus\ConnectionAdapter
|
|
{
|
|
if ( null !== $table = $this->getAnnotationFromClassname( Table::class ) ) {
|
|
if ( $table->adapter ?? null ) {
|
|
if ( null === ( $adapter = \Ulmus\Ulmus::$registeredAdapters[$table->adapter] ?? null ) ) {
|
|
throw new \Exception("Requested database adapter `{$table->adapter}` 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) : ? string
|
|
{
|
|
if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) {
|
|
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() : ? array
|
|
{
|
|
foreach($this->fieldList() as $key => $value) {
|
|
if ( null !== ( $field = $this->searchFieldAnnotation($key, new Field() ) ) ) {
|
|
if ( false !== ( $field->attributes['primary_key'] ?? false ) ) {
|
|
return [ $key => $field ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCompoundKeyFields() : ? array
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function getUniqueFields() : ? array
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Transform an annotation into it's object's counterpart
|
|
*/
|
|
public function getAnnotationFromClassname(string $className) : ? object
|
|
{
|
|
if ( $name = $this->uses[$className] ?? false ) {
|
|
|
|
foreach(array_reverse($this->class['tags']) as $item) {
|
|
if ( $item['tag'] === $name ) {
|
|
return $this->instanciateAnnotationObject($item);
|
|
}
|
|
|
|
foreach($this->properties as $item) {
|
|
foreach(array_reverse($item['tags']) as $item) {
|
|
if ( $item['tag'] === $name ) {
|
|
return $this->instanciateAnnotationObject($item);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach($this->methods as $item) {
|
|
foreach(array_reverse($item['tags']) as $item) {
|
|
if ( $item['tag'] === $name ) {
|
|
return $this->instanciateAnnotationObject($item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new \TypeError("Annotation `$className` could not be found within your object `{$this->entityClass}`");
|
|
}
|
|
else {
|
|
throw new \InvalidArgumentException("Class `$className` was not found within {$this->entityClass} uses statement (or it's children / traits)");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function instanciateAnnotationObject(array $tagDefinition) : Annotation
|
|
{
|
|
$arguments = $this->extractArguments($tagDefinition['arguments']);
|
|
|
|
if ( false === $class = array_search($tagDefinition['tag'], $this->uses) ) {
|
|
throw new \InvalidArgumentException("Annotation class `{$tagDefinition['tag']}` was not found within {$this->entityClass} uses statement (or it's children / traits)");
|
|
}
|
|
|
|
$obj = new $class(... $arguments['constructor']);
|
|
|
|
foreach($arguments['setter'] as $key => $value) {
|
|
$obj->$key = $value;
|
|
}
|
|
|
|
return $obj;
|
|
}
|
|
|
|
/**
|
|
* Extracts arguments from an Annotation definition, easing object's declaration.
|
|
*/
|
|
protected function extractArguments(array $arguments) : array
|
|
{
|
|
$list = [
|
|
'setter' => [],
|
|
'constructor' => [],
|
|
];
|
|
|
|
ksort($arguments);
|
|
|
|
foreach($arguments as $key => $value) {
|
|
$list[ is_int($key) ? 'constructor' : 'setter' ][$key] = $value;
|
|
}
|
|
|
|
return $list;
|
|
}
|
|
|
|
protected function resolveAnnotations()
|
|
{
|
|
foreach($this->class['tags'] as &$tag) {
|
|
$tag['object'] = $this->instanciateAnnotationObject($tag);
|
|
}
|
|
|
|
foreach($this->properties as &$property) {
|
|
foreach($property['tags'] as &$tag){
|
|
$tag['object'] = $this->instanciateAnnotationObject($tag);
|
|
}
|
|
}
|
|
|
|
foreach($this->methods as &$method) {
|
|
foreach($method['tags'] as &$tag){
|
|
$tag['object'] = $this->instanciateAnnotationObject($tag);
|
|
}
|
|
}
|
|
}
|
|
}
|