sirix / cycle-orm-factory
Cycle ORM Factories for Mezzio
Requires
- php: ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- cycle/annotated: ^4.4
- cycle/database: ^2.15
- cycle/orm: ^2.12.3
- psr/container: ^1.0 || ^2.0
- sirix/container-resolver: ^0.2 || ^1.0
- spiral/tokenizer: ^2.14 || ^3.0
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.8
- cycle/entity-behavior: ^1.7
- cycle/migrations: ^4.2
- cycle/schema-migrations-generator: ^2.3
- mockery/mockery: ^1.6
- phpstan/phpstan-mockery: ^1.1
- phpunit/phpunit: ^11.5
- symfony/console: ^7.3
Suggests
- cycle/entity-behavior: Optional: Entity behavior package for behavior-driven entity mapping
- cycle/entity-behavior-uuid: Optional: UUID behavior extensions for cycle/entity-behavior
- cycle/migrations: Optional: Database migrations runtime (Migrator). Required to run migration commands
- cycle/schema-migrations-generator: Optional: Generate migration files from schema changes
- laminas/laminas-cli: Optional: Provides convenient CLI integration for Laminas/Mezzio applications
- sirix/cycle-orm-extensions: Optional: Practical extensions for Cycle ORM: base repositories, entity traits, typecasts, and more
- symfony/console: Optional: Required only for built-in CLI commands
This package is auto-updated.
Last update: 2026-08-13 08:39:36 UTC
README
Migration guides:
Factories for integrating Cycle ORM into Mezzio with a runtime-focused schema pipeline.
Installation
composer require sirix/cycle-orm-factory
Optional packages:
symfony/console: required for built-in CLI commands.laminas/laminas-cli: optional CLI integration for Mezzio/Laminas.cycle/migrations: required for migration runtime commands.cycle/schema-migrations-generator: required forcycle:schema:migration:generate.cycle/entity-behaviorandcycle/entity-behavior-uuid: optional behavior events; runtime falls back to default Cycle command generator if not installed.
Configuration
Create config/autoload/cycle-orm.global.php:
<?php declare(strict_types=1); use Cycle\Database\Config; use Cycle\ORM\Collection\ArrayCollectionFactory; use Cycle\ORM\Mapper\Mapper; use Cycle\ORM\Relation; use Cycle\ORM\SchemaInterface; return [ 'cycle' => [ 'db-config' => [ 'default' => 'default', 'databases' => [ 'default' => [ 'connection' => 'mysql', ], ], 'connections' => [ 'mysql' => new Config\MySQLDriverConfig( connection: new Config\MySQL\TcpConnectionConfig( database: 'cycle-orm', host: '127.0.0.1', port: 3306, user: 'cycle', password: 'password', ), reconnect: true, timezone: 'UTC', queryCache: true, ), ], ], 'migrator' => [ 'directory' => 'db/migrations', 'table' => 'migrations', 'seed_directory' => 'db/seeds', 'namespace' => 'App\\Migrations', // optional 'vendor_directories' => ['vendor/path'], // optional 'safe' => false, // optional ], 'entities' => [ 'src/App/src/Entity', ], 'generators' => [ // 'my.custom.generator.service', // \App\Cycle\Schema\Generator\MyCustomGenerator::class, // new \App\Cycle\Schema\Generator\InlineGenerator(), ], 'collections' => [ 'default' => ArrayCollectionFactory::class, 'factories' => [ // Register any Cycle ORM collection factory: // 'doctrine' => \Cycle\ORM\Collection\DoctrineCollectionFactory::class, // 'illuminate' => \Cycle\ORM\Collection\IlluminateCollectionFactory::class, // 'loophp' => \Cycle\ORM\Collection\LoophpCollectionFactory::class, ], ], 'schema' => [ 'cache' => [ 'enabled' => true, ], 'compiled' => [ 'path' => 'data/cache/cycle/schema.php', ], 'manual_mapping_schema_definitions' => [ 'user' => [ SchemaInterface::ENTITY => User::class, SchemaInterface::MAPPER => Mapper::class, SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'user', SchemaInterface::PRIMARY_KEY => 'id', SchemaInterface::COLUMNS => [ 'id' => 'id', 'email' => 'email', ], SchemaInterface::TYPECAST => [ 'id' => 'int', ], SchemaInterface::RELATIONS => [ 'profile' => [ Relation::TYPE => Relation::HAS_ONE, Relation::TARGET => 'profile', Relation::SCHEMA => [ Relation::CASCADE => true, Relation::INNER_KEY => 'id', Relation::OUTER_KEY => 'user_id', ], ], ], ], ], ], ], ];
Runtime schema contract (v3)
Runtime behavior is controlled by cycle.schema.cache.enabled.
When true:
- ORM tries to load compiled schema from
cycle.schema.compiled.path. - If file exists: schema is loaded via
requireand ORM is created. - If file is missing: schema is compiled on first start, persisted to file, then reused.
When false:
- Schema is compiled on every start in memory.
- No compiled schema file is read or written by runtime.
Recommended production setup:
- keep
cache.enabled=true - run
cycle:schema:compileduring build/release.
Additional schema generators
cycle.generators supports:
- service ID from container,
- generator FQCN with zero-arg constructor,
- direct instance implementing
Cycle\Schema\GeneratorInterface.
Invalid entries throw Cycle\ORM\Exception\ConfigException.
Collection factories
cycle.collections configures Cycle ORM collection factories for *Many relations.
It works with the built-in Cycle factories and with custom implementations of
Cycle\ORM\Collection\CollectionFactoryInterface.
use Cycle\ORM\Collection\ArrayCollectionFactory; use Cycle\ORM\Collection\DoctrineCollectionFactory; use Cycle\ORM\Collection\IlluminateCollectionFactory; use Cycle\ORM\Collection\LoophpCollectionFactory; return [ 'cycle' => [ 'collections' => [ 'default' => ArrayCollectionFactory::class, 'factories' => [ // Requires doctrine/collections. 'doctrine' => DoctrineCollectionFactory::class, // Requires illuminate/collections. 'illuminate' => IlluminateCollectionFactory::class, // Requires loophp/collection. 'loophp' => LoophpCollectionFactory::class, ], ], ], ];
Factory definitions support:
- service ID from container,
- factory FQCN with zero-arg constructor,
- direct instance implementing
Cycle\ORM\Collection\CollectionFactoryInterface.
Custom collection factories may use the same short form as built-in factories:
return [ 'cycle' => [ 'collections' => [ 'factories' => [ 'custom' => App\Cycle\Collection\CustomCollectionFactory::class, ], ], ], ];
With the short form, the package registers the factory by alias and lets Cycle infer the collection interface from
CustomCollectionFactory::getInterface(). If the factory class has constructor dependencies, register it as a container
service and use that service ID instead of the class name.
Use the extended definition only when you need to explicitly provide a base collection class or interface for
collection: CustomCollection::class matching:
return [ 'cycle' => [ 'collections' => [ 'factories' => [ 'custom' => [ 'factory' => App\Cycle\Collection\CustomCollectionFactory::class, 'interface' => App\Collection\BaseCollection::class, ], ], ], ], ];
The factory value in the extended definition accepts the same inputs as the short form:
a container service ID, a factory FQCN with a zero-arg constructor, or a direct factory instance.
For ManyToMany pivot entity access specifically, use a collection factory that supports Cycle pivoted collections.
Cycle's Doctrine collection factory provides that support, so install doctrine/collections, register doctrine,
and set the relation collection to doctrine.
Manual mapping key compatibility
Manual schema definitions are configured with cycle.schema.manual_mapping_schema_definitions.
Services
Aliases provided by ConfigProvider:
orm->Cycle\ORM\ORMInterfacedbal->Cycle\Database\DatabaseInterface
Migration aliases provided only when cycle/migrations is installed:
migrator->Sirix\Cycle\Service\MigratorInterface
Repository services
RepositoryFactory exposes a custom Cycle repository as a container service without injecting ORMInterface into application code.
Assign the repository class to an entity in the Cycle schema, register it with the factory, and alias your application interface to it:
use App\Entity\User; use App\Repository\UserRepository; use App\Repository\UserRepositoryInterface; use Cycle\Annotated\Annotation\Entity; use Sirix\Cycle\Factory\RepositoryFactory; #[Entity(repository: UserRepository::class)] final class User {} return [ 'dependencies' => [ 'factories' => [ UserRepository::class => RepositoryFactory::class, ], 'aliases' => [ UserRepositoryInterface::class => UserRepository::class, ], ], ];
The factory resolves the repository class from the compiled Cycle schema and asks Cycle for the corresponding entity role. It supports custom read repositories and repositories that add persist methods through EntityManager.
See the Repository Services guide for read and persist repository examples, manual schema configuration, string roles, aliases, and schema-cache refresh requirements.
CLI commands
Commands are registered only when symfony/console is installed.
cycle:schema:* commands:
cycle:schema:compile: compile schema and store compiled file.cycle:schema:sync: run sync pipeline; refresh compiled file only when cache is enabled.cycle:schema:migration:generate: generate migrations via schema pipeline; available only withcycle/migrationsandcycle/schema-migrations-generator.
Other commands:
cycle:cache:clear: remove compiled schema file.cycle:migration:runcycle:migration:rollbackcycle:migration:createcycle:seed:createcycle:seed:run
Migration/seed command availability:
- registered only when
cycle/migrationsis installed.
Create migration notes
cycle:migration:create supports --database (-b).
Generated filename includes database alias:
<timestamp>_0_<counter>_<database-alias>_<migration_name_in_snake_case>.php
CLI usage examples
With laminas-cli:
php vendor/bin/laminas cycle:schema:compile php vendor/bin/laminas cycle:schema:sync php vendor/bin/laminas cycle:migration:create CreateUsers --database default
With standalone Symfony Console (manual command wiring in your app):
php bin/console cycle:schema:compile
Performance note
Compiled schema is stored as plain PHP and loaded by require, which works well with OPcache and avoids PSR-6 serialization overhead in runtime hot paths.