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

Lucid Laravel Package

lucidarch/lucid

Lucid is a Laravel package for building scalable, maintainable apps with Domain-Driven Design and Clean Architecture. It provides Artisan commands to generate domains, actions, services, repositories, and tests, keeping business logic organized.

View on GitHub
Deep Wiki
Context7

Getting Started

Lucid is not a package you "add" to an existing app so much as an application shell. It scaffolds a feature-oriented src/ tree and gives you generators that place every class in the right layer of the right feature.

Install and scaffold:

composer require lucidarch/lucid
php artisan lucid:install        # publishes config/lucid.php and scaffolds src/
composer dump-autoload -o        # required: install adds the PSR-4 mapping for src/

lucid:install is the entry point. It creates the directory structure, publishes config/lucid.php, and (depending on your version) wires the PSR-4 autoload entry so Lucid\Components\Foo\... resolves to src/Components/Foo/.... If you are merging into a hand-built composer.json, confirm the autoload block manually — a missing entry here is the single most common "class not found" cause with this package.

Where to look first:

  1. config/lucid.php — namespace root, test paths, and generator defaults. Read this before writing any code.
  2. src/Components/ — one folder per feature ("unit"). This is the primary navigation surface.
  3. src/Foundation/ — cross-cutting providers, exceptions, helpers, and anything that does not belong to a single feature.
  4. A generated feature folder — the layered layout (Console/, Database/, Domain/, Http/, Tests/) is the contract you are agreeing to; learn it once and every feature looks the same.

First use case — a CRUD feature end-to-end:

php artisan make:feature Blog
php artisan make:model Blog/Post
php artisan make:service Blog/CreatePost
php artisan make:controller Blog/PostController
php artisan make:request Blog/StorePostRequest
php artisan make:resource Blog/PostResource

Then wire a route in the feature's route file (normally under src/Components/Blog/Http/routes.php or registered from the feature's service provider), inject CreatePost into the controller, and delegate. The point of the scaffolding is that "where does this class go?" is answered for you every time — fight that and you lose the benefit.

Implementation Patterns

Feature as the unit of change. Group code by what the app does, not by what kind of file it is. Blog, Billing, Authentication, Notifications — each owns its models, migrations, controllers, tests, and routes. When you cannot name a feature without "and", it is probably two features. New work almost always starts with make:feature or an existing feature folder, never with a folder under app/.

Thin controller → request → service → model. Controllers validate via a Form Request, hand off to a single service, and return a Resource. Keep controllers to routing, authorization, and response shaping. All decision logic lives in the service:

final class CreatePost
{
    public function __construct(private Post $post) {}

    public function execute(array $attributes): Post
    {
        return $this->post->create($attributes);
    }
}

One service per operation, not per entity. CreatePost, PublishPost, ArchivePost — not PostService with ten methods. This is the pattern that makes Lucid hold up as the app grows: a service name is a verb you can search for, and a diff on a single file tells you exactly what changed in a workflow. It also gives you a natural seam to wrap in a transaction, event, or job later.

Jobs wrap services. A queued job should do nothing but resolve its service and call execute(). This keeps retry semantics and side effects identical between sync and async paths, and it means the service can be unit-tested without touching the queue.

Domain models stay anemic. Models carry relationships, casts, accessors, and scopes — not business rules. If a model needs an if, that decision probably belongs in a service or a value object under Domain/Values. Value objects (money, slugs, statuses) are the right home for invariants.

Repositories are opt-in. Lucid supports them, but the useful rule is: add a repository when a query is complex or reused across services, not by default. An unnecessary repository layer is pure indirection and one of the fastest ways to make a clean architecture feel heavy.

Policies, requests, resources live with the feature. Authorization for Blog belongs in Blog/Policies, not in a global Policies/ folder. Anything genuinely shared — base exceptions, common traits, cross-feature contracts — moves up to src/Foundation/.

Monolith now, services later. Features are shaped like service boundaries: each one has its own models, migrations, and routes. Extraction is mechanical (move the folder, move the migrations, point the gateway at it) rather than archaeological. If you are targeting microservices, keep features talking to each other only through their services or events — never reach into another feature's models.

Incremental adoption. In a legacy Laravel app, do not rewrite. Add src/, pick one feature, build it entirely in Lucid, and route traffic to it from the existing app. Over time the app/ layer shrinks. Doing a big-bang migration to a new structure is how teams abandon architectures.

Gotchas and Tips

  • Autoload is the #1 failure. After lucid:install, run composer dump-autoload. If you change the namespace in config/lucid.php, dump again and clear the config cache. Class "Lucid\Components\Foo\..." not found is almost always a missing or stale PSR-4 entry, not a missing file.
  • Config cache in production. Because Lucid reads its namespace and paths from config, php artisan config:cache can bake in stale values after a deploy that changed config. Standard fix: run config:clear then config:cache in your deploy script, in that order.
  • Route registration is per-feature. Lucid does not magically pick up every routes file. Each feature's service provider (or a central router you maintain) registers its own routes, usually with an explicit prefix and middleware group. If a route 404s, check that the provider is registered and that the feature's route file is actually included — not that the controller is wrong.
  • Keep the provider registered. Feature service providers need to be listed (typically in the app's provider list or the package's auto-discovery mechanism). A feature that generates fine but behaves as if its bindings do not exist is a provider-registration problem.
  • Generator naming is path-sensitive. Commands take Feature/Class — make:service Blog/PublishPost writes to Components/Blog/Domain/Services/PublishPost.php. A typo in the feature segment creates a new feature folder silently rather than failing. Always git status after generating a batch.
  • Watch the src/ namespace collision. If you also have an src/ (or app/) PSR-4 root, you can end up with two mappings for the same prefix. Keep Lucid's root distinct and confirmed in composer.json.
  • Tests need path configuration. Lucid's generated tests live in the feature tree, so phpunit.xml must include src/Components (or wherever your root is) in its testsuite. New tests silently not running is a common half-hour debug.
  • Service granularity creep. The pattern invites one class per operation; taken too far you get fifty two-line services. A good sanity check: if the service has no branching, no transaction, and no event, it can probably just live on the model.
  • Debugging. php artisan about confirms the app boots cleanly. composer dump-autoload -o surfaces namespace mistakes immediately (optimized autoload validates class maps). php artisan route:list confirms per-feature routes are registered. Reach for these three before reading source.
  • Extension points. To customize generated stubs, publish the package views/stubs and edit them; to change behavior globally, use config/lucid.php before you fork anything. Feature-level service providers are the clean place to bind interfaces to implementations — bind cross-feature contracts there, not in a global service provider.
  • Onboarding payoff is real, so protect it. The architecture's whole value proposition is "any file can be found in under thirty seconds." That only holds if everyone respects the layer boundaries. A code review rule of "no queries in controllers, no business logic in models" preserves the investment better than any tooling.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib