一键导入
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 页面并帮你完成安装。
Multi-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
基于 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:clear