Skip to content

Laravel Comparison

LaraFly is additive: it layers a Spring-Boot-shaped application model on top of Laravel 13 — it never forks, wraps, or replaces the framework underneath. Every LaraFly capability is a normal Composer package registering normal Laravel service providers; a LaraFly app is still, in every respect a Laravel developer would recognize, a Laravel app. This page is the same kind of comparison pyfly's Spring Boot comparison draws for Python, mapped onto Laravel instead.

At a glance

Concern Raw Laravel LaraFly
Entry point Service providers registered in bootstrap/providers.php, wired by hand The same providers, plus auto-discovered AutoConfiguration classes assembled by a kernel-decided BootPass pipeline
Dependency injection app()->bind()/app()->singleton() in a provider's register() #[Component]/#[Service]/#[Repository]/#[Configuration] stereotypes on the class itself; compiled component scan resolves constructor dependencies
Configuration config('mail.host') (array access, untyped) #[ConfigProperties] DTOs bound from a config subtree — typed, fail-fast on a missing/mismatched key
HTTP routing routes/web.php/routes/api.php route files #[RestController] (JSON) / #[Controller] (HTML) + verb attributes (#[GetMapping], …), compiled to a RouteManifest, still dispatched through native Laravel routes
Validation FormRequest::rules() (array rules) #[Valid] parameter interception over a Bean-Validation-style constraint model, still backed by Laravel's validator
Transactions DB::transaction(fn () => …) (closure-scoped) #[Transactional] on a class/method — declarative propagation/isolation/rollback rules, manual beginTransaction/commit/rollBack under the hood
Events Event::listen() / #[AsEventListener]-style Laravel listeners, in-process only Two distinct surfaces: the in-process bus (#[AsEventListener]) and a broker-backed EDA bus (#[EventListener]) — see below
CQRS Not a first-class concept — Laravel has no command/query bus CommandBus/QueryBus with a bounded pipeline (validate → authorize → invoke → metrics) and #[CommandHandler]/#[QueryHandler]
Security Auth guards + route middleware (->middleware('auth'), Gate::allows()) Spring-Security-6-shaped SecurityContext + deny-by-default HttpSecurity URL DSL + method security (#[PreAuthorize]/#[Secured]/#[RolesAllowed])
Operations Nothing built in — health checks and metrics are typically bespoke or a package firefly/actuator (health/info/env/beans/mappings) + firefly/observability (Prometheus/Micrometer-JSON metrics), both Spring Boot Actuator/Micrometer analogues

Entry point: service providers vs. auto-configuration

Laravel boots by registering whatever service providers bootstrap/providers.php lists, in the order they appear (or, for package-discovered providers, roughly alphabetically). LaraFly's firefly/context sits on top of that: every capability package's provider still exists and is still discovered by Laravel exactly the same way — but its register() method does nothing except buffer its BootPass contributions into a PendingBootPasses collector. The shared FireflyKernel drains that buffer once it is actually resolved and runs each phase (scanning, condition evaluation, bean registration, wiring passes) in a kernel-decided order — so which provider Laravel happened to instantiate first is irrelevant. See Architecture for the full pipeline diagram.

Dependency injection: app()->bind() vs. stereotypes

Laravel's container is powerful but explicit — a binding lives in a provider, separate from the class it binds. LaraFly's firefly/container layers PHP 8 attributes onto Illuminate\Container: mark the class itself with #[Component] (or #[Service]/#[Repository]/#[Configuration], all specializations of it) and a compiled component scan registers it, resolves constructor dependencies by type, and honors #[Primary]/#[Qualifier]/#[Order] for multi-implementation ports — see Dependency Injection. Nothing stops you from also using app()->bind() directly; the two coexist.

Configuration: config() vs. #[ConfigProperties]

config('mail.port') is untyped array access — a typo or a missing key returns null silently. LaraFly's firefly/config adds a fail-fast typed accessor ($config->int('mail.port', 25), throws on a required key that's absent or the wrong type) and #[ConfigProperties('mail')] DTOs that bind a whole config subtree onto a plain readonly class at once — see Configuration. Laravel's config/*.php files remain the source of truth; LaraFly reads them, it doesn't replace them.

