Merge branch 'release/4.0.0'
All checks were successful
ci/woodpecker/push/code-style Pipeline was successful
ci/woodpecker/push/test Pipeline was successful

This commit is contained in:
Robin Krahnen 2022-05-04 23:41:15 +02:00
commit 81c93c2b06
19 changed files with 336 additions and 271 deletions

1
.gitignore vendored
View file

@ -1 +0,0 @@
.svn

View file

@ -0,0 +1,7 @@
pipeline:
code-style:
image: composer
commands:
- composer global config repositories.repo-name vcs https://git.digital-competence.de/Packages/php-codesniffer
- composer global require digicomp/php-codesniffer:@dev
- composer global exec -- phpcs --runtime-set ignore_warnings_on_exit 1 --standard=DigiComp Classes/ Migrations/ Tests/

25
.woodpecker/test.yml Normal file
View file

@ -0,0 +1,25 @@
workspace:
base: /woodpecker
path: package
matrix:
FLOW_VERSION:
- 6.3
pipeline:
functional-tests:
image: thecodingmachine/php:7.4-v4-cli
environment:
# Enable the PDO_SQLITE extension
- "PHP_EXTENSION_PDO_SQLITE=1"
- "FLOW_VERSION=${FLOW_VERSION}"
- "NEOS_BUILD_DIR=/woodpecker/Build-${FLOW_VERSION}"
commands:
- "sudo mkdir $NEOS_BUILD_DIR"
- "sudo chown -R docker:docker $NEOS_BUILD_DIR"
- "cd $NEOS_BUILD_DIR"
- "composer create-project --no-install neos/flow-base-distribution:^$FLOW_VERSION ."
- "composer config repositories.repo-name path /woodpecker/package"
- "composer remove --dev neos/behat"
- "composer require digicomp/sequence:@dev"
- "bin/phpunit --configuration Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/DigiComp.Sequence/Tests/Functional"

8
CHANGELOG.md Normal file
View file

@ -0,0 +1,8 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
Start of the changelog.

View file

@ -4,15 +4,16 @@ declare(strict_types=1);
namespace DigiComp\Sequence\Command;
use DigiComp\Sequence\Domain\Model\Insert;
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;
use Doctrine\ORM\EntityManagerInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cli\CommandController;
/**
* A database agnostic SequenceNumber generator
*
* @Flow\Scope("singleton")
*/
class SequenceCommandController extends CommandController
@ -30,38 +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
* @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
* 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 (empty($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 . '".');
}
}
}

View file

@ -1,78 +0,0 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
/**
* SequenceInsert
*
* @Flow\Entity
* @ORM\Table(indexes={
* @ORM\Index(name="type_idx", columns={"type"})
* })
*/
class Insert
{
/**
* @Flow\Identity
* @ORM\Id
* @var int
*/
protected int $number;
/**
* @Flow\Identity
* @ORM\Id
* @var string
*/
protected string $type;
/**
* @param int $number
* @param string|object $type
*/
public function __construct(int $number, $type)
{
$this->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;
}
}

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
/**
* This class is only here to set up the table. We never create an instance of this class.
*
* @Flow\Entity
*/
class SequenceEntry
{
/**
* @ORM\Id
* @var string
*/
protected string $type;
/**
* @ORM\Id
* @var int
*/
protected int $number;
}

11
Classes/Exception.php Normal file
View file

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence;
use Neos\Flow\Exception as NeosFlowException;
class Exception extends NeosFlowException
{
}

View file

@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence\Service;
/**
* SequenceException
*/
class Exception extends \Exception
{
}

View file

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence\Service\Exception;
use DigiComp\Sequence\Exception;
class InvalidSourceException extends Exception
{
}

View file

