diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index 8d5107f..8a881da 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -4,7 +4,6 @@ workspace: matrix: FLOW_VERSION: - - 5.3 - 6.3 pipeline: diff --git a/Classes/Command/SequenceCommandController.php b/Classes/Command/SequenceCommandController.php index cf9dc7e..1884d44 100644 --- a/Classes/Command/SequenceCommandController.php +++ b/Classes/Command/SequenceCommandController.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace DigiComp\Sequence\Command; -use DigiComp\Sequence\Domain\Model\Insert; -use DigiComp\Sequence\Service\Exception as DigiCompSequenceServiceException; +use DigiComp\Sequence\Domain\Model\SequenceEntry; +use DigiComp\Sequence\Service\Exception\InvalidSourceException; use DigiComp\Sequence\Service\SequenceGenerator; use Doctrine\DBAL\Driver\Exception as DoctrineDBALDriverException; use Doctrine\DBAL\Exception as DoctrineDBALException; @@ -14,8 +14,6 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; /** - * A database agnostic SequenceNumber generator - * * @Flow\Scope("singleton") */ class SequenceCommandController extends CommandController @@ -33,42 +31,52 @@ class SequenceCommandController extends CommandController protected $entityManager; /** - * Sets minimum number for sequence generator + * Set last number for sequence generator. * - * @param int $to * @param string $type - * @throws DigiCompSequenceServiceException + * @param int $number + * @throws DoctrineDBALDriverException + * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function advanceCommand(int $to, string $type): void + public function setLastNumberForCommand(string $type, int $number): void { - $this->sequenceGenerator->advanceTo($to, $type); + if ($this->sequenceGenerator->setLastNumberFor($type, $number)) { + $this->outputLine('Last number successfully set.'); + } else { + $this->outputLine('Failed to set last number.'); + } } /** - * @param string[] $typesToClean - * @throws DigiCompSequenceServiceException + * Clean up sequence table. + * + * @param string[] $types * @throws DoctrineDBALDriverException * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function cleanSequenceInsertsCommand(array $typesToClean = []) + public function cleanUpCommand(array $types = []): void { - $cleanArray = []; - if ($typesToClean === []) { - $results = $this->entityManager - ->createQuery('SELECT i.type, MAX(i.number) max_number FROM ' . Insert::class . ' i GROUP BY i.type') - ->getScalarResult(); - foreach ($results as $result) { - $cleanArray[$result['type']] = (int)$result['max_number']; - } - } else { - foreach ($typesToClean as $typeToClean) { - $cleanArray[$typeToClean] = $this->sequenceGenerator->getLastNumberFor($typeToClean); + if ($types === []) { + foreach ( + $this + ->entityManager + ->createQuery('SELECT DISTINCT(se.type) type FROM ' . SequenceEntry::class . ' se') + ->execute() + as $result + ) { + $types[] = $result['type']; } } - foreach ($cleanArray as $typeToClean => $number) { - $this->entityManager - ->createQuery('DELETE FROM ' . Insert::class . ' i WHERE i.type = ?0 AND i.number < ?1') - ->execute([$typeToClean, $number]); + + foreach ($types as $type) { + $rowCount = $this + ->entityManager + ->createQuery('DELETE FROM ' . SequenceEntry::class . ' se WHERE se.type = ?0 AND se.number < ?1') + ->execute([$type, $this->sequenceGenerator->getLastNumberFor($type)]); + + $this->outputLine('Deleted ' . $rowCount . ' row(s) for type "' . $type . '".'); } } } diff --git a/Classes/Domain/Model/Insert.php b/Classes/Domain/Model/Insert.php deleted file mode 100644 index 97b0cb8..0000000 --- a/Classes/Domain/Model/Insert.php +++ /dev/null @@ -1,78 +0,0 @@ -setNumber($number); - $this->setType($type); - } - - /** - * @return int - */ - public function getNumber(): int - { - return $this->number; - } - - /** - * @param int $number - */ - public function setNumber(int $number): void - { - $this->number = $number; - } - - /** - * @return string - */ - public function getType(): string - { - return $this->type; - } - - /** - * @param string|object $type - */ - public function setType($type): void - { - if (\is_object($type)) { - $type = \get_class($type); - } - $this->type = $type; - } -} diff --git a/Classes/Domain/Model/SequenceEntry.php b/Classes/Domain/Model/SequenceEntry.php new file mode 100644 index 0000000..ee8c39f --- /dev/null +++ b/Classes/Domain/Model/SequenceEntry.php @@ -0,0 +1,28 @@ + 1 we could return new keys immediately for this * request, as we "reserved" the space between. @@ -35,98 +36,102 @@ class SequenceGenerator protected $logger; /** - * @param string|object $type + * @param string|object $source * @return int - * @throws Exception * @throws DoctrineDBALDriverException * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function getNextNumberFor($type): int + public function getNextNumberFor($source): int { - $type = $this->inferTypeFromSource($type); - $count = $this->getLastNumberFor($type); + $type = $this->inferTypeFromSource($source); + $number = $this->getLastNumberFor($type); - // TODO: Check for maximal tries, or similar - // TODO: Let increment be configurable per type + // TODO: Check for maximal tries, or similar? + // TODO: Let increment be configurable per type? do { - $count++; - } while (!$this->validateFreeNumber($count, $type)); + $number++; + } while (!$this->insertFor($type, $number)); - return $count; + return $number; } /** - * @param int $count * @param string $type + * @param int $number * @return bool */ - protected function validateFreeNumber(int $count, string $type): bool + protected function insertFor(string $type, int $number): bool { - $em = $this->entityManager; try { - $em->getConnection()->insert( - $em->getClassMetadata(Insert::class)->getTableName(), - ['number' => $count, 'type' => $type] + $this->entityManager->getConnection()->insert( + $this->entityManager->getClassMetadata(SequenceEntry::class)->getTableName(), + ['type' => $type, 'number' => $number] ); + return true; - } catch (\PDOException $e) { - return false; - } catch (DoctrineDBALException $e) { - if (!$e->getPrevious() instanceof \PDOException) { - $this->logger->critical('Exception occurred: ' . $e->getMessage()); + } catch (\PDOException $exception) { + } catch (DoctrineDBALException $exception) { + if (!$exception->getPrevious() instanceof \PDOException) { + $this->logger->critical('Exception occurred: ' . $exception->getMessage()); } - } catch (\Exception $e) { - $this->logger->critical('Exception occurred: ' . $e->getMessage()); + } catch (\Exception $exception) { + $this->logger->critical('Exception occurred: ' . $exception->getMessage()); } return false; } /** - * @param int $to - * @param string|object $type - * + * @param string|object $source + * @param int $number * @return bool - * @throws Exception + * @throws DoctrineDBALDriverException + * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function advanceTo(int $to, $type): bool + public function setLastNumberFor($source, int $number): bool { - $type = $this->inferTypeFromSource($type); + $type = $this->inferTypeFromSource($source); - return $this->validateFreeNumber($to, $type); + if ($this->getLastNumberFor($type) >= $number) { + return false; + } + + return $this->insertFor($type, $number); } /** - * @param string|object $type - * @return int - * @throws Exception + * @param string|object $source * @throws DoctrineDBALDriverException * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function getLastNumberFor($type): int + public function getLastNumberFor($source): int { return (int)$this->entityManager->getConnection()->executeQuery( 'SELECT MAX(number) FROM ' - . $this->entityManager->getClassMetadata(Insert::class)->getTableName() + . $this->entityManager->getClassMetadata(SequenceEntry::class)->getTableName() . ' WHERE type = :type', - ['type' => $this->inferTypeFromSource($type)] + ['type' => $this->inferTypeFromSource($source)] )->fetchOne(); } /** - * @param string|object $stringOrObject + * @param string|object $source * @return string - * @throws Exception + * @throws InvalidSourceException */ - protected function inferTypeFromSource($stringOrObject): string + protected function inferTypeFromSource($source): string { - if (\is_object($stringOrObject)) { - $stringOrObject = TypeHandling::getTypeForValue($stringOrObject); - } - if (!$stringOrObject) { - throw new Exception('No Type given'); + if (\is_string($source)) { + return $source; } - return $stringOrObject; + if (\is_object($source)) { + return TypeHandling::getTypeForValue($source); + } + + throw new InvalidSourceException('Could not infer type from source.', 1632216173); } } diff --git a/Configuration/Testing/Settings.yaml b/Configuration/Testing/Settings.yaml index 54d2680..3f9770d 100644 --- a/Configuration/Testing/Settings.yaml +++ b/Configuration/Testing/Settings.yaml @@ -2,5 +2,5 @@ Neos: Flow: persistence: backendOptions: - driver: 'pdo_sqlite' - path: '%FLOW_PATH_DATA%/Temporary/testing.db' + driver: "pdo_sqlite" + path: "%FLOW_PATH_DATA%Temporary/testing.db" diff --git a/License.txt b/License.txt index 2ffa411..ab84d82 100644 --- a/License.txt +++ b/License.txt @@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/Migrations/Mysql/Version20140505093853.php b/Migrations/Mysql/Version20140505093853.php index d1aa1a3..2b1ea74 100644 --- a/Migrations/Mysql/Version20140505093853.php +++ b/Migrations/Mysql/Version20140505093853.php @@ -1,5 +1,7 @@ abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on "mysql".'); + + $this->addSql('CREATE TABLE digicomp_sequence_domain_model_sequenceentry (type VARCHAR(255) NOT NULL, number INT NOT NULL, PRIMARY KEY(type, number))'); + $this->addSql('INSERT INTO digicomp_sequence_domain_model_sequenceentry (type, number) SELECT i.type, MAX(i.number) FROM digicomp_sequence_domain_model_insert AS i GROUP BY i.type'); + $this->addSql('DROP TABLE digicomp_sequence_domain_model_insert'); + } + + /** + * @param Schema $schema + * @throws AbortMigrationException + * @throws DoctrineDBALException + */ + public function down(Schema $schema): void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on "mysql".'); + + $this->addSql('CREATE TABLE digicomp_sequence_domain_model_insert (number INT NOT NULL, type VARCHAR(255) NOT NULL, INDEX type_idx (type), PRIMARY KEY(number, type))'); + $this->addSql('INSERT INTO digicomp_sequence_domain_model_insert (number, type) SELECT MAX(se.number), se.type FROM digicomp_sequence_domain_model_sequenceentry AS se GROUP BY se.type'); + $this->addSql('DROP TABLE digicomp_sequence_domain_model_sequenceentry'); + } +} diff --git a/README.md b/README.md index 36ea7c5..4030886 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ This is a very simple tool, helping in generation of gapless sequences. For this database of your choice. Usage is quite simple also: + ```php /** * @param SequenceNumberGenerator $sequenceNumberGenerator @@ -16,5 +17,5 @@ public function __construct(SequenceNumberGenerator $sequenceNumberGenerator) `getNextNumberFor` allows you to give an object (which will be resolved to its FQCN) or a custom sequence name. -The `SequenceCommandController` helps you to advance the current sequence number, in case of migrations or similar. See -`./flow help sequence:advance` if interested. +The `SequenceCommandController` helps you to set the last sequence number, in case of migrations or similar. See +`./flow help sequence:setlastnumberfor` if interested. diff --git a/Tests/Functional/SequenceTest.php b/Tests/Functional/SequenceTest.php index b400c34..542095e 100644 --- a/Tests/Functional/SequenceTest.php +++ b/Tests/Functional/SequenceTest.php @@ -1,8 +1,10 @@ objectManager->get(SequenceGenerator::class); - $number = $sequenceGenerator->getLastNumberFor($sequenceGenerator); - $this->assertEquals(0, $number); - $this->assertEquals(1, $sequenceGenerator->getNextNumberFor($sequenceGenerator)); + self::assertEquals(0, $sequenceGenerator->getLastNumberFor($sequenceGenerator)); + self::assertEquals(1, $sequenceGenerator->getNextNumberFor($sequenceGenerator)); $pIds = []; for ($i = 0; $i < 10; $i++) { $pId = \pcntl_fork(); - if ($pId) { + if ($pId > 0) { $pIds[] = $pId; } else { for ($j = 0; $j < 10; $j++) { @@ -48,21 +49,21 @@ class SequenceTest extends FunctionalTestCase \pcntl_waitpid($pId, $status); } - $this->assertEquals(101, $sequenceGenerator->getLastNumberFor($sequenceGenerator)); + self::assertEquals(101, $sequenceGenerator->getLastNumberFor($sequenceGenerator)); } /** * @test - * @throws DigiCompSequenceServiceException * @throws DoctrineDBALDriverException * @throws DoctrineDBALException + * @throws InvalidSourceException */ - public function advanceTest() + public function setLastNumberForTest(): void { $sequenceGenerator = $this->objectManager->get(SequenceGenerator::class); + $sequenceGenerator->setLastNumberFor($sequenceGenerator, 100); - $sequenceGenerator->advanceTo(100, $sequenceGenerator); - $this->assertEquals(100, $sequenceGenerator->getLastNumberFor($sequenceGenerator)); - $this->assertEquals(0, $sequenceGenerator->getLastNumberFor('strangeOtherSequence')); + self::assertEquals(100, $sequenceGenerator->getLastNumberFor($sequenceGenerator)); + self::assertEquals(0, $sequenceGenerator->getLastNumberFor('otherSequence')); } } diff --git a/composer.json b/composer.json index cace62e..89a5b5a 100644 --- a/composer.json +++ b/composer.json @@ -4,12 +4,12 @@ "type": "neos-package", "require": { "ext-pdo": "*", - "neos/flow": "^5.3.0 || ^6.3.5", - "php": "~7.4.0" + "neos/flow": "^6.3.5", + "php": ">=7.4" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "3.7.*" + "phpunit/phpunit": "~8.5" }, "autoload": { "psr-4": { @@ -26,7 +26,7 @@ "package-key": "DigiComp.Sequence" }, "branch-alias": { - "dev-develop": "3.0.x-dev", + "dev-develop": "4.0.x-dev", "dev-version/2.x-dev": "2.1.x-dev", "dev-version/1.x-dev": "1.1.x-dev" }, @@ -62,7 +62,9 @@ "Neos.Flow-20170125103800", "Neos.Flow-20170127183102", "DigiComp.SettingValidator-20170603120900", - "Neos.Flow-20180415105700" + "Neos.Flow-20180415105700", + "Neos.Flow-20190425144900", + "Neos.Flow-20190515215000" ] }, "authors": [