HTTP: route files vs. #[RestController]

Laravel routes live in routes/*.php, separate from the controller class. firefly/web's #[RestController] + #[GetMapping]/#[PostMapping]/etc. attributes put the route on the controller method itself; a RouteScanner compiles them into a RouteManifest at cache time (or scans in-process when there is no cache), and that manifest is what actually registers native Laravel routes at boot — there is no custom dispatch mechanism underneath. #[Controller] is the HTML sibling: same routing, but a returned View/ModelAndView/Htmlable renders as text/html instead of negotiating to JSON. See Web Layer.

Validation: FormRequest vs. #[Valid]

A Laravel FormRequest::rules() returns an array of string/rule-object rules, evaluated when the request is resolved. firefly/validation's #[Valid] (paired with firefly/web's #[RequestBody]) intercepts a method parameter and runs it through the same validator — the difference is where the rule set lives (a validate() call or a rule class, not a rules() array method) and that failures render as RFC-7807 ProblemDetails instead of Laravel's default redirect/JSON-errors response. See Validation.

Data & transactions: DB::transaction() vs. #[Transactional]

DB::transaction(fn () => …) scopes a transaction to a closure — propagation and rollback rules are whatever you write inline. firefly/data's #[Transactional] attribute (a Spring @Transactional analog) declares propagation (REQUIRED, REQUIRES_NEW, NESTED, …), isolation, read-only, and rollback/no-rollback exception lists on the class or method itself; a proxy generated at scan time drives the boundary through manual beginTransaction()/commit()/rollBack() so a caught exception can still be committed when it matches noRollbackFor. See Transactions.

Events: one Laravel surface vs. two LaraFly surfaces

Laravel has one event mechanism: Event::dispatch()/listeners, in-process, synchronous by default. LaraFly keeps that surface (#[AsEventListener], from firefly/context) and adds a second, unrelated one: firefly/eda's broker-backed bus, where #[EventListener] subscribes to an event-type pattern ('user.*') rather than a PHP class, and delivery can be in-memory, queued (async, cross-process), or — via the SP-4 broker adapters — RabbitMQ/Postgres/Kafka. These are deliberately not the same thing; see Event-Driven Architecture § Two event surfaces — not one for the full distinction, and CQRS for how a committed domain event bridges onto the EDA bus as an integration event.

CQRS: a concept Laravel doesn't have

Laravel has no built-in command/query bus — a "command" in Laravel usually just means an Artisan console command. firefly/cqrs adds a genuine CQRS mediator: CommandBus::send()/QueryBus::ask(), each running a bounded pipeline (correlate → validate → authorize → invoke → metrics, plus a cache stage on the query side), with handlers discovered via #[CommandHandler]/#[QueryHandler]. A #[Transactional] command handler gets transaction semantics for free from the firefly/data proxy — the CQRS layer writes no interception code of its own. See CQRS (Command/Query).

Security: middleware vs. deny-by-default HttpSecurity

Laravel's default posture is permissive-by-default: a route is public unless you attach ->middleware('auth') or an ability check. firefly/security inverts that: the HttpSecurity URL DSL builds an ordered rule list and denies anything unmatched (401 anonymous, 403 authenticated) — the same deny-by-default model as Spring Security. On top of that, method security (#[PreAuthorize], #[Secured], #[RolesAllowed]) is enforced at the CQRS bus and the controller dispatcher via a whitelist expression evaluator (no eval), and a first-party SecurityContext/Authentication principal model backs both JWT and OAuth2-resource-server authentication. Laravel's own auth guards and middleware still work underneath — firefly/security is a stricter layer atop them, not a fork. See Security.

Operations: actuator & observability

Laravel ships no health-check or metrics endpoint out of the box — most teams either hand-roll one or reach for a package. firefly/actuator (health/info/env/beans/conditions/mappings/loggers/scheduledtasks under /actuator, secured entirely by ordinary HttpSecurity config) and firefly/observability (a first-party Prometheus-0.0.4 + Micrometer-JSON /metrics exposition, HTTP auto-instrumentation, and the real CqrsMetrics recorder) are the Spring Boot Actuator and Micrometer analogues, respectively — both opt-in Composer packages, both secure-by-default. See Actuator and Observability.