con un clic
cycle-orm
Cycle ORM patterns, configuration and common pitfalls for Symfony integration
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Cycle ORM patterns, configuration and common pitfalls for Symfony integration
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
| name | cycle-orm |
| description | Cycle ORM patterns, configuration and common pitfalls for Symfony integration |
| license | MIT |
| compatibility | opencode |
| metadata | {"type":"orm","framework":"cycle-orm","language":"php"} |
docs/adrs/ADR-007-cycle-orm-over-doctrine.mdCycle ORM se usa SOLO en la capa de Infrastructure. El Domain NO conoce el ORM.
Domain/ -> Entidades puras, Repository interfaces
Infrastructure/
Persistence/
Cycle/
Entity/ -> Entidades Cycle (anémicas, propiedades públicas)
Repository/ -> Implementaciones de repos del Domain
Mapper/ -> Conversión Domain <-> Cycle Entity
OrmFactory.php
DatabaseFactory.php
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Entity;
#[Entity(table: 'categories')]
class CategoryEntity
{
#[Column(type: 'uuid', primary: true)]
public string $id = '';
#[Column(type: 'string(50)')]
public string $name = '';
#[Column(type: 'integer', default: 0)]
public int $displayOrder = 0;
}
// SIN typecast: devuelve string crudo, NO array
#[Column(type: 'json')]
public array $pictogramIds = []; // BUG: sera string
// CON typecast: devuelve array correctamente
#[Column(type: 'json', typecast: 'json')]
public array $pictogramIds = []; // CORRECTO: sera array
use Cycle\Annotated\Annotation\Relation\BelongsTo;
#[BelongsTo(target: CategoryEntity::class, innerKey: 'categoryId')]
public ?CategoryEntity $category = null;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Select\Repository;
final class CycleCategoryRepository implements CategoryRepository
{
/** @param Repository<CategoryEntity> $repository */
public function __construct(
private readonly Repository $repository,
private readonly EntityManagerInterface $entityManager
) {}
public function findById(CategoryId $id): ?Category
{
$entity = $this->repository->findByPK($id->value());
if (!$entity instanceof CategoryEntity) {
return null;
}
return CategoryMapper::toDomain($entity);
}
public function save(Category $category): void
{
$entity = CategoryMapper::toEntity($category);
$this->entityManager->persist($entity);
$this->entityManager->run();
}
}
use Cycle\Database\Injection\Fragment;
// Para funciones SQL como LOWER, UPPER, COALESCE:
$entities = $this->repository
->select()
->where(new Fragment('LOWER("label") LIKE ?', "%{$query}%"))
->limit($limit)
->fetchAll();
final class CategoryMapper
{
public static function toDomain(CategoryEntity $entity): Category
{
return new Category(
CategoryId::fromString($entity->id),
$entity->name,
$entity->icon,
$entity->colorHex,
$entity->displayOrder
);
}
public static function toEntity(Category $domain): CategoryEntity
{
$entity = new CategoryEntity();
$entity->id = $domain->id()->value();
$entity->name = $domain->name();
// ... mapear todos los campos
return $entity;
}
}
services:
# 1. DatabaseManager (factory)
Cycle\Database\DatabaseManager:
factory: ['App\Infrastructure\Persistence\Cycle\DatabaseFactory', 'create']
arguments: ['%env(DATABASE_URL)%']
# 2. ORM (factory)
Cycle\ORM\ORM:
factory: ['App\Infrastructure\Persistence\Cycle\OrmFactory', 'create']
arguments:
- '@Cycle\Database\DatabaseManager'
- '%kernel.project_dir%/src/Infrastructure/Persistence/Cycle/Entity'
# 3. EntityManager
Cycle\ORM\EntityManagerInterface:
class: Cycle\ORM\EntityManager
arguments: ['@Cycle\ORM\ORM']
# 4. Repositorios Cycle (factory desde ORM)
cycle.repository.category:
class: Cycle\ORM\Select\Repository
factory: ['@Cycle\ORM\ORM', 'getRepository']
arguments: [App\Infrastructure\Persistence\Cycle\Entity\CategoryEntity]
# 5. Wrapper repository con inyección nombrada
App\Infrastructure\Persistence\Cycle\Repository\CycleCategoryRepository:
arguments:
$repository: '@cycle.repository.category'
$entityManager: '@Cycle\ORM\EntityManagerInterface'
services:
App\Domain\Category\Repository\CategoryRepository:
alias: App\Infrastructure\Persistence\Cycle\Repository\CycleCategoryRepository
public: true
Problema: #[Column(type: 'json')] sin typecast devuelve string crudo.
Solución: Siempre añadir typecast: 'json':
#[Column(type: 'json', typecast: 'json')]
public array $data = [];
Problema: ->where('LOWER(label)', 'LIKE', $query) genera SQL inválido.
Solución: Usar Fragment:
->where(new Fragment('LOWER("label") LIKE ?', "%{$query}%"))
Problema: Default en PHP difiere del default en la BD. Solución: Definir en AMBOS lugares:
// En la entidad Cycle:
#[Column(type: 'string(7)', default: '#6B7280')]
public string $colorHex = '#6B7280'; // AMBOS deben coincidir
// En la migración SQL:
// ALTER TABLE ... ADD COLUMN color_hex VARCHAR(7) DEFAULT '#6B7280'
Problema: Cycle repositories no son autowireables (Symfony no sabe crearlos).
Solución: Registrar como factory en cycle.yaml:
# 1. Crear el repo Cycle vía factory del ORM
cycle.repository.pictogram:
class: Cycle\ORM\Select\Repository
factory: ['@Cycle\ORM\ORM', 'getRepository']
arguments: [App\Infrastructure\Persistence\Cycle\Entity\PictogramEntity]
# 2. Inyectar en el wrapper
App\Infrastructure\Persistence\Cycle\Repository\CyclePictogramRepository:
arguments:
$repository: '@cycle.repository.pictogram'
$entityManager: '@Cycle\ORM\EntityManagerInterface'
# 3. Alias a la interfaz del Domain
App\Domain\Pictogram\Repository\PictogramRepository:
alias: App\Infrastructure\Persistence\Cycle\Repository\CyclePictogramRepository
Problema: Nueva entidad Cycle no es detectada por el ORM. Solución: Verificar que:
OrmFactory (src/Infrastructure/Persistence/Cycle/Entity)#[Entity(table: 'xxx')]php bin/console cache:clearProblema: $entityManager->persist($entity) sin efecto.
Solución: Siempre llamar run() después:
$this->entityManager->persist($entity);
$this->entityManager->run(); // NECESARIO: flush al DB
Infrastructure/Persistence/Cycle/Entity/Infrastructure/Persistence/Cycle/Mapper/Domain/.../Repository/Infrastructure/Persistence/Cycle/Repository/config/packages/cycle.yamlconfig/packages/repositories.yamlcache:clearMulti-provider LLM integration for phrase generation (Groq, OpenAI, Gemini, extensible)
Docker with multi-stage builds, security best practices and Docker Compose
Symfony framework with PestPHP testing and Clean Architecture patterns
WCAG 2.2 AA compliance for SAAC applications
Symfony with Pest PHP testing framework and TDD patterns
PostgreSQL database configuration and best practices