Ver código fonte

add ProxyFactory::registerAutoloader + misc improvements & bug fixes

phpdevcommunity 1 mês atrás
pai
commit
f3370c6353

+ 0 - 1
src/Cache/EntityMemcachedCache.php

@@ -10,7 +10,6 @@ final class EntityMemcachedCache
      */
     private array $cache = [];
 
-
     public function get(string $class, string $primaryKeyValue): ?object
     {
         $key = $this->generateKey($class, $primaryKeyValue);

+ 3 - 1
src/EntityManager.php

@@ -13,6 +13,7 @@ use PhpDevCommunity\PaperORM\EventListener\UpdatedAtListener;
 use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
 use PhpDevCommunity\PaperORM\Parser\DSNParser;
 use PhpDevCommunity\PaperORM\Platform\PlatformInterface;
+use PhpDevCommunity\PaperORM\Proxy\ProxyFactory;
 use PhpDevCommunity\PaperORM\Repository\Repository;
 use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\EventDispatcher\ListenerProviderInterface;
@@ -20,6 +21,7 @@ use Psr\Log\LoggerInterface;
 
 class EntityManager implements EntityManagerInterface
 {
+
     private PaperConnection $connection;
 
     private UnitOfWork $unitOfWork;
@@ -54,7 +56,7 @@ class EntityManager implements EntityManagerInterface
         if (!isset($config['driver'])) {
             throw new \InvalidArgumentException('Missing "driver" in EntityManager configuration.');
         }
-
+        
         $this->connection = DriverManager::createConnection($config['driver'], $config);
         $this->unitOfWork = new UnitOfWork();
         $this->cache = new EntityMemcachedCache();

+ 2 - 18
src/Hydrator/EntityHydrator.php

@@ -10,6 +10,7 @@ use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\OneToMany;
+use PhpDevCommunity\PaperORM\Proxy\ProxyFactory;
 use PhpDevCommunity\PaperORM\Proxy\ProxyInterface;
 use ReflectionClass;
 
@@ -26,28 +27,11 @@ final class EntityHydrator extends AbstractEntityHydrator
     {
         $primaryKey = ColumnMapper::getPrimaryKeyColumnName($class);
 
-        $object = $this->cache->get($class, $data[$primaryKey])
-            ?: $this->createProxy($class);
+        $object = $this->cache->get($class, $data[$primaryKey]) ?: ProxyFactory::create($class);
 
         $this->cache->set($class, $data[$primaryKey], $object);
 
         return $object;
     }
-
-    private function createProxy(string $class): object
-    {
-        $sanitized = str_replace('\\', '_', $class);
-        $proxyClass = 'Proxy_' . $sanitized;
-
-        if (!class_exists($proxyClass)) {
-            eval("
-                class $proxyClass extends \\$class implements \\PhpDevCommunity\\PaperORM\\Proxy\\ProxyInterface {
-                    use \\PhpDevCommunity\\PaperORM\\Proxy\\ProxyInitializedTrait;
-                }
-            ");
-        }
-
-        return new $proxyClass();
-    }
 }
 

+ 6 - 1
src/Mapper/EntityMapper.php

@@ -6,6 +6,7 @@ namespace PhpDevCommunity\PaperORM\Mapper;
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\Entity\TableMetadataInterface;
 use PhpDevCommunity\PaperORM\Mapping\Entity;
+use PhpDevCommunity\PaperORM\Proxy\ProxyInterface;
 
 final class EntityMapper
 {
@@ -63,8 +64,12 @@ final class EntityMapper
         ));
     }
 
