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

Laravel Cycle Orm Adapter Laravel Package

wayofdev/laravel-cycle-orm-adapter

Integrates Cycle ORM into Laravel applications. Provides package configuration, service provider bindings, and a bridge between Cycle's data-mapper ORM and Laravel's container and ecosystem, letting you use Cycle entities alongside familiar Laravel tooling.

View on GitHub
Deep Wiki
Context7

Getting Started

Pick the adapter version before anything else. The compatibility matrix is the single most important thing on the README:

Laravel Adapter PHP
10.28 <4.9.0 8.2
10.28, 11 >=4.9.0 8.2
10.28, 11, 12 5.0.0 8.2
11, 12, 13 >5.0.0 8.3

If you're on PHP 8.2 and Laravel 13, you cannot move — 6.x requires PHP 8.3. Pin the constraint explicitly in composer.json rather than letting ^ float across majors.

Install and publish:

composer require wayofdev/laravel-cycle-orm-adapter
php artisan vendor:publish --tag=cycle-config
php artisan list | grep cycle   # see which schema/migration commands this version ships

The package auto-registers its service provider. Cycle reuses your existing Laravel database.connections entries, so there is no second credentials file — that is the nicest part of the onboarding.

Where to look first, in order:

  1. The docs site (laravel-cycle-orm-adapter.wayof.dev) — the README is mostly badges and marketing; the real setup, entity mapping, and command list live in the docs.
  2. The published config file (config/cycle.php in current majors). The keys you actually care about: entity/schema source paths, the proxy/compiled-schema output directory, and cache toggles. The exact key names have changed between majors — read the file you got, not a blog post.
  3. app/Entities + app/Repositories (whatever you name them in config) as the two folders you'll live in.
  4. The container bindings: Cycle\ORM\ORMInterface, Cycle\ORM\EntityManagerInterface, and Cycle\ORM\SchemaInterface are what you type-hint.

First use case: don't migrate the app. Pick one bounded context where the Data Mapper model pays for itself — an audit log, a billing/invoice aggregate, a state-machine entity — and build it with Cycle while everything else stays Eloquent. A minimal vertical slice:

#[Entity]
#[Table('invoices')]
class Invoice
{
    #[Column(type: 'primary')]
    public int $id;

    #[Column(type: 'string')]
    public string $number;

    #[Column(type: 'decimal(10,2)')]
    public string $amount;
}
final class InvoiceRepository extends \Cycle\ORM\Select\Repository
{
    public function findByNumber(string $number): ?Invoice
    {
        return $this->findOne(['number' => $number]);
    }
}
public function __construct(private InvoiceRepository $invoices) {}

public function show(string $number): Invoice
{
    return $this->invoices->findByNumber($number) ?? throw new ModelNotFoundException();
}

If that slice feels natural after a day, the package is a fit. If you find yourself fighting the schema compilation step, stop there — the cost curve gets steeper, not flatter.

Implementation Patterns

Repositories are the read boundary, EntityManager is the write boundary. This is the idiom to enforce on the team. Reads go through injected repositories (which extend Cycle\ORM\Select\Repository and expose named finders); writes go through EntityManagerInterface:

public function handle(CreateInvoice $command): void
{
    $invoice = new Invoice();
    $invoice->number = $command->number;
    $invoice->amount = $command->amount;

    $this->em->persist($invoice);
    $this->em->run();          // nothing is written until this line
}

Wrap multi-entity operations in an explicit transaction ($em->getTransaction() / $em->run() inside a try/catch on your domain exception), because there is no Eloquent-style DB::transaction closure that automatically knows about Cycle's unit of work.

Keep entities Laravel-free. The whole reason to accept the learning curve is that entity classes have zero framework imports — no Model, no HasFactory, no traits. Validate and authorize in Form Requests / Actions, hydrate the entity in a mapper or action, persist. The moment you add Illuminate\* to an entity, you've paid for Data Mapper and gotten ActiveRecord.

Constructor-inject the container bindings, never app(). Bind repositories in a small service provider:

$this->app->bind(InvoiceRepository::class, fn ($app) =>
    $app->make(ORMInterface::class)->getRepository(Invoice::class)
);

Injecting ORMInterface directly into controllers is a smell — it invites ad-hoc getRepository() calls scattered across the codebase and makes it impossible to statically see which aggregates a class touches.

