Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Schema Builder Laravel Package

cycle/schema-builder

Cycle ORM Schema Builder lets you define and configure your ORM and database schema programmatically using annotations or custom generators. Register entities, map fields and columns, link tables, then compile the schema for Cycle ORM.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require cycle/schema-builder
    

    Use 2.12.2 or newer if your schema relies on nullable relation-generated foreign keys shared by multiple relations.

  2. Initialize the Registry:

    use Cycle\Database;
    use Cycle\Schema\Registry;
    
    $dbal = new Database\DatabaseManager(new Config\DatabaseConfig([...]));
    $registry = new Registry($dbal);
    
  3. Define a basic entity:

    $entity = (new Definition\Entity())
        ->setRole('user')
        ->setClass(User::class);
    
    $entity->getFields()
        ->set('id', (new Definition\Field())->setType('primary')->setPrimary(true))
        ->set('name', (new Definition\Field())->setType('string(32)'));
    
    $registry->register($entity);
    $registry->linkTable($entity, 'default', 'users');
    
  4. Compile and apply the schema:

    $schema = (new Compiler())->compile($registry);
    $orm = $orm->withSchema(new \Cycle\ORM\Schema($schema));
    

First Use Case: Quick Table Creation

Use this for rapid prototyping or one-off schema changes without writing migrations. Example:

$registry = new Registry($dbal);
$entity = (new Definition\Entity())
    ->setRole('product')
    ->setClass(Product::class);

$entity->getFields()
    ->set('id', (new Definition\Field())->setType('primary')->setPrimary(true))
    ->set('sku', (new Definition\Field())->setType('string(255)')->setUnique(true))
    ->set('price', (new Definition\Field())->setType('decimal(10,2)'));

$registry->register($entity);
$registry->linkTable($entity, 'default', 'products');

// Generate and apply schema
$schema = (new Compiler())->compile($registry);
$orm->withSchema(new \Cycle\ORM\Schema($schema));

Implementation Patterns

Core Workflow: Declarative Schema Management

  1. Define Entities: Use Definition\Entity to map PHP classes to database tables. Assign a role (e.g., 'user') for schema resolution.

    $entity = (new Definition\Entity())
        ->setRole('post')
        ->setClass(Post::class);
    
  2. Configure Fields: Define columns with Definition\Field, specifying types (string, int, decimal), constraints (primary, unique), and attributes (e.g., default, nullable).

    $entity->getFields()
        ->set('title', (new Definition\Field())->setType('string(255)')->setNullable(false))
        ->set('published_at', (new Definition\Field())->setType('datetime')->setDefault('now()'));
    
  3. Link Tables: Associate entities with database connections and table names.

    $registry->linkTable($entity, 'default', 'posts');
    
  4. Compile and Apply: Use Compiler to generate the schema and integrate it with Cycle ORM.

    $schema = (new Compiler())->compile($registry);
    $orm->withSchema(new \Cycle\ORM\Schema($schema));
    

Advanced Patterns

1. Relationships

Define foreign keys and relationships declaratively:

$authorEntity = (new Definition\Entity())->setRole('author')->setClass(Author::class);
$authorEntity->getFields()->set('id', (new Definition\Field())->setType('primary')->setPrimary(true));

$postEntity->getFields()
    ->set('author_id', (new Definition\Field())
        ->setType('integer')
        ->setForeignKey('author_id', $authorEntity, 'id')
        ->setOnDelete('cascade')
    );

When several relations share the same generated foreign key column, nullability is inferred from those relations. As of 2.12.2, if any relation sharing that FK asks for nullable, the generated FK column is nullable. This fixes cases where one nullable relation could still produce a non-nullable FK column.

2. Indexes and Constraints

Add indexes or constraints programmatically:

$entity->getIndexes()
    ->add('idx_post_title', 'title', 'unique');

$entity->getConstraints()
    ->add('chk_post_price', 'price', '>', 0);

3. Generated Fields

Use Definition\Field::setGenerated() for computed columns:

$entity->getFields()
    ->set('slug', (new Definition\Field())
        ->setType('string(255)')
        ->setGenerated('CONCAT(LOWER(title), "-", id)')
    );

4. Schema Modifiers

Extend functionality with custom modifiers (e.g., for soft deletes):

use Cycle\Schema\Modifier;

class SoftDeleteModifier implements Modifier {
    public function modify(Definition\Entity $entity): void {
        $entity->getFields()->set('deleted_at', (new Definition\Field())
            ->setType('datetime')
            ->setNullable(true)
        );
    }
}

$registry->addModifier(new SoftDeleteModifier());

5. Dynamic Schema Generation

Generate schemas at runtime (e.g., for multi-tenant apps):

$tenantId = 1;
$tenantSchema = (new Compiler())->compile($registry->forTenant($tenantId));

6. Integration with Annotations

Use @Cycle\Annotation attributes for schema definitions (alternative to fluent API):

#[Cycle\Entity(table: 'users')]
class User {
    #[Cycle\Column(type: 'primary')]
    public int $id;

    #[Cycle\Column(type: 'string(32)', nullable: false)]
    public string $name;
}

Configure the registry to parse annotations:

$registry->addParser(new \Cycle\Annotated\Parser());

Gotchas and Tips

Pitfalls

  1. Role Conflicts:

    • Each entity must have a unique role (e.g., 'user'). Reusing roles can cause schema resolution errors.
    • Fix: Use descriptive roles like 'app_user' instead of generic 'user'.
  2. Missing Table Links:

    • Forgetting to call $registry->linkTable() will result in unassigned tables.
    • Fix: Add a validation step before compilation:
      foreach ($registry->getEntities() as $entity) {
          if (!$entity->getTable()) {
              throw new \RuntimeException("Table not linked for entity {$entity->getRole()}");
          }
      }
      
  3. Foreign Key Duplication:

    • If a foreign key’s outer key is also a primary key, indexes may duplicate. Use setNoIndex() to avoid this:
      ->setForeignKey('user_id', $userEntity, 'id')
          ->setNoIndex(true)
      
  4. Shared FK Nullability Across Relations:

    • When multiple relations share the same generated foreign key column, nullable flags can conflict. Prior to 2.12.2, a nullable relation could still produce a non-nullable FK when another relation shared it.
    • Fix: Upgrade to 2.12.2+. Remove manual setNullable(true) workarounds if you added them for this case.
    • Debug: Inspect the compiled schema and confirm the shared FK column is nullable when at least one relation requires it.
  5. PHP 8.4+ Deprecations:

    • Some older code may use deprecated features. Update to the latest version (v2.12.2) for compatibility.
  6. Schema Overwrites:

    • By default, register() merges existing definitions. Use registerChildWithoutMerge() to force overwrite:
      $registry->registerChildWithoutMerge($entity);
      
  7. Composite Primary Keys:

    • Ensure all parts of a composite key are marked as primary:
      $entity->getFields()
          ->set('user_id', (new Definition\Field())->setType('integer')->setPrimary(true))
          ->set('order_id', (new Definition\Field())->setType('integer')->setPrimary(true));
      

Debugging Tips

  1. Print Schema Changes: Use the PrintChanges generator to log schema differences:

    $generator = new \Cycle\Schema\Generator\PrintChanges();
    $generator->generate($registry, $schema);
    
  2. Validate Schema: Check for errors before compilation:

    $validator = new \Cycle\Schema\Validator();
    $errors = $validator->validate($registry);
    if (!empty($errors)) {
        throw new \RuntimeException("Schema validation failed: " . implode(', ', $errors));
    }
    
  3. Inspect Compiled Schema: Dump the compiled schema to debug nullable FK behavior and other generated attributes:

    $compiledSchema = (new Compiler())->compile($registry);
    file_put_contents('schema_dump.json', json_encode($compiledSchema->toArray(), JSON_PRETTY_PRINT));
    

Performance Tips

  1. Batch Operations: Register multiple entities in a single batch to reduce overhead:

    $entities = [
        (new Definition\Entity())->setRole('user')->setClass(User::class)->getFields()->set('id', (new Definition\Field())->setType('primary')),
        // ... other entities
    ];
    foreach ($entities as $entity) {
        $registry->register($entity);
    }
    
  2. Reuse Definitions: Cache compiled schemas if your application has static schemas (e.g., in a monolith):

    $schemaCache = new \Symfony\Component\Cache\SimpleFileCache('/tmp/schema_cache');
    $schema = $schemaCache->get('orm_schema', function() use ($registry) {
        return (new Compiler())->compile($registry);
    });
    
  3. Avoid Redundant Compilation: Compile the schema once (e.g., during bootstrapping) and reuse it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator