amethyst/core
Amethyst Core provides the foundational Laravel/PHP building blocks for the Amethyst stack, including service providers, shared contracts, configuration, and common utilities. Lightweight and extensible, it keeps shared concerns consistent across Amethyst packages and applications.
Read this first: check whether it installs at all. amethyst/core has 1 star, 0 dependents, and a health score of 0.035. That is not "niche," that is "nobody is running this in production." Before you invest a day in it, run composer require amethyst/core in a throwaway directory and look at what Composer resolves. The composer.json constraints for symfony/* and the php requirement are the single most informative file in the repo — they tell you which era of PHP and Symfony this targets, and therefore whether it can coexist with anything you run today.
Minimal path:
composer require amethyst/core in an empty project (do not try it inside an existing Laravel app yet — see Gotchas).composer.json and src/. You are looking for the application/kernel class (the thing that owns the container, the router, and the dispatcher) and the bundle interface. Those three names are the whole framework.tests/. With a 1-star package this is the only documentation that is guaranteed to be executable. A test that builds the kernel and dispatches a request is worth more than the README.Sketch (class names are indicative — confirm against src/, not the README, which is likely stale):
$kernel = new Application('dev', true); // env, debug
$kernel->registerBundle(new App\AppBundle()); // your service provider analogue
$request = Request::createFromGlobals(); // symfony/http-foundation
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
That handle/send/terminate shape is the Symfony HttpKernel contract, and this package is built on Symfony components. That is your mental model for everything that follows. You are not learning a new framework; you are learning a thin opinionated layer over Symfony's container, router, and event dispatcher, plus a bundle convention. If you already know Symfony, you already know 80% of this.
First real use case: a single-purpose HTTP service (webhook receiver, small internal API, queue worker front door) where you want Symfony semantics without Symfony's full-stack skeleton and without Laravel's weight. Do not pick it as the foundation of a long-lived product — see Gotchas.
Bundles are your service providers. This is the one concept you must internalize. A bundle is a package that registers services, config, routes, and listeners into the kernel. Where Laravel has register() / boot() in a ServiceProvider, Amethyst has a bundle with an equivalent pair of lifecycle hooks that receive the container. Keep the same discipline you use in Laravel:
register-equivalent: bind definitions only, no side effects, no I/O.boot-equivalent: wire listeners, register routes, touch the filesystem — only after every bundle has registered.Container-first wiring, constructor injection, no facades. Laravel's facades and app() helper are habits that will hurt you here. Resolve dependencies in the bundle, inject via constructor, and treat $container->get() as a smell outside of entry points. If you need a facade-like ergonomics layer, write it yourself — it does not ship with core.
Routing is Symfony routing, not Laravel routing. Expect route definitions as arrays or YAML/PHP config with path, defaults._controller, requirements, methods. The controller is typically a service id resolved from the container, not 'App\Http\Controllers\Foo@bar' string syntax. This is a small difference with a big consequence: your controllers must be registered services, otherwise the route resolves to nothing and you get a confusing 404 rather than a clear "class not found."
Cross-cutting concerns belong on the event dispatcher. The kernel.request / kernel.controller / kernel.response / kernel.exception events are your middleware pipeline. Auth, CORS, request logging, and JSON error shapes all become event subscribers. This is genuinely nicer than Laravel middleware for anything that needs to run on failures, because kernel.exception is a first-class hook rather than a render callback.
class CorsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [KernelEvents::RESPONSE => 'onResponse'];
}
public function onResponse(ResponseEvent $event): void
{
$event->getResponse()->headers->set('Access-Control-Allow-Origin', '*');
}
}
Integration tip for Laravel shops: do not try to run this inside a Laravel application. It owns a kernel, a container, and a dispatcher; booting it alongside Laravel gives you two of everything and a Compose dependency resolution fight over symfony/* versions. The two sane options are (a) deploy it as a separate service and call it over HTTP, or (b) mine it for patterns — the bundle registration lifecycle and the compiler-pass-based extension model are worth reading even if you never ship it.
Testing pattern: never spin up a web server in tests. Instantiate the kernel with 'test' as the environment, build a Request with Request::create('/path', 'GET'), call handle(), and assert on the Response status, headers, and content. That gives you full-stack coverage in-process and is the fastest way to explore undocumented behaviour in this package.
composer.json and commit composer.lock. If you ship it, vendor a fork under your own namespace and plan to maintain it.symfony/* constraints will almost certainly be unsatisfiable. That is a hard stop, not something to work around with --ignore-platform-reqs.dev with debug on it usually recompiles; in prod it will not.src/ and tests/ over prose. Grep for the class name before copying any snippet.Response is not a Laravel response — no ->json() convenience, no view(). Build the body and set the status/headers explicitly. Headers mutated after send() are silently lost, so do all header work in a kernel.response listener.symfony/var-dumper (dump()/dd()), error_log, and the kernel's debug flag, which controls exception verbosity. Leave debug true in local only — it will happily leak stack traces and config to clients otherwise.extend/rebinding callbacks — they let you decorate definitions across the whole container after registration, and they are how you'd add framework-level behaviour without forking.How can I help you explore Laravel packages today?