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

Object Reference Bundle Laravel Package

arthem/object-reference-bundle

Doctrine ORM bundle that stores references to any entity as a type and ID pair, using the ObjectReference attribute and lazy closures so relations stay lightweight and framework-agnostic.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require arthem/object-reference-bundle
    

    Ensure your project uses Symfony 6.3+ or 7.x and Doctrine ORM 2.5+.

  2. Enable the Bundle: Add to config/bundles.php:

    return [
        // ...
        Arthem\ObjectReferenceBundle\ArthemObjectReferenceBundle::class => ['all' => true],
    ];
    
  3. First Use Case: Define an entity with a polymorphic reference using the #[ObjectReference] attribute:

    use Arthem\ObjectReferenceBundle\Mapping\Attribute\ObjectReference;
    
    #[ORM\Entity]
    class Story
    {
        #[ORM\Column(type: Types::STRING, length: 36, nullable: true)]
        #[ObjectReference(keyLength: 15)]
        private \Closure|Actor $actor;
    
        private $actorId; // Required, even if unused
        private $actorType; // Required, even if unused
    
        // Getter/setter logic as shown in README
    }
    
  4. Database Migration: Run:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    

Implementation Patterns

Common Workflows

1. Polymorphic Relationships

Use ObjectReference for polymorphic associations (e.g., Actor could be User, Guest, or custom types):

#[ObjectReference(keyLength: 15)]
private \Closure|(User|Guest) $actor;

2. Lazy-Loading Closures

Leverage closures for deferred loading (e.g., fetching related data only when accessed):

public function getActor(): ?User
{
    if ($this->actor instanceof \Closure) {
        $this->actor = $this->actor->call($this); // Resolves on first access
    }
    return $this->actor;
}

3. Integration with Forms

Bind polymorphic fields in Symfony forms:

use Symfony\Bridge\Doctrine\Form\Type\EntityType;

$builder->add('actor', EntityType::class, [
    'class' => [User::class, Guest::class],
    'choice_label' => 'name',
    'placeholder' => 'Select an actor...',
]);

4. Repository Methods

Query polymorphic references in repositories:

public function findByActorType(string $type, string $id)
{
    return $this->createQueryBuilder('s')
        ->andWhere('s.actorType = :type')
        ->andWhere('s.actorId = :id')
        ->setParameter('type', $type)
        ->setParameter('id', $id)
        ->getQuery()
        ->getResult();
}

5. Custom Key Generation

Override default key generation (e.g., UUIDs) by extending the bundle or using a custom closure:

#[ObjectReference(keyLength: 36, keyGenerator: new \Ramsey\Uuid\Uuid())]
private \Closure|Actor $actor;

Integration Tips

Doctrine Events

Listen for lifecycle events to manage closures:

use Doctrine\ORM\Events;

$entityManager->getEventManager()->addEventListener(Events::postLoad, function ($event) {
    $entity = $event->getObject();
    if (method_exists($entity, 'initializeClosures')) {
        $entity->initializeClosures();
    }
});

API Serialization

Exclude closures from serialization (e.g., API Platform):

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;

#[ApiResource]
class Story
{
    #[ApiProperty(serialize: false)]
    private \Closure|Actor $actor;
}

Testing

Mock closures in unit tests:

$story = new Story();
$story->setActor(function () {
    return $this->createMock(Actor::class);
});

Gotchas and Tips

Pitfalls

1. Missing actorId/actorType Fields

  • Error: ObjectReference requires these fields, even if unused. Doctrine will throw errors if omitted.
  • Fix: Always declare them as private properties.

2. Closure Serialization

  • Error: Closures may not serialize/deserialize correctly (e.g., in API responses or Doctrine hydration).
  • Fix: Use serialize: false in API Platform or implement __serialize()/__unserialize() in your entity.

3. Key Length Mismatch

  • Error: If keyLength in #[ObjectReference] doesn’t match the column length, Doctrine may fail to map data.
  • Fix: Ensure Types::STRING column length ≥ keyLength.

4. Circular References

  • Error: Polymorphic closures in circular references (e.g., Story → Actor → Story) can cause infinite loops.
  • Fix: Use lazy-loading closures and validate cycles in getActor():
    if ($this->actor instanceof \Closure && $this->isInCycle()) {
        return null; // or throw an exception
    }
    

5. Doctrine Proxy Issues

  • Error: Proxies may not handle closures correctly during lazy loading.
  • Fix: Initialize closures in postLoad (as shown in integration tips) or use #[ORM\HasLifecycleCallbacks].

Debugging Tips

1. Log Closure Resolution

Add debug logs to track when closures are resolved:

public function getActor(): ?Actor
{
    if ($this->actor instanceof \Closure) {
        \Log::debug('Resolving actor closure for Story ID: ' . $this->id);
        $this->actor = $this->actor->call($this);
    }
    return $this->actor;
}

2. Validate Entity State

Check entity state after hydration:

$entityManager->getEventManager()->addEventListener(Events::postLoad, function ($event) {
    $entity = $event->getObject();
    if ($entity instanceof Story && $entity->getActor() instanceof \Closure) {
        throw new \RuntimeException('Actor closure not resolved!');
    }
});

3. Query Debugging

Enable Doctrine query logging:

# config/packages/dev/doctrine.yaml
doctrine:
    dbal:
        logging: true
        profiling: true

Extension Points

1. Custom Key Generators

Extend the bundle to support custom key generators:

// src/KeyGenerator/CustomUuidGenerator.php
namespace App\KeyGenerator;

use Arthem\ObjectReferenceBundle\KeyGenerator\KeyGeneratorInterface;
use Ramsey\Uuid\Uuid;

class CustomUuidGenerator implements KeyGeneratorInterface
{
    public function generate(object $object): string
    {
        return Uuid::uuid4()->toString();
    }
}

Use it in your entity:

#[ObjectReference(keyGenerator: new \App\KeyGenerator\CustomUuidGenerator())]

2. Event Subscribers

Listen for object_reference.load events to intercept closure resolution:

use Arthem\ObjectReferenceBundle\Event\ObjectReferenceEvents;

$eventDispatcher->addListener(ObjectReferenceEvents::LOAD, function ($event) {
    if ($event->getObject() instanceof Story) {
        $event->setResolvedObject($event->getClosure()->call($event->getObject()));
    }
});

3. Custom Type Mapping

Override the default Types::STRING mapping for specific use cases:

// src/DependencyInjection/Configuration.php
$builder->node('object_reference')
    ->children()
        ->scalarNode('default_type')->defaultValue('string')->end()
    ->end();

4. Validation Constraints

Add validation for polymorphic references:

use Symfony\Component\Validator\Constraints as Assert;

class Story
{
    #[Assert\Valid]
    #[ObjectReference]
    private \Closure|Actor $actor;
}
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style