원클릭으로
cycle-orm
Cycle ORM patterns, configuration and common pitfalls for Symfony integration
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Cycle ORM patterns, configuration and common pitfalls for Symfony integration
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
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