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

Shared Kernel Laravel Package

lin3s/shared-kernel

A lightweight PHP shared kernel offering reusable DDD building blocks such as value objects, identifiers, aggregates, domain events, and specifications for consistent modeling across bounded contexts.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a DDD shared kernel, not a utility library: it is meant to be the small set of building blocks (entities, value objects, aggregate roots, domain events, command/query bus contracts) that several bounded contexts of one product agree on. The current tag is v0.9.8 — still pre-1.0, with the last meaningful activity in the Symfony 4 era. Treat it as a reference implementation you vendor or copy, not a package you upgrade over time.

  1. Inspect before installing. Clone or open the repo and read src/Domain/Model first — it defines the vocabulary you will use every day (ValueObject, Entity, AggregateRoot, DomainEvent, DomainEventStream, identifier/UUID types, possibly Enum/Collection/Specification). Then read src/Application/Command and src/Application/Query (interfaces only). Also look at any Resources/config directory — the v0.9.8 note about Tactician event subscriber definitions confirms the package ships Symfony-flavoured DI service definitions alongside the PHP contracts.
  2. Check the composer.json constraints. A Symfony-4-era package typically pins php: ^7.x and old PHPUnit/Doctrine versions. If you are on PHP 8.1+, either install with --ignore-platform-reqs in a scratch project to evaluate it, or fork and bump constraints.
  3. First use case: model one value object + one aggregate root that records a domain event, then dispatch it through a hand-written command bus. Do not start by wiring a framework — the PHP side of the package stops at interfaces, and the shipped service definitions are Symfony XML/YAML you will not be loading in Laravel.

Minimal smoke test:

composer require lin3s/shared-kernel

// src/Domain/Order/OrderId.php
final class OrderId extends /* package identifier base */ \LIN3S\SharedKernel\Domain\Model\Uuid
{
    public static function generate(): self { return new self(/* uuid */); }
}

// src/Domain/Order/Money.php
final class Money extends \LIN3S\SharedKernel\Domain\Model\ValueObject
{
    private $amount;
    private $currency;
    public function __construct(int $amount, string $currency) { /* guard */ }
    public function equals(ValueObject $other): bool { /* compare */ }
}

Then confirm the package’s equals(), __toString() and identifier factory signatures against the source — they are the contract you will type against in every class.

Implementation Patterns

Value objects everywhere. Extend the package ValueObject, keep properties private and immutable, validate in the constructor (throw domain exceptions), expose only getters and equals(). Do not add setters; create new instances for state change.

Aggregate roots own event recording. Subclass AggregateRoot, call the protected record() (or equivalent — check the exact method name) in named constructors and behaviour methods, and pull events out in the repository or command handler:

final class Order extends AggregateRoot
{
    public static function place(OrderId $id, Money $total): self
    {
        $order = new self($id, $total, OrderStatus::placed());
        $order->record(new OrderPlaced($id, $total));
        return $order;
    }

    public function pay(PaymentId $paymentId): void
    {
        // guard invariants
        $this->record(new OrderPaid($this->id, $paymentId));
    }
}

Dispatch after persistence, not before. The classic workflow: handler invokes the aggregate → repository saves → $events = $order->releaseEvents() → publish through your event bus (or an outbox table) → clear. DomainEventStream from the package is a convenient immutable collection to iterate.

Buses are interfaces — bring your own. Implement CommandBus/QueryBus with a simple class-name→callable map for small apps, or bind them to Laravel’s container/dispatcher. Keep one handler per command class and let the bus do the lookup:

final class InMemoryCommandBus implements CommandBus
{
    /** @var callable[] */
    private $handlers = [];
    public function register(string $commandClass, callable $handler): void
    { $this->handlers[$commandClass] = $handler; }
    public function dispatch(Command $command): void
    { ($this->handlers[get_class($command)] ?? throw new \RuntimeException(...))($command); }
}

The package’s shipped Tactician wiring (the thing v0.9.8 touched) is a Symfony service-definition convenience and is not something a Laravel app will consume; on Laravel you either write the ~40-line in-memory bus above or expose the handler map as tagged bindings in a service provider.

Keep infrastructure out. Doctrine mappings, HTTP controllers, serializers, and framework services belong in your app’s Infrastructure layer, never in the shared kernel or your domain. The kernel is deliberately persistence-agnostic — map aggregates with XML/YAML Doctrine config or your own mapper, or with Eloquent models + a hand-written repository in Laravel.

Per-context use. Because a shared kernel is, by definition, shared by contexts, resist the urge to dump generic helpers (string utils, arrays) into it. Only add types that at least two bounded contexts genuinely share, and version it inside your monorepo rather than via Packagist.

Testing pattern. Assert recorded events directly in unit tests ($order->releaseEvents() after a behaviour call) instead of mocking an event bus. This keeps aggregate tests fast and the bus as a thin integration seam.

Gotchas and Tips

  • Stale but not dead — and the patch proves it. v0.9.8 is a one-line visibility fix ("made Tactician event subscriber definitions public to avoid Symfony 4 issues"), not a maintenance release. Nothing about PHP 8.2+, Laravel 10/11 or modern Doctrine is addressed. Audit for Serializable, implicit nullable parameters, missing return types on interfaces, and #[\ReturnTypeWillChange] warnings before adopting, or fork it (MIT licence permits this) and pin your own tag.
  • Partly wired, but only for Symfony. The release note reveals the package does ship Tactician event-subscriber service definitions (previously declared private, which broke container compilation on Symfony 4). Do not read that as "framework integration included": there is no Laravel service provider, no auto-discovery, and no tagged handler registration. Anything you get from those definitions you would have to port to config/*.php by hand.
  • Don't upgrade blindly for v0.9.8. The diff only changes service visibility; PHP-level behaviour is unchanged. If you had vendored an older Resources/config, diff the two and apply the public: true change locally, otherwise a copied config will keep the original container-compilation error.
  • Interfaces with no implementations. If you call new CommandBus(...) you will fail — the package ships contracts. Budget a day to write the in-memory/Laravel adapter and its DI wiring.
  • Ambiguous equals() semantics. Value objects often compare with == or via get_class checks; verify strict vs loose comparison, especially with floats/Money. Write an explicit equality test per value object.
  • Identifier collisions. If the kernel provides Uuid/Id base classes, extend rather than duplicate; two UUID generators in one codebase produce inconsistent string formats in URLs and DB columns.
  • Event ordering and immutability. releaseEvents() usually clears the internal buffer. If you need events twice (e.g. sync publisher + outbox), copy them into your own array before releasing, or wrap publishing in a transaction so a failed dispatch doesn’t silently drop events.
  • Serialization for queues/outbox. Domain events are plain objects; if they cross a queue boundary, add your own serializer (JSON with an event-type discriminator + reconstructor registry) and test round-trips. Do not serialize() aggregates.
  • Naming footprint. LIN3S\SharedKernel\... namespaces leak into every domain file. If you fork, consider keeping the namespace to avoid churn, and document the fork in your README so future devs don’t composer update into a conflict.
  • When to skip it. For a single bounded context, this package adds indirection without payoff. Use it only when you actually have multiple contexts that must share domain vocabulary — otherwise copy the two or three base classes you need into src/Shared/Domain.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo