sirix/cycle-orm-factory

Cycle ORM Factories for Mezzio

Maintainers

Package info

github.com/sirix777/cycle-orm-factory

pkg:composer/sirix/cycle-orm-factory

Transparency log

Fund package maintenance!

sirix777

buymeacoffee.com/sirix

Statistics

Installs: 1 389

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

4.2.1 2026-08-13 08:38 UTC

README

Latest Stable Version Total Downloads Latest Unstable Version License PHP Version Require

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 for cycle:schema:migration:generate.
  • cycle/entity-behavior and cycle/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 require and 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:compile during 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\ORMInterface
  • dbal -> 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 with cycle/migrations and cycle/schema-migrations-generator.

Other commands:

  • cycle:cache:clear: remove compiled schema file.
  • cycle:migration:run
  • cycle:migration:rollback
  • cycle:migration:create
  • cycle:seed:create
  • cycle:seed:run

Migration/seed command availability:

  • registered only when cycle/migrations is 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.

More