Prechádzať zdrojové kódy

add PHP 8 attributes, improve PHP <8 support, general cleanup and bugfixes

phpdevcommunity 3 mesiacov pred
rodič
commit
b8636b0008

+ 80 - 6
README.md

@@ -48,8 +48,12 @@ $entityManager = new EntityManager([
 
 ## Basic Usage
 
+> **Note**: The `repository` attribute/method is optional. If none is defined, a dummy repository will be automatically generated.
+
 ### Defining an Entity
 
+#### PHP 7.4 < 8
+
 ```php
 use PaperORM\Entity\EntityInterface;
 use PaperORM\Mapping\{PrimaryKeyColumn, StringColumn, BoolColumn, DateTimeColumn, OneToMany, JoinColumn};
@@ -70,11 +74,11 @@ class User implements EntityInterface
     public static function columnsMapping(): array
     {
         return [
-            new PrimaryKeyColumn('id'),
-            new StringColumn('name'),
-            new StringColumn('email'),
-            new BoolColumn('isActive'),
-            new DateTimeColumn('createdAt')
+            (new PrimaryKeyColumn())->bindProperty('id'),
+            (new StringColumn())->bindProperty('name'),
+            (new StringColumn())->bindProperty('email'),
+            (new BoolColumn('is_active'))->bindProperty('isActive'), // 'is_active' is the column name
+            (new DateTimeColumn('created_at'))->bindProperty('createdAt') // 'created_at' is the column name
         ];
     }
     
@@ -82,6 +86,31 @@ class User implements EntityInterface
 }
 ```
 
+#### PHP 8+ with attributes
+
+```php
+use PaperORM\Entity\EntityInterface;
+use PaperORM\Mapping\{PrimaryKeyColumn, StringColumn, BoolColumn, DateTimeColumn, OneToMany, JoinColumn};
+
+#[Entity(table : 'user', repository : null)]
+class User implements EntityInterface
+{
+    #[PrimaryKeyColumn]
+    private ?int $id = null;
+    #[StringColumn]
+    private string $name;
+    #[StringColumn]
+    private string $email;
+    #[BoolColumn(name: 'is_active')]
+    private bool $isActive = true;
+    #[DateTimeColumn(name: 'created_at')]
+    private \DateTime $createdAt;
+   
+    
+    // Getters/Setters...
+}
+```
+
 ### CRUD Operations
 
 **Fetching Entities:**
@@ -121,6 +150,14 @@ $entityManager->flush();
 // OneToMany relationship
 class Article 
 {
+    // IF PHP >= 8
+    #[\PhpDevCommunity\PaperORM\Mapping\OneToMany(Comment::class, 'article')] 
+    private \PhpDevCommunity\PaperORM\Collection\ObjectStorage $comments;
+    
+    public function __construct() {
+        $this->comments = new ObjectStorage();
+    }
+    
     // ...
     public static function columnsMapping(): array
     {
@@ -149,7 +186,7 @@ $userObject = $repository->find(1)->toObject();
 // Object collection
 $activeUsers = $repository->findBy()
     ->where('isActive', true)
-    ->toCollection();
+    ->toObject();
 ```
 
 > PaperORM offers a simple API while covering the essential needs of a modern ORM.
@@ -225,8 +262,12 @@ $entityManager = new EntityManager([
 
 ## Utilisation de base
 
+> **Note**: L’attribut ou la méthode `repository` est facultatif. Si aucun n’est défini, un repository fictif sera généré automatiquement.
+
 ### Définition d'une entité
 
+#### PHP 7.4 < 8
+
 ```php
 use PaperORM\Entity\EntityInterface;
 use PaperORM\Mapping\{PrimaryKeyColumn, StringColumn, BoolColumn, DateTimeColumn, OneToMany, JoinColumn};
@@ -259,6 +300,31 @@ class User implements EntityInterface
 }
 ```
 
+#### PHP 8+ avec attributs
+
+```php
+use PaperORM\Entity\EntityInterface;
+use PaperORM\Mapping\{PrimaryKeyColumn, StringColumn, BoolColumn, DateTimeColumn, OneToMany, JoinColumn};
+
+#[Entity(table : 'user', repository : null)]
+class User implements EntityInterface
+{
+    #[PrimaryKeyColumn]
+    private ?int $id = null;
+    #[StringColumn]
+    private string $name;
+    #[StringColumn]
+    private string $email;
+    #[BoolColumn(name: 'is_active')]
+    private bool $isActive = true;
+    #[DateTimeColumn(name: 'created_at')]
+    private \DateTime $createdAt;
+   
+    
+    // Getters/Setters...
+}
+```
+
 ### Opérations CRUD
 
 **Récupération d'entités :**
@@ -298,6 +364,14 @@ $entityManager->flush();
 // Relation OneToMany
 class Article 
 {
+    // IF PHP >= 8
+    #[\PhpDevCommunity\PaperORM\Mapping\OneToMany(Comment::class, 'article')] 
+    private \PhpDevCommunity\PaperORM\Collection\ObjectStorage $comments;
+    
+    public function __construct() {
+        $this->comments = new ObjectStorage();
+    }
+    
     // ...
     public static function columnsMapping(): array
     {

+ 1 - 1
src/Collection/ObjectStorage.php

@@ -44,7 +44,7 @@ class ObjectStorage extends SplObjectStorage
      * @param mixed $value The value to search for
      * @return object|null The found object or null if not found
      */
-    public function findOneBy(string $method, $value): ?object
+    public function findOneByMethod(string $method, $value): ?object
     {
         foreach ($this as $object) {
             if (method_exists($object, $method) && $object->$method() === $value) {

+ 0 - 3
src/Entity/EntityInterface.php

@@ -4,8 +4,5 @@ namespace PhpDevCommunity\PaperORM\Entity;
 
 interface EntityInterface
 {
-    static public function getTableName(): string;
-    static public function getRepositoryName(): ?string;
-    static public function columnsMapping(): array;
     public function getPrimaryKeyValue();
 }

+ 10 - 0
src/Entity/TableMetadataInterface.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Entity;
+
+interface TableMetadataInterface
+{
+    static public function getTableName(): string;
+    static public function getRepositoryName(): ?string;
+    static public function columnsMapping(): array;
+}

+ 58 - 3
src/Mapper/ColumnMapper.php

@@ -8,6 +8,7 @@ use PhpDevCommunity\PaperORM\Cache\ColumnCache;
 use PhpDevCommunity\PaperORM\Cache\OneToManyCache;
 use PhpDevCommunity\PaperORM\Cache\PrimaryKeyColumnCache;
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
+use PhpDevCommunity\PaperORM\Entity\TableMetadataInterface;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
@@ -38,7 +39,6 @@ final class ColumnMapper
             }
 
             $primaryKey = $columnsFiltered[0];
-
             $cache->set($class, $primaryKey);
         }
         return $cache->get($class);
@@ -79,7 +79,7 @@ final class ColumnMapper
         if (empty($cache->get($class))) {
             $columnsMapping = [];
             if (is_subclass_of($class, EntityInterface::class)) {
-                $columnsMapping = $class::columnsMapping();
+                $columnsMapping = self::getColumnsMapping($class);
                 $columnsMapping = array_filter($columnsMapping, function ($column) {
                     return $column instanceof OneToMany;
                 });
@@ -128,7 +128,7 @@ final class ColumnMapper
     static private function loadCache(string $class): void
     {
         if (is_subclass_of($class, EntityInterface::class)) {
-            $columnsMapping = $class::columnsMapping();
+            $columnsMapping = self::getColumnsMapping($class);
             $columnsMapping = array_filter($columnsMapping, function ($column) {
                 return $column instanceof Column;
             });
@@ -139,4 +139,59 @@ final class ColumnMapper
 
         ColumnCache::getInstance()->set($class, $columnsMapping);
     }
+
+    static private function getColumnsMapping($class): array
+    {
+        if (is_subclass_of($class, TableMetadataInterface::class)) {
+            return $class::columnsMapping();
+        }
+
+        if (PHP_VERSION_ID >= 80000) {
+            $columns   = [];
+            $refClass  = new \ReflectionClass($class);
+            while ($refClass) {
+                foreach ($refClass->getProperties() as $property) {
+                    if ($property->getDeclaringClass()->getName() !== $refClass->getName()) {
+                        continue;
+                    }
+
+                    foreach ($property->getAttributes(
+                        Column::class,
+                        \ReflectionAttribute::IS_INSTANCEOF
+                    ) as $attr) {
+                        $instance = $attr->newInstance();
+                        if (method_exists($instance, 'bindProperty')) {
+                            $instance->bindProperty($property->getName());
+                        }
+                        $columns[] = $instance;
+                    }
+
+                    foreach ($property->getAttributes(
+                        OneToMany::class,
+                        \ReflectionAttribute::IS_INSTANCEOF
+                    ) as $attr) {
+                        $instance = $attr->newInstance();
+                        if (method_exists($instance, 'bindProperty')) {
+                            $instance->bindProperty($property->getName());
+                        }
+                        $columns[] = $instance;
+                    }
+                }
+
+                $refClass = $refClass->getParentClass();
+            }
+
+            return $columns;
+        }
+
+        if (method_exists($class, 'columnsMapping')) {
+            return $class::columnsMapping();
+        }
+
+        throw new \LogicException(sprintf(
+            'Entity %s must define columns via interface, attribute or static method : ::columnsMapping() or implement %s',
+            is_object($class) ? get_class($class) : $class, TableMetadataInterface::class
+        ));
+
+    }
 }

+ 54 - 2
src/Mapper/EntityMapper.php

@@ -4,6 +4,8 @@ namespace PhpDevCommunity\PaperORM\Mapper;
 
 
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
+use PhpDevCommunity\PaperORM\Entity\TableMetadataInterface;
+use PhpDevCommunity\PaperORM\Mapping\Entity;
 
 final class EntityMapper
 {
@@ -12,7 +14,26 @@ final class EntityMapper
         if (!is_subclass_of($class, EntityInterface::class)) {
             throw new \LogicException(sprintf('%s must implement %s', $class, EntityInterface::class));
         }
-        return $class::getTableName();
+
+        if (is_subclass_of($class, TableMetadataInterface::class)) {
+            return $class::getTableName();
+        }
+
+        if (PHP_VERSION_ID >= 80000) {
+            $entity = self::getEntityPHP8($class);
+            if ($entity instanceof Entity) {
+                return $entity->getTable();
+            }
+        }
+
+        if (method_exists($class, 'getTableName')) {
+            return $class::getTableName();
+        }
+
+        throw new \LogicException(sprintf(
+            'Entity %s must define a entityName via interface, attribute or static method',
+            is_object($class) ? get_class($class) : $class
+        ));
     }
 
     static public function getRepositoryName($class): ?string
@@ -20,6 +41,37 @@ final class EntityMapper
         if (!is_subclass_of($class, EntityInterface::class)) {
             throw new \LogicException(sprintf('%s must implement %s', $class, EntityInterface::class));
         }
-        return $class::getRepositoryName();
+
+        if (is_subclass_of($class, TableMetadataInterface::class)) {
+            return $class::getRepositoryName();
+        }
+
+        if (PHP_VERSION_ID >= 80000) {
+            $entity = self::getEntityPHP8($class);
+            if ($entity instanceof Entity) {
+                return $entity->getRepositoryClass();
+            }
+        }
+
+        if (method_exists($class, 'getRepositoryName')) {
+            return $class::getRepositoryName();
+        }
+
+        throw new \LogicException(sprintf(
+            'Entity %s must define a repository via interface, attribute or static method',
+            is_object($class) ? get_class($class) : $class
+        ));
+    }
+
+    static public function getEntityPHP8($class): ?Entity
+    {
+        $reflector = new \ReflectionClass($class);
+        $attributes = $reflector->getAttributes(Entity::class);
+        if (!empty($attributes)) {
+            /** @var \PhpDevCommunity\PaperORM\Mapping\Entity $instance */
+            return $attributes[0]->newInstance();
+        }
+
+        return null;
     }
 }

+ 2 - 3
src/Mapping/Column/BoolColumn.php

@@ -5,16 +5,15 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use Attribute;
 use PhpDevCommunity\PaperORM\Types\BoolType;
 
-#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class BoolColumn extends Column
 {
     public function __construct(
-        string  $property,
         ?string $name = null,
         bool    $nullable = false,
         ?bool $defaultValue = null
     )
     {
-        parent::__construct($property, $name, BoolType::class, $nullable, $defaultValue);
+        parent::__construct('', $name, BoolType::class, $nullable, $defaultValue);
     }
 }

+ 18 - 5
src/Mapping/Column/Column.php

@@ -30,6 +30,10 @@ abstract class Column
          ?string $secondArgument = null
      )
     {
+
+        if (empty($property) && !empty($name)) {
+            $property = $name;
+        }
         $this->property = $property;
         $this->name = $name;
         $this->type = $type;
@@ -38,10 +42,6 @@ abstract class Column
         $this->nullable = $nullable;
         $this->firstArgument = $firstArgument;
         $this->secondArgument = $secondArgument;
-
-        if ($this instanceof JoinColumn || $unique === true) {
-            $this->index = new Index([$this->getName()], $unique);
-        }
     }
 
     final public function __toString(): string
@@ -49,14 +49,24 @@ abstract class Column
         return $this->getProperty();
     }
 
+    public function bindProperty(string $propertyName): self
+    {
+        $this->property = $propertyName;
+        return $this;
+    }
+
     public function getProperty(): string
     {
+        if (empty($this->property)) {
+            throw  new \LogicException('Property must be set, use bindProperty');
+        }
         return $this->property;
     }
 
     final public function getName(): ?string
     {
-        return $this->name ?: $this->getProperty();
+        $property = $this->getProperty();
+        return $this->name ?: $property;
     }
 
 
@@ -103,6 +113,9 @@ abstract class Column
 
     public function getIndex(): ?Index
     {
+        if ($this->index === null && ($this instanceof JoinColumn || $this->isUnique())) {
+            $this->index = new Index([$this->getName()], $this->isUnique());
+        }
         return $this->index;
     }
 

+ 2 - 3
src/Mapping/Column/DateColumn.php

@@ -4,16 +4,15 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 
 use PhpDevCommunity\PaperORM\Types\DateType;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class DateColumn extends Column
 {
 
     public function __construct(
-        string $property,
         string $name = null,
         bool $nullable = false
     )
     {
-        parent::__construct($property, $name, DateType::class, $nullable);
+        parent::__construct('', $name, DateType::class, $nullable);
     }
 }

+ 2 - 3
src/Mapping/Column/DateTimeColumn.php

@@ -5,16 +5,15 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use Attribute;
 use PhpDevCommunity\PaperORM\Types\DateTimeType;
 
-#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class DateTimeColumn extends Column
 {
 
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false
     )
     {
-        parent::__construct($property, $name, DateTimeType::class, $nullable);
+        parent::__construct('', $name, DateTimeType::class, $nullable);
     }
 }

+ 2 - 3
src/Mapping/Column/DecimalColumn.php

@@ -5,12 +5,11 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use Attribute;
 use PhpDevCommunity\PaperORM\Types\DecimalType;
 
-#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class DecimalColumn extends Column
 {
 
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false,
         string $defaultValue = null,
@@ -19,7 +18,7 @@ final class DecimalColumn extends Column
         bool   $unique = false
     )
     {
-        parent::__construct($property, $name, DecimalType::class, $nullable, $defaultValue, $unique, $precision, $scale);
+        parent::__construct('', $name, DecimalType::class, $nullable, $defaultValue, $unique, $precision, $scale);
     }
 
     public function getPrecision(): ?int

+ 2 - 3
src/Mapping/Column/FloatColumn.php

@@ -4,18 +4,17 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 
 use PhpDevCommunity\PaperORM\Types\FloatType;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class FloatColumn extends Column
 {
 
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false,
         ?float $defaultValue = null,
         bool   $unique = false
     )
     {
-        parent::__construct($property, $name, FloatType::class, $nullable, $defaultValue, $unique);
+        parent::__construct('', $name, FloatType::class, $nullable, $defaultValue, $unique);
     }
 }

+ 2 - 3
src/Mapping/Column/IntColumn.php

@@ -4,18 +4,17 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 
 use PhpDevCommunity\PaperORM\Types\IntType;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class IntColumn extends Column
 {
 
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false,
         ?int $defaultValue = null,
         bool   $unique = false
     )
     {
-        parent::__construct($property, $name, IntType::class, $nullable, $defaultValue, $unique);
+        parent::__construct('', $name, IntType::class, $nullable, $defaultValue, $unique);
     }
 }

+ 2 - 3
src/Mapping/Column/JoinColumn.php

@@ -5,7 +5,7 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use PhpDevCommunity\PaperORM\Types\IntegerType;
 use ReflectionClass;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class JoinColumn extends Column
 {
     /**
@@ -18,7 +18,6 @@ final class JoinColumn extends Column
     private string $targetEntity;
 
     final public function __construct(
-        string  $property,
         string  $name,
         string  $referencedColumnName,
         string  $targetEntity,
@@ -26,7 +25,7 @@ final class JoinColumn extends Column
         bool   $unique = false
     )
     {
-        parent::__construct($property, $name, IntegerType::class, $nullable, null, $unique);
+        parent::__construct('', $name, IntegerType::class, $nullable, null, $unique);
         $this->referencedColumnName = $referencedColumnName;
         $this->targetEntity = $targetEntity;
     }

+ 2 - 3
src/Mapping/Column/JsonColumn.php

@@ -4,16 +4,15 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 
 use PhpDevCommunity\PaperORM\Types\JsonType;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class JsonColumn extends Column
 {
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false,
         array $defaultValue = null
     )
     {
-        parent::__construct($property, $name, JsonType::class, $nullable, $defaultValue);
+        parent::__construct('', $name, JsonType::class, $nullable, $defaultValue);
     }
 }

+ 3 - 3
src/Mapping/Column/PrimaryKeyColumn.php

@@ -4,11 +4,11 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 
 use PhpDevCommunity\PaperORM\Types\IntegerType;
 
-#[\Attribute(\Attribute::TARGET_CLASS)]
+#[\Attribute(\Attribute::TARGET_PROPERTY)]
 final class PrimaryKeyColumn extends Column
 {
-    public function __construct(string $property, string $name = null, string $type = IntegerType::class)
+    public function __construct(string $name = null, string $type = IntegerType::class)
     {
-        parent::__construct($property, $name, $type);
+        parent::__construct('', $name, $type);
     }
 }

+ 2 - 3
src/Mapping/Column/StringColumn.php

@@ -5,11 +5,10 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use Attribute;
 use PhpDevCommunity\PaperORM\Types\StringType;
 
-#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class StringColumn extends Column
 {
     public function __construct(
-        string $property,
         string $name = null,
         int $length = 255,
         bool   $nullable = false,
@@ -17,7 +16,7 @@ final class StringColumn extends Column
         bool $unique = false
     )
     {
-        parent::__construct($property, $name, StringType::class, $nullable, $defaultValue, $unique, $length);
+        parent::__construct('', $name, StringType::class, $nullable, $defaultValue, $unique, $length);
     }
 
     public function getLength(): int

+ 2 - 3
src/Mapping/Column/TextColumn.php

@@ -5,16 +5,15 @@ namespace PhpDevCommunity\PaperORM\Mapping\Column;
 use Attribute;
 use PhpDevCommunity\PaperORM\Types\StringType;
 
-#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class TextColumn extends Column
 {
     public function __construct(
-        string $property,
         string $name = null,
         bool   $nullable = false,
         string $defaultValue = null
     )
     {
-        parent::__construct($property, $name, StringType::class, $nullable, $defaultValue);
+        parent::__construct('', $name, StringType::class, $nullable, $defaultValue);
     }
 }

+ 4 - 4
src/Mapping/Entity.php

@@ -6,12 +6,12 @@ namespace PhpDevCommunity\PaperORM\Mapping;
 final class Entity
 {
     private string $table;
-    private string $repositoryClass;
+    private ?string $repositoryClass = null;
 
-    public function __construct( string $table, string $repositoryClass)
+    public function __construct( string $table, ?string $repository = null)
     {
         $this->table = $table;
-        $this->repositoryClass = $repositoryClass;
+        $this->repositoryClass = $repository;
     }
 
     public function getTable(): string
@@ -19,7 +19,7 @@ final class Entity
         return $this->table;
     }
 
-    public function getRepositoryClass(): string
+    public function getRepositoryClass(): ?string
     {
         return $this->repositoryClass;
     }

+ 19 - 6
src/Mapping/OneToMany.php

@@ -2,19 +2,21 @@
 
 namespace PhpDevCommunity\PaperORM\Mapping;
 
+use Attribute;
+use LogicException;
 use PhpDevCommunity\PaperORM\Collection\ObjectStorage;
 
-#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
 final class OneToMany
 {
-    private string $property;
+    private ?string $property = null;
     private string $targetEntity;
     private ?string $mappedBy;
     private array $criteria;
     private ObjectStorage $storage;
-    final public function __construct(string $property, string $targetEntity, string $mappedBy = null, array $criteria = [])
+
+    final public function __construct(string $targetEntity, string $mappedBy = null, array $criteria = [])
     {
-        $this->property = $property;
         $this->targetEntity = $targetEntity;
         $this->mappedBy = $mappedBy;
         $this->criteria = $criteria;
@@ -37,21 +39,32 @@ final class OneToMany
         return $this->criteria;
     }
 
-    public function getDefaultValue():ObjectStorage
+    public function getDefaultValue(): ObjectStorage
     {
         return clone $this->storage;
     }
+
     public function getType(): string
     {
-        return '\\'.ltrim(get_class($this->getDefaultValue()), '\\');
+        return '\\' . ltrim(get_class($this->getDefaultValue()), '\\');
     }
 
     public function getName(): string
     {
         return $this->getProperty();
     }
+
     public function getProperty(): string
     {
+        if (empty($this->property)) {
+            throw  new \LogicException('Property must be set, use bindProperty');
+        }
         return $this->property;
     }
+
+    public function bindProperty(string $propertyName): self
+    {
+        $this->property = $propertyName;
+        return $this;
+    }
 }

+ 2 - 1
src/Metadata/ColumnMetadata.php

@@ -3,6 +3,7 @@
 namespace PhpDevCommunity\PaperORM\Metadata;
 
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
+use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
@@ -96,7 +97,7 @@ class ColumnMetadata
             $targetEntity = $column->getTargetEntity();
             if (is_subclass_of($targetEntity, EntityInterface::class)) {
                 $foreignKeyMetadata = [
-                    'referencedTable' => $targetEntity::getTableName(),
+                    'referencedTable' => EntityMapper::getTable($targetEntity),
                     'referencedColumn' => $column->getReferencedColumnName(),
                 ];
             }

+ 4 - 3
src/Migration/PaperMigration.php

@@ -8,6 +8,7 @@ use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
 use PhpDevCommunity\PaperORM\Generator\SchemaDiffGenerator;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
+use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
 use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\PaperConnection;
@@ -112,7 +113,7 @@ SQL;
         $tables = [];
         foreach ($entities as $entity) {
             if (is_subclass_of($entity, EntityInterface::class)) {
-                $tableName = $entity::getTableName();
+                $tableName = EntityMapper::getTable($entity);
                 $tables[$tableName] = [
                     'columns' => ColumnMapper::getColumns($entity),
                     'indexes' => [] // TODO IndexMapper::getIndexes($entity)
@@ -191,8 +192,8 @@ SQL;
     private function createVersion(): void
     {
         $this->platform->createTableIfNotExists($this->tableName, [
-            new StringColumn('version', 'version', 50),
-            new DateTimeColumn('created_at', 'created_at')
+            (new StringColumn( null, 50))->bindProperty('version'),
+            (new DateTimeColumn(null, 'created_at'))->bindProperty('created_at'),
         ]);
     }
 

+ 1 - 1
src/Platform/AbstractPlatform.php

@@ -111,7 +111,7 @@ abstract class AbstractPlatform implements PlatformInterface
         $indexesExisting = [];
         foreach ($indexes as $index) {
             $indexMetadata = new IndexMetadata($tableName, $index->getName() ?: $this->generateIndexName($tableName, $index->getColumns()), $index->getColumns(), $index->isUnique());
-            $indexFound = $indexesFromTable->findOneBy('getName', $indexMetadata->getName());
+            $indexFound = $indexesFromTable->findOneByMethod('getName', $indexMetadata->getName());
             if ($indexFound) {
                 if ($indexMetadata->toArray() != $indexFound->toArray()) {
                     $indexesToUpdate[] = $indexMetadata;

+ 2 - 2
src/Repository/Repository.php

@@ -58,9 +58,9 @@ abstract class Repository
         return (new Fetcher($this->qb(), true))->where(...$expressions);
     }
 
-    public function where(Expr ...$expressions): Fetcher
+    public function findOneBy(array $arguments = []): Fetcher
     {
-        return (new Fetcher($this->qb(), true))->where(...$expressions);
+        return $this->findBy($arguments)->first();
     }
 
     public function insert(object $entityToInsert): int

+ 2 - 2
tests/Common/ObjectStorageTest.php

@@ -180,10 +180,10 @@ class ObjectStorageTest extends TestCase
         $user->setFirstname('John');
         $objectStorage = new ObjectStorage();
         $objectStorage->attach($user);
-        $foundObject = $objectStorage->findOneBy('getFirstname', 'John');
+        $foundObject = $objectStorage->findOneByMethod('getFirstname', 'John');
         $this->assertStrictEquals($user, $foundObject);
 
-        $foundObject = $objectStorage->findOneBy('getNonExistentMethod', 'John');
+        $foundObject = $objectStorage->findOneByMethod('getNonExistentMethod', 'John');
         $this->assertNull($foundObject);
     }
 

+ 1 - 1
tests/DatabaseShowTablesCommandTest.php

@@ -49,7 +49,7 @@ class DatabaseShowTablesCommandTest extends TestCase
 
         $platform->createTable('post', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('user', 'user_id', 'id', UserTest::class),
+            new JoinColumn('user_id', 'id', UserTest::class),
             new StringColumn('title'),
             new StringColumn('content'),
         ]);

+ 7 - 4
tests/Entity/CommentTest.php

@@ -3,12 +3,15 @@
 namespace Test\PhpDevCommunity\PaperORM\Entity;
 
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
+use PhpDevCommunity\PaperORM\Entity\TableMetadataInterface;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Mapping\Entity;
 use Test\PhpDevCommunity\PaperORM\Repository\TagTestRepository;
 
-class CommentTest implements EntityInterface
+#[Entity(table : 'comment', repository : null)]
+class CommentTest implements EntityInterface, TableMetadataInterface
 {
 
     private ?int $id = null;
@@ -28,9 +31,9 @@ class CommentTest implements EntityInterface
     static public function columnsMapping(): array
     {
         return [
-            new PrimaryKeyColumn('id'),
-            new StringColumn('body'),
-            new JoinColumn('post', 'post_id', 'id', PostTest::class),
+            (new PrimaryKeyColumn())->bindProperty('id'),
+            (new StringColumn())->bindProperty('body'),
+            (new JoinColumn('post_id', 'id', PostTest::class))->bindProperty('post'),
         ];
     }
 

+ 19 - 7
tests/Entity/PostTest.php

@@ -9,23 +9,32 @@ use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Mapping\Entity;
 use PhpDevCommunity\PaperORM\Mapping\OneToMany;
 use Test\PhpDevCommunity\PaperORM\Repository\PostTestRepository;
 
+#[Entity(table : 'post', repository : PostTestRepository::class)]
 class PostTest implements EntityInterface
 {
 
+    #[PrimaryKeyColumn]
     private ?int $id = null;
 
+    #[StringColumn]
     private ?string $title = null;
 
+    #[StringColumn]
     private ?string $content = null;
 
+    #[DateTimeColumn(name: 'created_at')]
     private ?DateTime $createdAt = null;
 
+    #[JoinColumn(name: 'user_id', referencedColumnName: 'id', targetEntity:  UserTest::class)]
     private ?UserTest $user = null;
 
+    #[OneToMany(targetEntity: TagTest::class, mappedBy: 'post')]
     private ObjectStorage $tags;
+    #[OneToMany(targetEntity: CommentTest::class, mappedBy: 'post')]
     private ObjectStorage $comments;
 
     public function __construct()
@@ -46,14 +55,17 @@ class PostTest implements EntityInterface
 
     static public function columnsMapping(): array
     {
+        if (PHP_VERSION_ID > 80000) {
+            return [];
+        }
         return [
-            new PrimaryKeyColumn('id'),
-            new StringColumn('title'),
-            new StringColumn('content'),
-            new DateTimeColumn('createdAt', 'created_at'),
-            new JoinColumn('user', 'user_id', 'id', UserTest::class),
-            new OneToMany('tags', TagTest::class, 'post'),
-            new OneToMany('comments', CommentTest::class, 'post'),
+            (new PrimaryKeyColumn())->bindProperty('id'),
+            (new StringColumn())->bindProperty('title'),
+            (new StringColumn())->bindProperty('content'),
+            (new DateTimeColumn( 'created_at'))->bindProperty('createdAt'),
+            (new JoinColumn('user_id', 'id', UserTest::class))->bindProperty('user'),
+            (new OneToMany( TagTest::class, 'post'))->bindProperty('tags'),
+            (new OneToMany( CommentTest::class, 'post'))->bindProperty('comments'),
         ];
     }
 

+ 12 - 4
tests/Entity/TagTest.php

@@ -7,14 +7,19 @@ use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Mapping\Entity;
 use Test\PhpDevCommunity\PaperORM\Repository\PostTestRepository;
 use Test\PhpDevCommunity\PaperORM\Repository\TagTestRepository;
 
+#[Entity(table : 'tag', repository : TagTestRepository::class)]
 class TagTest implements EntityInterface
 {
-
+    #[PrimaryKeyColumn]
     private ?int $id = null;
+    #[StringColumn]
     private ?string $name = null;
+
+    #[JoinColumn(name : 'post_id', referencedColumnName : 'id', targetEntity : PostTest::class)]
     private ?PostTest $post = null;
 
     static public function getTableName(): string
@@ -29,10 +34,13 @@ class TagTest implements EntityInterface
 
     static public function columnsMapping(): array
     {
+        if (PHP_VERSION_ID > 80000) {
+            return [];
+        }
         return [
-            new PrimaryKeyColumn('id'),
-            new StringColumn('name'),
-            new JoinColumn('post', 'post_id', 'id', PostTest::class),
+            (new PrimaryKeyColumn())->bindProperty('id'),
+            (new StringColumn())->bindProperty('name'),
+            (new JoinColumn( 'post_id', 'id', PostTest::class))->bindProperty('post'),
         ];
     }
 

+ 25 - 9
tests/Entity/UserTest.php

@@ -9,25 +9,38 @@ use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Mapping\Entity;
 use PhpDevCommunity\PaperORM\Mapping\OneToMany;
+use Test\PhpDevCommunity\PaperORM\Repository\PostTestRepository;
 
+#[Entity(table : 'user', repository : null)]
 class UserTest implements EntityInterface
 {
+    #[PrimaryKeyColumn]
     private ?int $id = null;
 
+    #[StringColumn]
     private ?string $firstname = null;
 
+    #[StringColumn]
     private ?string $lastname    = null;
 
+    #[StringColumn]
     private ?string $email = null;
 
+    #[StringColumn]
     private ?string $password = null;
 
+    #[BoolColumn(name: 'is_active')]
     private bool $active = false;
 
+    #[DateTimeColumn(name: 'created_at')]
     private ?\DateTime $createdAt = null;
 
+    #[OneToMany(targetEntity: PostTest::class, mappedBy: 'user')]
     private ObjectStorage $posts;
+
+    #[JoinColumn(name: 'last_post_id', referencedColumnName: 'id', targetEntity: PostTest::class)]
     private ?PostTest $lastPost = null;
     public function __construct()
     {
@@ -47,16 +60,19 @@ class UserTest implements EntityInterface
 
     static public function columnsMapping(): array
     {
+        if (PHP_VERSION_ID > 80000) {
+            return [];
+        }
         return [
-            new PrimaryKeyColumn('id'),
-            new StringColumn('firstname'),
-            new StringColumn('lastname'),
-            new StringColumn('email'),
-            new StringColumn('password'),
-            new BoolColumn('active', 'is_active'),
-            new DateTimeColumn('createdAt', 'created_at'),
-            new OneToMany('posts', PostTest::class,  'user'),
-            new JoinColumn('lastPost', 'last_post_id', 'id', PostTest::class),
+            (new PrimaryKeyColumn())->bindProperty('id'),
+            (new StringColumn())->bindProperty('firstname'),
+            (new StringColumn())->bindProperty('lastname'),
+            (new StringColumn())->bindProperty('email'),
+            (new StringColumn())->bindProperty('password'),
+            (new BoolColumn( 'is_active'))->bindProperty('active'),
+            (new DateTimeColumn( 'created_at'))->bindProperty('createdAt'),
+            (new OneToMany( PostTest::class,  'user'))->bindProperty('posts'),
+            (new JoinColumn( 'last_post_id', 'id', PostTest::class))->bindProperty('lastPost'),
         ];
     }
 

+ 6 - 6
tests/Helper/DataBaseHelperTest.php

@@ -24,35 +24,35 @@ class DataBaseHelperTest
         $platform = $entityManager->createDatabasePlatform();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('post', 'last_post_id', 'id', PostTest::class, true),
+            (new JoinColumn( 'last_post_id', 'id', PostTest::class, true)),
             new StringColumn('firstname'),
             new StringColumn('lastname'),
             new StringColumn('email'),
             new StringColumn('password'),
             new BoolColumn('is_active'),
-            new DateTimeColumn('created_at', 'created_at', true),
+            new DateTimeColumn('created_at', true),
         ]);
 
         $platform->createTable('post', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('user', 'user_id', 'id', UserTest::class, false),
+            ( new JoinColumn( 'user_id', 'id', UserTest::class, false)),
             new StringColumn('title'),
             new StringColumn('content'),
-            new DateTimeColumn('created_at', 'created_at', true),
+            new DateTimeColumn('created_at', true),
         ]);
 
         $platform->createIndex(new IndexMetadata('post', 'idx_post_user_id', ['user_id']));
 
         $platform->createTable('tag', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('post', 'post_id', 'id', PostTest::class),
+            (new JoinColumn('post_id', 'id', PostTest::class)),
             new StringColumn('name'),
         ]);
 
 
         $platform->createTable('comment', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('post', 'post_id', 'id', PostTest::class),
+            (new JoinColumn('post_id', 'id', PostTest::class)),
             new StringColumn('body'),
         ]);
 

+ 3 - 3
tests/MigrationTest.php

@@ -78,8 +78,8 @@ class MigrationTest extends TestCase
     private function testColumnModification(): void
     {
         $userColumns = ColumnMapper::getColumns(UserTest::class);
-        $userColumns[3] = new StringColumn('email', 'email', 255, true, null, true);
-        $userColumns[] = new IntColumn('childs', 'childs', false, 0);
+        $userColumns[3] = (new StringColumn( null, 255, true, null, true))->bindProperty('email');
+        $userColumns[] = (new IntColumn( null, false, 0))->bindProperty('childs');
         $migrationFile = $this->paperMigration->diff([
             'user' => [
                 'columns' => $userColumns,
@@ -98,7 +98,7 @@ class MigrationTest extends TestCase
     private function testFailedMigration(): void
     {
         $userColumns = ColumnMapper::getColumns(UserTest::class);
-        $userColumns[3] = new StringColumn('email', 'email', 100, true, null, true);
+        $userColumns[3] = (new StringColumn( null, 100, true, null, true))->bindProperty('email');
         $this->paperMigration->diff([
             'user' => [
                 'columns' => $userColumns,

+ 3 - 4
tests/PlatformDiffTest.php

@@ -37,7 +37,7 @@ class PlatformDiffTest extends TestCase
             new StringColumn('lastname'),
             new StringColumn('email'),
             new StringColumn('password'),
-            new BoolColumn('active', 'is_active'),
+            (new BoolColumn('is_active'))->bindProperty('active'),
         ];
         $platform->createTable('user', $columns);
 
@@ -46,8 +46,7 @@ class PlatformDiffTest extends TestCase
         $this->assertEmpty($diff->getColumnsToAdd());
         $this->assertEmpty($diff->getColumnsToUpdate());
         $this->assertEmpty($diff->getColumnsToDelete());
-//
-//
+
         $columns[3] = new StringColumn('username');
 
         $diff = $platform->diff('user', $columns, [] );
@@ -59,7 +58,7 @@ class PlatformDiffTest extends TestCase
         $platform->dropTable('user');
         $platform->createTable('user', $columns, [] );
 
-        $columns[3] = new StringColumn('username', 'username', 100);
+        $columns[3] = new StringColumn( 'username', 100);
         $diff = $platform->diff('user', $columns, [] );
 
         $this->assertTrue(count($diff->getColumnsToUpdate()) == 1);

+ 1 - 1
tests/PlatformTest.php

@@ -108,7 +108,7 @@ class PlatformTest extends TestCase
         ]);
 
         $this->assertStrictEquals(6, count($platform->listTableColumns('user')));
-        $platform->dropColumn('user', 'lastname');
+        $platform->dropColumn('user', new StringColumn('lastname'));
         $this->assertStrictEquals(5, count($platform->listTableColumns('user')));
     }