-    static public function getEntityPHP8($class): ?Entity
+    static private function getEntityPHP8($class): ?Entity
     {
+        if ($class instanceof ProxyInterface) {
+            $class = $class->__getParentClass();
+        }
+
         $reflector = new \ReflectionClass($class);
         $attributes = $reflector->getAttributes(Entity::class);
         if (!empty($attributes)) {

+ 1 - 1
src/PaperConnection.php

@@ -123,7 +123,7 @@ final class PaperConnection
     public function cloneConnectionWithoutDbname(): self
     {
         $params = $this->params;
-        unset($params['dbname']);
+        unset($params['path']);
         return new self($this->driver, $params);
     }
 

+ 43 - 23
src/Persistence/EntityPersistence.php

@@ -5,7 +5,6 @@ namespace PhpDevCommunity\PaperORM\Persistence;
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\Event\PreCreateEvent;
 use PhpDevCommunity\PaperORM\Event\PreUpdateEvent;
-use PhpDevCommunity\PaperORM\Hydrator\EntityHydrator;
 use PhpDevCommunity\PaperORM\Hydrator\ReadOnlyEntityHydrator;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
 use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
@@ -18,12 +17,14 @@ use Psr\EventDispatcher\EventDispatcherInterface;
 class EntityPersistence
 {
     private PlatformInterface $platform;
+    private \SplObjectStorage $managed;
 
     private ?EventDispatcherInterface $dispatcher;
     public function __construct(PlatformInterface $platform, EventDispatcherInterface $dispatcher = null)
     {
         $this->platform = $platform;
         $this->dispatcher = $dispatcher;
+        $this->managed = new \SplObjectStorage();
     }
 
     public function insert(object $entity): int
@@ -32,8 +33,12 @@ class EntityPersistence
          * @var EntityInterface $entity
          */
         $this->checkEntity($entity);
-        if ($entity->getPrimaryKeyValue() !== null) {
-            throw new \LogicException(static::class . sprintf(' Cannot insert an entity %s with a primary key ', get_class($entity)));
+        if ($entity->getPrimaryKeyValue() !== null || $this->managed->contains($entity)) {
+            throw new \LogicException(sprintf(
+                '%s cannot insert entity of type "%s": entity already managed (has primary key, is a proxy, or is attached).',
+                static::class,
+                get_class($entity)
+            ));
         }
 
         if ($this->dispatcher) {
@@ -54,6 +59,7 @@ class EntityPersistence
         if ($rows > 0) {
             $primaryKeyColumn = ColumnMapper::getPrimaryKeyColumnName($entity);
             (new ReadOnlyEntityHydrator())->hydrate($entity, [$primaryKeyColumn => $lastInsertId]);
+            $this->managed->attach($entity);
         }
         return $rows;
     }
@@ -68,13 +74,22 @@ class EntityPersistence
             throw new \LogicException(static::class . sprintf(' Cannot update an entity %s without a primary key ', get_class($entity)));
         }
 
-        if (!$entity->__wasModified()) {
-            return 0;
+        if ($entity instanceof ProxyInterface) {
+            if (!$entity->__wasModified()) {
+                return 0;
+            }
         }
 
         if ($this->dispatcher) {
             $this->dispatcher->dispatch(new PreUpdateEvent($entity));
         }
+
+        if ($entity instanceof ProxyInterface) {
+            $propertiesModified = $entity->__getPropertiesModified();
+        } else {
+            $propertiesModified = [];
+        }
+
         $tableName = EntityMapper::getTable($entity);
         $schema = $this->platform->getSchema();
         $qb = QueryBuilder::update($schema->quote($tableName))
@@ -84,14 +99,15 @@ class EntityPersistence
                     $entity->getPrimaryKeyValue()
                 )
             );
+
         $values = [];
-        foreach ((new SerializerToDb($entity))->serialize($entity->__getPropertiesModified()) as $key => $value) {
+        foreach ((new SerializerToDb($entity))->serialize($propertiesModified) as $key => $value) {
             $qb->set($schema->quote($key), ":$key");
             $values[$key] = $value;
         }
         $conn = $this->platform->getConnection();
         $rows = $conn->executeStatement($qb, $values);
-        if ($rows > 0) {
+        if ($rows > 0 && $entity instanceof ProxyInterface) {
             $entity->__reset();
         }
         return $rows;
@@ -110,22 +126,24 @@ class EntityPersistence
         $tableName = EntityMapper::getTable($entity);
         $schema = $this->platform->getSchema();
         $qb = QueryBuilder::delete($schema->quote($tableName))
-            ->where(
-                sprintf('%s = %s',
-                    $schema->quote(ColumnMapper::getPrimaryKeyColumnName($entity)),
-                    $entity->getPrimaryKeyValue()
-                )
-            );
+            ->where(sprintf('%s = :id', $schema->quote(ColumnMapper::getPrimaryKeyColumnName($entity))));
 
         $conn = $this->platform->getConnection();
-        $rows = $conn->executeStatement($qb);
+        $rows = $conn->executeStatement($qb,  [
+            'id' => $entity->getPrimaryKeyValue(),
+        ]);
         if ($rows > 0) {
-            $entity->__destroy();
+            if ($entity instanceof ProxyInterface) {
+                $entity->__destroy();
+            }
+            if ($this->managed->contains($entity)) {
+                $this->managed->detach($entity);
+            }
         }
         return $rows;
     }
 
-    private function checkEntity(object $entity, bool $proxy = false): void
+    private function checkEntity(object $entity,  bool $requireManaged = false): void
     {
         if (!$entity instanceof EntityInterface) {
             throw new \LogicException(sprintf(
@@ -135,14 +153,16 @@ class EntityPersistence
             ));
         }
 
-        if ($proxy && (!$entity instanceof ProxyInterface || !$entity->__isInitialized())) {
-            throw new \LogicException(sprintf(
-                'Entity of type "%s" is not a valid initialized proxy (expected instance of "%s").',
-                get_class($entity),
-                ProxyInterface::class
-            ));
+        if ($requireManaged) {
+            $isManaged = $this->managed->contains($entity);
+            $isProxy = $entity instanceof ProxyInterface && $entity->__isInitialized();
+            if (!$isManaged && !$isProxy) {
+                throw new \LogicException(sprintf(
+                    'Entity of type "%s" is not managed by ORM',
+                    get_class($entity)
+                ));
+            }
         }
     }
 
-
 }

+ 1 - 1
src/Platform/MariaDBPlatform.php

@@ -38,7 +38,7 @@ class MariaDBPlatform extends AbstractPlatform
 
     public function getDatabaseName(): string
     {
-        return $this->connection->getParams()['dbname'] ?? '';
+        return $this->connection->getParams()['path'] ?? '';
     }
 
     public function listTables(): array

+ 51 - 0
src/Proxy/ProxyFactory.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Proxy;
+
+final class ProxyFactory
+{
+    private static bool $registered = false;
+
+    public static function registerAutoloader(): void
+    {
+        if (self::$registered) {
+            return;
+        }
+
+        spl_autoload_register(function ($class) {
+            if (strpos($class, 'Proxy_') === 0) {
+                $original = str_replace('_', '\\', substr($class, 6));
+                self::generate($original, $class);
+            }
+        });
+
+        self::$registered = true;
+    }
+
+    private static function generate(string $original, string $proxyClass): void
+    {
+        if (!class_exists($original)) {
+            throw new \RuntimeException("Cannot create proxy: original class {$original} does not exist.");
+        }
+
+        if (!class_exists($proxyClass)) {
+            eval("
+                class $proxyClass extends \\$original implements \\PhpDevCommunity\\PaperORM\\Proxy\\ProxyInterface {
+                    use \\PhpDevCommunity\\PaperORM\\Proxy\\ProxyInitializedTrait;
+                }
+            ");
+        }
+    }
+
+    public static function create(string $original): object
+    {
+        $sanitized = str_replace('\\', '_', $original);
+        $proxyClass = 'Proxy_' . $sanitized;
+
+        if (!class_exists($proxyClass)) {
+            self::generate($original, $proxyClass);
+        }
+
+        return new $proxyClass();
+    }
+}

+ 19 - 3
src/Proxy/ProxyInitializedTrait.php

@@ -36,7 +36,7 @@ trait ProxyInitializedTrait
         if (!$this->__initialized) {
             return false;
         }
-        return json_encode($this->getValues()) !== json_encode($this->__valuesInitialized);
+        return count($this->__getPropertiesModified()) > 0;
     }
 
     public function __getPropertiesModified() : array
@@ -44,7 +44,23 @@ trait ProxyInitializedTrait
         if (!$this->__initialized) {
             return [];
         }
-        return array_keys(array_diff_assoc($this->getValues(), $this->__valuesInitialized));
+
+        $changed = [];
+        $initial = $this->__valuesInitialized;
+        $current = $this->getValues();
+
+        foreach ($current as $key => $value) {
+            if (!array_key_exists($key, $initial)) {
+                $changed[] = $key;
+                continue;
+            }
+
+            if ($value !== $initial[$key]) {
+                $changed[] = $key;
+            }
+        }
+
+        return $changed;
     }
 
     public function __destroy() : void
@@ -59,7 +75,7 @@ trait ProxyInitializedTrait
         $this->__setInitialized($this->__propertiesInitialized);
     }
 
-    private function getParentClass(): string
+    public function __getParentClass(): string
     {
         return get_parent_class($this);
     }

+ 2 - 0
src/Proxy/ProxyInterface.php

@@ -15,4 +15,6 @@ interface ProxyInterface
     public function __destroy(): void;
 
     public function __reset(): void;
+
+    public function __getParentClass(): string;
 }