@ -4,15 +4,17 @@ declare(strict_types=1);
namespace DigiComp\Sequence\Service;
use DigiComp\Sequence\Domain\Model\Insert;
use Doctrine\DBAL\Exception as DBALException;
use DigiComp\Sequence\Domain\Model\SequenceEntry;
use DigiComp\Sequence\Service\Exception\InvalidSourceException;
use Doctrine\DBAL\Driver\Exception as DoctrineDBALDriverException;
use Doctrine\DBAL\Exception as DoctrineDBALException;
use Doctrine\ORM\EntityManagerInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Utility\TypeHandling;
use Psr\Log\LoggerInterface;
/**
* A SequenceNumber generator working for transactional databases
* A sequence number generator working for transactional databases.
*
* Thoughts: We could make the step-range configurable, and if > 1 we could return new keys immediately for this
* request, as we "reserved" the space between.
@ -31,101 +33,105 @@ class SequenceGenerator
* @Flow\Inject
* @var LoggerInterface
*/
protected $systemLogger;
protected $logger;
/**
* @param string|object $type
*
* @param string|object $source
* @return int
* @throws Exception
* @throws DBALException
* @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 (DBALException $e) {
if (! $e->getPrevious() instanceof \PDOException) {
$this->systemLogger->critical('Exception occured: ' . $e->getMessage());
} catch (\PDOException $exception) {
} catch (DoctrineDBALException $exception) {
if (!$exception->getPrevious() instanceof \PDOException) {
$this->logger->critical('Exception occurred: ' . $exception->getMessage());
}
} catch (\Exception $e) {
$this->systemLogger->critical('Exception occured: ' . $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
* @throws DBALException
* @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(
return (int)$this->entityManager->getConnection()->executeQuery(
'SELECT MAX(number) FROM '
. $this->entityManager->getClassMetadata(Insert::class)->getTableName()
. ' WHERE type = :type',
['type' => $this->inferTypeFromSource($type)]
)->fetchAll(\PDO::FETCH_COLUMN)[0];
. $this->entityManager->getClassMetadata(SequenceEntry::class)->getTableName()
. ' WHERE type = :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);
}
}

View file

@ -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"

View file

@ -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.
THE SOFTWARE.

View file

@ -1,44 +1,36 @@
<?php
declare(strict_types=1);
namespace Neos\Flow\Persistence\Doctrine\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Exception as DoctrineDBALException;
use Doctrine\DBAL\Migrations\AbortMigrationException;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs! This block will be used as the migration description if getDescription() is not used.
*/
class Version20140505093853 extends AbstractMigration
{
/**
* @return string
*/
public function getDescription()
{
return '';
}
/**
* @param Schema $schema
* @return void
* @throws AbortMigrationException
* @throws DoctrineDBALException
*/
public function up(Schema $schema)
public function up(Schema $schema): void
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
$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, PRIMARY KEY(number, type))');
}
/**
* @param Schema $schema
* @return void
* @throws AbortMigrationException
* @throws DoctrineDBALException
*/
public function down(Schema $schema)
public function down(Schema $schema): void
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on "mysql".');
$this->addSql('DROP TABLE digicomp_sequence_domain_model_insert');
}

View file

@ -1,44 +1,36 @@
<?php
declare(strict_types=1);
namespace Neos\Flow\Persistence\Doctrine\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Exception as DoctrineDBALException;
use Doctrine\DBAL\Migrations\AbortMigrationException;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs! This block will be used as the migration description if getDescription() is not used.
*/
class Version20160624203903 extends AbstractMigration
{
/**
* @return string
*/
public function getDescription()
{
return '';
}
/**
* @param Schema $schema
* @return void
* @throws AbortMigrationException
* @throws DoctrineDBALException
*/
public function up(Schema $schema)
public function up(Schema $schema): void
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on "mysql".');
$this->addSql('CREATE INDEX type_idx ON digicomp_sequence_domain_model_insert (type)');
}
/**
* @param Schema $schema
* @return void
* @throws AbortMigrationException
* @throws DoctrineDBALException
*/
public function down(Schema $schema)
public function down(Schema $schema): void
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on "mysql".');
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on "mysql".');
$this->addSql('DROP INDEX type_idx ON digicomp_sequence_domain_model_insert');
}

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Neos\Flow\Persistence\Doctrine\Migrations;
use Doctrine\DBAL\Exception as DoctrineDBALException;
use Doctrine\DBAL\Migrations\AbortMigrationException;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
class Version20210922110814 extends AbstractMigration
{
/**
* @param Schema $schema
* @throws AbortMigrationException
* @throws DoctrineDBALException
*/
public function up(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_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');
}
}

View file

