$entity */ public function __construct( private string $entity ) {} /** * @return EntityResult * @noinspection PhpDocMissingThrowsInspection */ public function analyze(): EntityResult { $refClass = new ReflectionClass($this->entity); $fields = $this->fields($refClass); return new EntityResult( classFqcn: $refClass->getName(), tableName: $this->tableName($refClass), fields: $fields, idField: $this->idField($fields) ); } /** * @param ReflectionClass $refClass * @return string */ private function tableName(ReflectionClass $refClass): string { $attrs = $refClass->getAttributes(Table::class); if (count($attrs) !== 1) { return $refClass->getShortName(); } /** @var string */ return $attrs[0]->getArguments()[0]; } /** * @param ReflectionClass $refClass * @return list */ private function fields(ReflectionClass $refClass): array { $ctor = $refClass->getConstructor(); if (!$ctor) { throw new InvalidEntity( "Entity $this->entity does not declare a constructor. Implement one accepting all of the " . "entitie's values." ); } $params = $ctor->getParameters(); return array_map(fn(ReflectionParameter $param) => $this->fieldFromParameter($param), $params); } /** * @param list $fields * @return EntityField */ private function idField(array $fields): EntityField { $idFields = array_filter($fields, static fn(EntityField $f) => $f->isIdField); $nrIdFields = count($idFields); if ($nrIdFields === 0 || $nrIdFields > 1) { throw new InvalidEntity( "Invalid number of id fields on entity $this->entity found: $nrIdFields, expected: 1" ); } return $idFields[array_key_first($idFields)]; } private function fieldFromParameter(ReflectionParameter $param): EntityField { $attrs = $param->getAttributes(Column::class); if (count($attrs) === 1) { /** @var Column $column */ $column = $attrs[0]->newInstance(); $name = $column->name ?: $param->getName(); } else { $name = $param->getName(); } /** @var ReflectionClass $refClass */ $refClass = $param->getDeclaringClass(); $readAccess = $this->readAccess($refClass, $param); return new EntityField( name: $param->name, type: $this->parameterType($param), columnName: $name, isIdField: count($param->getAttributes(Id::class)) > 0, readAccess: $readAccess ); } private function parameterType(ReflectionParameter $param): PrimitiveType|ClassType|ArrayType { if (!$param->hasType()) { throw new InvalidEntity( "Entity parameter $this->entity::$param->name has no type declaration. Add one." ); } $type = $param->getType(); if (!$type instanceof ReflectionNamedType) { throw new InvalidEntity( "Entity parameter $this->entity::$param->name has an invalid return type. " . "Simple type expected. Unions, Intersections or no type are not simple." ); } // workaround for phpstan /** @var class-string $classType */ $classType = $type->getName(); if ($classType === "array") { $listAttr = $param->getAttributes(ListOf::class); if (empty($listAttr) || count($listAttr) > 1) { throw new InvalidEntity( "Entity parameter $this->entity::$param->name declares its typr as array. " . "Exactly one #[ListOf(...)] attribute expected." ); } /** @var ListOf $listAttrInstance */ $listAttrInstance = $listAttr[0]->newInstance(); return new ArrayType( (new EntityAnalyzer($listAttrInstance->entitiyFqcn))->analyze() ); } return match ($type->getName()) { "string" => PrimitiveType::STRING, "bool" => PrimitiveType::BOOLEAN, "int" => PrimitiveType::INTEGER, "float" => PrimitiveType::FLOAT, default => new ClassType($classType) }; } /** * @param ReflectionClass $refClass * @noinspection PhpDocMissingThrowsInspection */ private function readAccess(ReflectionClass $refClass, ReflectionParameter $param): FieldAccess { $getter = 'get' . ucfirst($param->getName()); $hasPublicProp = $refClass->hasProperty($param->getName()) && $refClass->getProperty($param->getName())->isPublic(); if ($refClass->hasMethod($getter)) { $readAccess = FieldAccess::GETTER; } elseif ($hasPublicProp) { $readAccess = FieldAccess::PUBLIC; } else { throw new InvalidEntity( "Constructor parameter of entity $this->entity '$param->name' has no valid reading method. " . "Implement $getter() or make the field public." ); } return $readAccess; } }