+ 3 - 1
src/Repository/Repository.php

@@ -98,7 +98,9 @@ abstract class Repository
 
     public function delete(object $entityToDelete): int
     {
-        return $this->ep->delete($entityToDelete);
+        $rows = $this->ep->delete($entityToDelete);
+        $this->em->getCache()->invalidate(get_class($entityToDelete), $entityToDelete->getPrimaryKeyValue());
+        return $rows;
     }
 
     public function qb(): QueryBuilder

+ 1 - 1
src/Schema/MariaDBSchema.php

@@ -24,7 +24,7 @@ class MariaDBSchema implements SchemaInterface
 
     public function showTableColumns(string $tableName): string
     {
-        return sprintf("SHOW COLUMNS FROM %s", $tableName);
+        return sprintf("SHOW COLUMNS FROM %s", $this->quote($tableName));
     }
 
     public function showForeignKeys(string $tableName): string

+ 1 - 1
tests/Helper/DataBaseHelperTest.php

@@ -30,7 +30,7 @@ class DataBaseHelperTest
                 'driver' => 'mariadb',
                 'host' => getenv('MARIADB_HOST') ?: '127.0.0.1',
                 'port' => (int)(getenv('MARIADB_PORT') ?: 3306),
-                'dbname' => getenv('MARIADB_DB') ?: 'paper_orm_test',
+                'path' => getenv('MARIADB_DB') ?: 'paper_orm_test',
                 'user' => getenv('MARIADB_USER') ?: 'root',
                 'password' => getenv('MARIADB_PASSWORD') ?: '',
                 'charset' => 'utf8mb4',