Bridge to Eloquent at explicit seams. Auth, queue payloads, Blade views, and Nova/Filament all expect Eloquent models. Don't fight it: keep User as Eloquent, keep Invoice as a Cycle entity, and convert at the boundary (a small InvoiceResource or read-model). Sharing one table between an Eloquent model and a Cycle entity works until one of them caches schema, and then it doesn't.

Deploy workflow is a real step, not an afterthought. Cycle compiles schema and generates proxy classes to disk. Your deploy script needs: clear the compiled schema/cache, regenerate, and warm it — before the new code serves traffic. Treat it exactly like config:cache and route:cache. Put it in the release script and in CI, not in someone's memory.

Testing. The 4.9.0 changelog added a seed() helper for tests; use the package's testing helpers rather than hand-rolling fixtures. Two patterns that hold up:

  • Feature tests against a real database (SQLite in-memory is fine for simple schemas but will bite you on enums/JSON/defaults) with schema built once per test class.
  • A DatabaseTransactions-equivalent wrapper per test so entity state doesn't leak between tests.

Unit-test repositories against a real DB, not mocks — mocking Select produces tests that pass while the schema is broken.

Strangler adoption. New bounded context → Cycle. Existing CRUD → Eloquent. Migrate a legacy module only when you're already rewriting its logic; a pure "swap the ORM" migration has no payoff and a long tail of surprises around timestamps, soft deletes, and casting.

Gotchas and Tips

persist() without run() is the #1 bug. Everything looks fine, the response is 200, and nothing is in the database. If writes silently vanish, check for a missing run() before you check the connection.

Version-matrix drift is the #1 upgrade pain. The 4.9 → 5.0 → 6.0 jumps each move the PHP/Laravel floor and (in 5.0) fix container binding semantics. Before any Laravel/PHP upgrade, read the changelog for the adapter specifically — a Laravel upgrade that "just works" for Eloquent can break the Cycle provider at boot.

The ORM vs ORMInterface singleton. 5.0 fixed a bug where ORM::class and ORMInterface::class resolved to different instances. On older versions you can get two identity maps in one request and spend an afternoon debugging why a persisted entity isn't visible. If you're pinned below 5.0, resolve one and only one.

Long-running processes: Octane and queue workers. The ORM holds an identity map and a schema. In a worker that processes hundreds of jobs, entities accumulate and stale data can be returned for rows updated by another process. Clear the entity manager at job boundaries (or run Cycle work in a short-lived process). This is the failure mode that shows up in production, not in tests.

Proxy directory permissions and gitignore. Generated proxies must be writable at runtime on some setups and must not be committed. Forgetting the .gitignore entry means merge conflicts in generated PHP; forgetting the permissions means a 500 on first request after deploy, in an environment where nobody can reproduce it locally.

Don't run Cycle migrations and Laravel migrations against the same tables. Pick one owner per table and write it down. The classic incident: php artisan migrate in CI and the Cycle schema sync in the deploy script both trying to own invoices, producing drift that only appears in staging.

Relations don't lazy-load the way you expect. Coming from Eloquent, you'll reach for ->with(). Cycle uses ->load() / explicit select() on the repository, and the N+1 behavior is different. Write the eager-load explicitly in the repository finder, and treat every repository method as the place where loading strategy is decided.

Debugging recipe. When something is wrong, in this order: (1) dump the compiled schema and confirm the entity is registered and columns match the table; (2) confirm the proxy class exists for the entity; (3) confirm the column type mapping (decimal as string, enums, JSON) rather than assuming Eloquent's casting; (4) run the failing query through Cycle's Select directly with logging on. Most issues are schema/mapping, not ORM logic.

Extension points worth knowing: custom Column types (for value objects and enums), a custom repository base class for shared finders, and the container bindings themselves — you can rebind SchemaInterface or wrap EntityManagerInterface with a decorator for logging/metrics without touching the package. These are stable across majors; the config keys are not.

Vendor risk is real but bounded. 34 stars and zero dependents means you are early. The offset: MIT license, active releases (6.x shipped for Laravel 13), documented changelog, and a codebase small enough that a mid-level dev can read the provider and bindings in an hour. Before adopting, skim the service provider and config — if you can't follow them, you're one incident away from being blocked. Pin versions, and be prepared to fork; the surface area you depend on (bindings + commands + config) is small enough that a fork is a weekend, not a quarter.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource