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

Micro Framework Bundle Laravel Package

gnugat/micro-framework-bundle

A lightweight Symfony bundle for bootstrapping micro-frameworks and small PHP applications. It provides a minimal kernel and configuration so you can get routing and services running quickly, ideal for prototypes, simple APIs, and tiny services built on Symfony components.

View on GitHub
Deep Wiki
Context7

Getting Started

gnugat/micro-framework-bundle is not a framework. It is a ~200-line bundle that wires the bare Symfony request→response loop: EventDispatcher, Router, HttpKernel, ControllerResolver, ArgumentResolver, and the RouterListener. That's it. You are trading symfony/framework-bundle for a container you can read in one sitting.

Requirements (v0.8.0). PHP 8.0+ and Symfony 6.x. The 0.8.0 release added Symfony 6 / PHP 8 support and dropped Symfony 5 and PHP 7 — so ^0.7 is the last line that runs on Symfony 5, and nothing here runs on Symfony 7 yet. Check composer.json before you start; on Symfony 7 expect to edit constraints or fork.

Install

composer require gnugat/micro-framework-bundle

Do not require symfony/framework-bundle alongside it. If a transitive dependency demands it, Composer will fail — the usual (dishonest) workaround is a replace entry in your root composer.json, which silences the resolver but does not give you the services anything expects. There is no Flex recipe; you register the bundle by hand.

Minimal kernel

<?php
// src/Kernel.php
namespace App;

use Gnugat\MicroFrameworkBundle\MicroFrameworkBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    public function registerBundles(): iterable
    {
        return [new MicroFrameworkBundle()];
    }

    public function registerContainerConfiguration(LoaderInterface $loader): void
    {
        $loader->load($this->getProjectDir().'/config/services.yaml');
    }
}

Front controller

<?php
// public/index.php
use App\Kernel;
use Symfony\Component\HttpFoundation\Request;

require dirname(__DIR__).'/vendor/autoload.php';

$kernel   = new Kernel($_SERVER['APP_ENV'] ?? 'prod', (bool) ($_SERVER['APP_DEBUG'] ?? false));
$request  = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

You can swap this for symfony/runtime (require dirname(__DIR__).'/vendor/autoload_runtime.php'; return fn (array $context) => new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);) if you want the standard-skeleton ergonomics — the bundle doesn't care, but nothing configures it for you either.

Services and routes

# config/services.yaml
services:
    _defaults: { autowire: true, autoconfigure: true, public: false }

    App\:
        resource: '../src/*'
        exclude: '../src/Kernel.php'

    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']
# config/routes.yaml
health:
    path: /health
    controller: App\Controller\HealthController
final class HealthController
{
    public function __invoke(): JsonResponse
    {
        return new JsonResponse(['ok' => true]);
    }
}

Where to look first. Read vendor/gnugat/micro-framework-bundle/Resources/config/services.xml and DependencyInjection/MicroFrameworkExtension.php. That is the entire package. Whatever service ID you don't see there, you don't have, and you must register yourself.

First use case. A single-purpose internal API: a webhook receiver, a health/readiness endpoint, a queue consumer's HTTP face, a Lambda/FaaS handler, or a sidecar service. Anything where boot time and container size matter more than batteries.

Implementation Patterns

Everything is a service, nothing is magic. There is no AbstractController, so no $this->json(), $this->render(), or $this->redirectToRoute(). Return new JsonResponse(...), new Response(...), or new RedirectResponse($urlGenerator->generate('name')) directly. Inject what you need through the constructor — this is closer to Laravel's invokable controllers than to its resource controllers. On PHP 8 you can and should use constructor property promotion and readonly-style discipline (readonly properties themselves need 8.1, which you may have even though the package floor is 8.0).

Routing. YAML (or PHP array) loaders work out of the box. Attribute routing does not: #[Route] is resolved by loader classes that FrameworkBundle normally registers. Symfony 6 ships a perfectly good AttributeDirectoryLoader, but the wiring is still yours. Register it and point it at src/Controller, or accept YAML and move on. YAML is the low-friction path here.

Middleware is kernel.request / kernel.response. This is the mental translation from Laravel: there is no middleware array and no HandleMiddleware pipeline. Use event subscribers, ordered by priority. A request ID / CORS / API-key check looks like:

final class ApiKeyListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        // must run after RouterListener (priority 32) so routing is resolved
        return [KernelEvents::REQUEST => ['onRequest', 20]];
    }

    public function onRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }
        if ($event->getRequest()->headers->get('X-Api-Key') !== '%env(API_KEY)%') {
            $event->setResponse(new JsonResponse(['error' => 'forbidden'], 403));
        }
    }
}

Autoconfigure will tag it kernel.event_subscriber — provided the bundle registers RegisterListenersPass. Grep for it; if it's absent, add a compiler pass in your kernel or tag kernel.event_listener manually and register the pass yourself.

Adding capabilities à la carte. The pattern is: install the library, not the bundle, and define the service.

services:
    Doctrine\ORM\EntityManagerInterface:
        factory: ['Doctrine\ORM\EntityManager', 'create']
        arguments:
            - '%env(DATABASE_URL)%'
            - '%kernel.cache_dir%/doctrine'

The same shape applies to Twig (Twig\Environment + FilesystemLoader), Validator (Validation::createValidatorBuilder()), Serializer, and PSR-3 logging. You are writing ten lines of YAML per library instead of inheriting a bundle's config tree — that is the whole trade.

Testing. No KernelBrowser (that lives in FrameworkBundle). Either drive the kernel directly:

$kernel = new Kernel('test', true);
$response = $kernel->handle(Request::create('/health'));
self::assertSame(200, $response->getStatusCode());

or add symfony/browser-kit and Symfony\Component\HttpKernel\HttpKernelBrowser, which only needs the kernel itself. PHPUnit 9 is what the package's own suite uses; nothing constrains yours.

Environment config. Use %env(VAR)% parameters plus getenv(). If you want .env files, composer require symfony/dotenv and call (new Dotenv())->bootEnv(...) in the front controller. Nothing loads them for you.

Gotchas and Tips

  • It is a bundle, not a bundle ecosystem. DoctrineBundle, TwigBundle, SecurityBundle, and similar declare a dependency on symfony/framework-bundle and will refuse to install or explode at runtime. Plan on wiring the underlying libraries, not their bundles. If you need more than two of those, this package is the wrong choice.
  • No console. There is no bin/console, no debug:router, no cache:clear, no make: anything. Write throwaway scripts, or add symfony/console and register commands against the kernel by hand. Debugging a route usually means $router->getRouteCollection() in a scratch script.
  • No error handling. Nothing turns an exception into a friendly response; you get raw stack traces in every environment (Symfony 6's ErrorHandler/ErrorListener are not registered). Add a kernel.exception listener early, before you deploy anything.
  • Tag processing depends on compiler passes that FrameworkBundle normally adds. controller.service_arguments, kernel.event_subscriber, kernel.event_listener, and routing.loader are all inert unless the corresponding pass (RegisterControllerArgumentLocatorsPass, RegisterListenersPass, ...) is registered. Confirm against MicroFrameworkExtension and add what's missing in a Kernel::build() override. This is unchanged by 0.8.0 — the Symfony 6 bump widened the version constraint, it did not widen the feature surface.
  • Cache compilation is your deploy step. The container is compiled on first boot into var/cache/{env}. On read-only filesystems (containers, Lambda), warm it during build: instantiate the kernel with the target env and debug=false and call $kernel->boot() in a build script, or ship a writable cache dir. Symfony 6's cache layout is the same shape; the warmup command (cache:warmup) is still absent, so it's your script.
  • Version and longevity risk. v0.8.0 (Symfony 6 / PHP 8.0 drop for Symfony 5 / PHP 7) is the first release in a long while, from a project with tens of stars and essentially no dependents. That's a genuine signal in both directions: it's alive, but it moves in jumps and only as far as the maintainer needs. There is no Symfony 7 support yet, so on a current Symfony you are editing composer.json or forking. Budget for owning this code.
  • Pin deliberately, and read the BC breaks. ^0.8 silently excludes every Symfony 5 app; ^0.7 excludes Symfony 6. There is no overlap window and no deprecation path, so a composer update on an older project will either fail resolution or strand you. Lock the minor and test the jump.
  • The escape hatch is trivial and worth knowing. Since the bundle is a thin service definition file, migrating off it means copying services.xml into your own config/services.yaml and deleting the bundle. If your needs grow past a handful of endpoints, do that rather than fight the seam — you'll end up with a plain symfony/http-kernel app, which is what this always was. The 0.7→0.8 break is a good rehearsal for that move.
  • Where it genuinely wins: cold-start time and cognitive load. A container with ten services boots in single-digit milliseconds. If you're deploying per-request (FaaS) or running dozens of sidecars, the difference against a full FrameworkBundle container is measurable and the debugging surface is small enough to keep in your head.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core