+ 21 - 0
tests/PersistAndFlushTest.php

@@ -28,6 +28,7 @@ class PersistAndFlushTest extends TestCase
             $em = new EntityManager($params);
             DataBaseHelperTest::init($em);
             $this->testInsert($em);
+            $this->testInsertAndUpdate($em);
             $this->testUpdate($em);
             $this->testUpdateJoinColumn($em);
             $this->testDelete($em);
@@ -55,12 +56,32 @@ class PersistAndFlushTest extends TestCase
         $em->clear();
     }
 
+
+    private function testInsertAndUpdate(EntityManager $em): void
+    {
+        $user = new UserTest();
+        $user->setFirstname('John');
+        $user->setLastname('Doe');
+        $user->setPassword('secret');
+        $user->setEmail('Xq5qI@example.com');
+        $user->setActive(true);
+        $em->persist($user);
+        $em->flush();
+        $this->assertNotNull($user->getId());
+        $this->assertInstanceOf(\DateTimeInterface::class, $user->getCreatedAt());
+        $user->setLastname('TOTO');
+        $em->persist($user);
+        $em->flush();
+        $em->clear();
+    }
+
     private function testUpdate(EntityManager $em): void
     {
         $userRepository = $em->getRepository(UserTest::class);
         $user = $userRepository->findBy()->first()->orderBy('id')->toObject();
         $this->assertInstanceOf(ProxyInterface::class, $user);
         $this->assertInstanceOf(UserTest::class, $user);
+        $this->assertStrictEquals(UserTest::class, $user->__getParentClass());
         /**
          * @var ProxyInterface|UserTest $user
          */

+ 4 - 3
tests/PlatformDiffTest.php

@@ -24,7 +24,7 @@ class PlatformDiffTest extends TestCase
 
     protected function execute(): void
     {
-        foreach (DataBaseHelperTest::drivers() as $params) {
+        foreach (DataBaseHelperTest::drivers() as $name => $params) {
             $em = new EntityManager($params);
             $this->executeTest($em);
             $em->getConnection()->close();
@@ -45,10 +45,11 @@ class PlatformDiffTest extends TestCase
             new StringColumn('password'),
             (new BoolColumn('is_active'))->bindProperty('active'),
         ];
-        $platform->createTable('user', $columns);
+        $rows = $platform->createTable('user', $columns);
+//        var_dump($rows);
+//        exit();
 
         $diff = $platform->diff('user', $columns, [] );
-
         $this->assertEmpty($diff->getColumnsToAdd());
         $this->assertEmpty($diff->getColumnsToUpdate());
         $this->assertEmpty($diff->getColumnsToDelete());