diff --git a/.travis.yml b/.travis.yml index 3f17b0be..eec9ed73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,12 @@ cache: - $HOME/.composer/cache matrix: include: + - php: 7.4 + env: PREFER_LOWEST="" DB=mysql RUN_PHPSTAN=1 RUN_CSCHECK=1 RUN_REQUIRECHECKER=1 NO_WEAKREF=1 + services: + - mysql - php: 7.2 - env: PREFER_LOWEST="" DB=mysql RUN_PHPSTAN=1 RUN_CSCHECK=1 RUN_REQUIRECHECKER=1 + env: PREFER_LOWEST="" DB=mysql services: - mysql - php: 7.3 @@ -57,7 +61,10 @@ matrix: postgresql: "9.6" services: - postgresql + - php: 7.3 + env: PREFER_LOWEST="" DB=mysql NO_WEAKREF=1 allow_failures: + - php: 7.3 # 7.3 allowed to fail due to an issue with greenlion/PHP-SQL-Parser: https://github.com/greenlion/PHP-SQL-Parser/pull/304 - php: 7.1 env: PREFER_LOWEST="" DB=oracle PHPUNITFILE="-c phpunit.oracle.xml" - php: 7.1 diff --git a/composer-require-checker.json b/composer-require-checker.json index 8718fd43..bab62242 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -3,7 +3,7 @@ "null", "true", "false", "static", "self", "parent", "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", - "WeakRef", + "WeakRef", "WeakReference", "TheCodingMachine\\TDBM\\Fixtures\\Interfaces\\TestUserDaoInterface", "TheCodingMachine\\TDBM\\Fixtures\\Traits\\TestUserDaoTrait", "TheCodingMachine\\TDBM\\Fixtures\\Interfaces\\TestUserInterface", diff --git a/phpstan.neon b/phpstan.neon index 5bdd6d69..95ac25dd 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,6 +11,10 @@ parameters: - "#expects array, array given.#" - "/Parameter #. \\$types of method Doctrine\\\\DBAL\\\\Connection::.*() expects array, array given./" - "#Method TheCodingMachine\\\\TDBM\\\\Schema\\\\ForeignKey::.*() should return .* but returns array|string.#" + - '#Method TheCodingMachine\\TDBM\\NativeWeakrefObjectStorage::get\(\) should return TheCodingMachine\\TDBM\\DbRow\|null but returns object\|null.#' + - + message: '#WeakRef#' + path: src/WeakrefObjectStorage.php - message: '#Result of && is always false.#' path: src/Test/Dao/Bean/Generated/ArticleBaseBean.php diff --git a/src/NativeWeakrefObjectStorage.php b/src/NativeWeakrefObjectStorage.php new file mode 100644 index 00000000..0143da94 --- /dev/null +++ b/src/NativeWeakrefObjectStorage.php @@ -0,0 +1,107 @@ +objects[$tableName][$id] = WeakReference::create($dbRow); + ++$this->garbageCollectorCount; + if ($this->garbageCollectorCount === 10000) { + $this->garbageCollectorCount = 0; + $this->cleanupDanglingWeakRefs(); + } + } + + /** + * Returns an object from the storage (or null if no object is set). + * + * @param string $tableName + * @param string|int $id + * + * @return DbRow|null + */ + public function get(string $tableName, $id) : ?DbRow + { + if (isset($this->objects[$tableName][$id])) { + return $this->objects[$tableName][$id]->get(); + } + return null; + } + + /** + * Removes an object from the storage. + * + * @param string $tableName + * @param string|int $id + */ + public function remove(string $tableName, $id): void + { + unset($this->objects[$tableName][$id]); + } + + private function cleanupDanglingWeakRefs(): void + { + foreach ($this->objects as $tableName => $table) { + foreach ($table as $id => $obj) { + if ($obj->get() === null) { + unset($this->objects[$tableName][$id]); + } + } + } + } +} diff --git a/src/TDBMService.php b/src/TDBMService.php index af8a5f5f..1185cb3b 100644 --- a/src/TDBMService.php +++ b/src/TDBMService.php @@ -21,6 +21,7 @@ namespace TheCodingMachine\TDBM; +use function class_exists; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ClearableCache; use Doctrine\Common\Cache\VoidCache; @@ -46,7 +47,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Psr\Log\NullLogger; -use function var_export; +use WeakReference; /** * The TDBMService class is the main TDBM class. It provides methods to retrieve TDBMObject instances @@ -101,7 +102,7 @@ class TDBMService * Access is done by table name and then by primary key. * If the primary key is split on several columns, access is done by an array of columns, serialized. * - * @var StandardObjectStorage|WeakrefObjectStorage + * @var ObjectStorageInterface */ private $objectStorage; @@ -179,7 +180,9 @@ class TDBMService */ public function __construct(ConfigurationInterface $configuration) { - if (extension_loaded('weakref')) { + if (class_exists(WeakReference::class)) { + $this->objectStorage = new NativeWeakrefObjectStorage(); + } elseif (extension_loaded('weakref')) { $this->objectStorage = new WeakrefObjectStorage(); } else { $this->objectStorage = new StandardObjectStorage(); diff --git a/src/WeakrefObjectStorage.php b/src/WeakrefObjectStorage.php index 2ff8b399..a921ee19 100644 --- a/src/WeakrefObjectStorage.php +++ b/src/WeakrefObjectStorage.php @@ -26,7 +26,9 @@ * If a bean is requested twice from TDBM, the WeakrefObjectStorage is used to "cache" the bean. * Unlike the StandardObjectStorage, the WeakrefObjectStorage manages memory in a clever way, using the weakref * PHP extension. It is used if the "weakref" extension is available. - * Otherwise, the StandardObjectStorage is used instead. + * Otherwise, the StandardObjectStorage or NativeWeakrefObjectStorage (in PHP 7.4+) is used instead. + * + * Note: the weakref extension is available until PHP 7.2, but not available in PHP 7.3+ * * @author David Negrier */ diff --git a/tests/Commands/GenerateCommandTest.php b/tests/Commands/GenerateCommandTest.php index dd8c6898..fb1b4f6c 100644 --- a/tests/Commands/GenerateCommandTest.php +++ b/tests/Commands/GenerateCommandTest.php @@ -26,7 +26,7 @@ public function testCall(): void $result = $this->callCommand(new GenerateCommand($this->getConfiguration()), $input); - $this->assertStringContainsString('Finished regenerating DAOs and beans', $result); + $this->assertContains('Finished regenerating DAOs and beans', $result); } /** diff --git a/tests/TDBMDaoGeneratorTest.php b/tests/TDBMDaoGeneratorTest.php index c0682a10..c4bc19f2 100644 --- a/tests/TDBMDaoGeneratorTest.php +++ b/tests/TDBMDaoGeneratorTest.php @@ -30,6 +30,7 @@ use Ramsey\Uuid\Uuid; use ReflectionClass; use ReflectionMethod; +use ReflectionNamedType; use TheCodingMachine\TDBM\Dao\TestAlbumDao; use TheCodingMachine\TDBM\Dao\TestArticleDao; use TheCodingMachine\TDBM\Dao\TestCountryDao; @@ -88,6 +89,7 @@ use TheCodingMachine\TDBM\Utils\PathFinder\PathFinder; use TheCodingMachine\TDBM\Utils\TDBMDaoGenerator; use Symfony\Component\Process\Process; +use function gettype; class TDBMDaoGeneratorTest extends TDBMAbstractServiceTest { @@ -1611,9 +1613,10 @@ public function testUuidv4(): void public function testTypeHintedConstructors(): void { $userBaseBeanReflectionConstructor = new \ReflectionMethod(UserBaseBean::class, '__construct'); + /** @var ReflectionNamedType $nameParam */ $nameParam = $userBaseBeanReflectionConstructor->getParameters()[0]; - $this->assertSame('string', (string)$nameParam->getType()); + $this->assertSame('string', $nameParam->getType()->getName()); } /** @@ -1725,7 +1728,7 @@ public function testBlob(): void $resource = $loadedFile->getFile(); $result = fseek($resource, 0); $this->assertSame(0, $result); - $this->assertIsResource($resource); + $this->assertSame('resource', gettype($resource)); $firstLine = fgets($resource); $this->assertSame("getById(1); $resource = $loadedFile->getFile(); - $this->assertIsResource($resource); + $this->assertSame('resource', gettype($resource)); $firstLine = fgets($resource); $this->assertSame("assertSame('string', (string) $reflectionClass->getMethod('getLength')->getReturnType()); + $this->assertSame('string', $reflectionClass->getMethod('getLength')->getReturnType()->getName()); } /**