@ -1,22 +1,21 @@
DigiComp.Sequence
-------------------------
# DigiComp.Sequence
This is a very simple and stupid tool, helping in generation of gapless sequences. For this task it relies on key
integrity of the database of your choice.
This is a very simple tool, helping in generation of gapless sequences. For this task it relies on key integrity of the
database of your choice.
Usage is quite simple also:
/**
* @param \DigiComp\Sequence\Service\SequenceNumberGenerator $sequenceNumberGenerator
*/
public function __construct(SequenceNumberGenerator $sequenceNumberGenerator)
{
$this->orderId = $sequenceNumberGenerator->getNextNumberFor($this);
}
```php
/**
* @param SequenceNumberGenerator $sequenceNumberGenerator
*/
public function __construct(SequenceNumberGenerator $sequenceNumberGenerator)
{
$this->orderId = $sequenceNumberGenerator->getNextNumberFor($this);
}
```
``getNextNumberFor`` allows you to give an object which will be resolved to its FQCN or a custom sequence name.
`getNextNumberFor` allows you to give an object (which will be resolved to its FQCN) or a custom sequence name.
The CommandController 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.

View file

@ -1,32 +1,39 @@
<?php
declare(strict_types=1);
namespace DigiComp\Sequence\Tests\Functional;
use DigiComp\Sequence\Service\Exception\InvalidSourceException;
use DigiComp\Sequence\Service\SequenceGenerator;
use Doctrine\DBAL\Driver\Exception as DoctrineDBALDriverException;
use Doctrine\DBAL\Exception as DoctrineDBALException;
use Neos\Flow\Tests\FunctionalTestCase;
class SequenceTest extends FunctionalTestCase
{
/**
* @var bool
* @inheritDoc
*/
protected static $testablePersistenceEnabled = true;
/**
* @test
* @throws DoctrineDBALDriverException
* @throws DoctrineDBALException
* @throws InvalidSourceException
*/
public function sequenceTest()
public function sequenceTest(): void
{
$sequenceGenerator = $this->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) {
$pId = \pcntl_fork();
if ($pId > 0) {
$pIds[] = $pId;
} else {
for ($j = 0; $j < 10; $j++) {
@ -39,21 +46,24 @@ class SequenceTest extends FunctionalTestCase
foreach ($pIds as $pId) {
$status = 0;
pcntl_waitpid($pId, $status);
\pcntl_waitpid($pId, $status);
}
$this->assertEquals(101, $sequenceGenerator->getLastNumberFor($sequenceGenerator));
self::assertEquals(101, $sequenceGenerator->getLastNumberFor($sequenceGenerator));
}
/**
* @test
* @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'));
}
}

View file

@ -1,41 +1,34 @@
{
"name": "digicomp/sequence",
"type": "neos-package",
"description": "Sequence is a very simple database agnostic but database based sequence generator",
"keywords": [
"flow",
"neos",
"doctrine",
"sequence"
],
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "http://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://github.com/digicomp/DigiComp.Sequence",
"type": "neos-package",
"require": {
"neos/flow": "~5.3 | ^6.3.5",
"php": "^7.4"
"ext-pdo": "*",
"neos/flow": "^6.3.5",
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"ext-pcntl": "*"
"ext-pcntl": "*",
"phpunit/phpunit": "~8.5"
},
"autoload": {
"psr-4": {
"DigiComp\\Sequence\\": "Classes"
"DigiComp\\Sequence\\": "Classes/"
}
},
"autoload-dev": {
"psr-4": {
"DigiComp\\Sequence\\Tests\\": "Tests/"
}
},
"extra": {
"neos": {
"package-key": "DigiComp.Sequence"
},
"branch-alias": {
"dev-version/1.x-dev": "1.1.x-dev",
"dev-develop": "4.0.x-dev",
"dev-version/2.x-dev": "2.1.x-dev",
"dev-develop": "3.0.x-dev"
"dev-version/1.x-dev": "1.1.x-dev"
},
"applied-flow-migrations": [
"Inwebs.Basket-201409170938",
@ -69,7 +62,25 @@
"Neos.Flow-20170125103800",
"Neos.Flow-20170127183102",
"DigiComp.SettingValidator-20170603120900",
"Neos.Flow-20180415105700"
"Neos.Flow-20180415105700",
"Neos.Flow-20190425144900",
"Neos.Flow-20190515215000"
]
}
},
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "https://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://github.com/digital-competence/DigiComp.Sequence",
"keywords": [
"Neos",
"Flow",
"doctrine",
"sequence"
]
}