mouf/classname-mapper
mouf/classname-mapper is a lightweight PHP utility for mapping class names to custom identifiers, making it easier to resolve aliases and decouple configuration from concrete class strings. Useful for DI containers, plugin registries, and flexible component lookup.
Pull the package in and pin it, because this is effectively abandoned (last release Jan 2019, PHP 7 era):
composer require mouf/classname-mapper
Before writing any code, read three things in the repo, in this order:
composer.json — see what transitive Mouf packages it drags in (several mouf/* packages assume a Mouf DI container you will not be running).ClassnameMapperInterface (or equivalent) — this is the entire contract: given a class name, return the file it would be loaded from (and usually a way to enumerate everything it knows about).Treat the package as a small, readable utility, not infrastructure. The value is the interface and the "aggregate several resolution strategies" pattern; you can vendor a fork in app/Support/ in an afternoon if it fights PHP 8.
First use case that actually fits Laravel: you have a class name as a string from somewhere non-static — a DB column, a config value, a queue payload, a route parameter — and you need the file path so you can reflect on it (attributes, docblocks, method signatures) without instantiating it. Typical shape: a "modules" or "plugins" table where each row names a class, and you want to enumerate/validate those classes at boot or in an Artisan command.
// app/Providers/ClassnameMapperServiceProvider.php
public function register(): void
{
$this->app->singleton(ClassnameMapperInterface::class, function () {
// Build from your PSR-4 roots + Composer's classmap.
// Check the concrete constructor signature in the repo; most
// implementations take prefix => directory pairs or are composed.
return new /* ... */ ClassnameMapper(/* ... */);
});
}
Bind the interface, never the concrete class. The package's whole design is "compose mappers," so having the container resolve an interface lets you swap in a cached or test-double mapper without touching call sites.
Compose, don't configure. If the package ships an aggregate mapper, register your PSR-4 roots as one child mapper and Composer's vendor/composer/autoload_classmap.php as another. Order matters: put the authoritative classmap first if you want composer dump-autoload -o semantics.
Pair it with ReflectionClass, not with class_exists. The mapper answers "where," reflection answers "what." A clean discovery pattern:
foreach ($mapper->getClassPaths() as $className => $path) {
if (! str_starts_with($className, 'App\\Modules\\')) {
continue;
}
$reflection = new \ReflectionClass($className);
foreach ($reflection->getAttributes(Module::class) as $attribute) {
$modules->register($className, $attribute->newInstance());
}
}
Because you get paths, you can pre-filter by file mtime or directory before triggering autoload — useful when the map is large.
Cache the built map on deploy, not per request. Building a mapper that walks directories is filesystem I/O. Serialize the resolved className => path array into Laravel's cache (or a generated PHP file like bootstrap/cache/classmap.php) inside a php artisan app:cache-classes command, and rebuild it in your deploy script after composer install. This mirrors what Composer does and is the only way this package stays cheap in production.
Use it in Artisan commands and validation rules, not in hot paths. The natural home is php artisan modules:list, a ClassExistsInMapper validation rule for admin-supplied class strings, or a config sanity check at boot. Do not put a mapper call inside a loop that renders views.
Wrap it behind your own small service. App\Support\ClassLocator with path(string $class): ?string and all(): array means the 2019 package is an implementation detail you can replace with a 20-line Composer ClassLoader wrapper the day it breaks.
${var} interpolation). Run the suite under your target PHP before committing to it; if it breaks, fork it — the surface area is tiny.new ReflectionClass($name) will still trigger the real autoloader and throw if Composer can't find it. Use the path result to avoid autoloading, not to replace it.classmap-authoritative will lie to you. If your production composer.json sets "classmap-authoritative": true or "optimize-autoloader": true, the map the package derives from Composer data will differ from dev. Verify behaviour in a production-shaped build, not just php artisan serve.null in CI. Add a test that asserts a known class resolves to a known path.null vs false vs exception. Check what the interface returns for "not found" and normalise it in your wrapper. This is the single most common source of TypeError when swapping implementations.mouf/* siblings expect Mouf\Container or a MOUF_URL/ROOT_PATH constant. If you see a ContainerException or a missing-constant fatal on boot, you're hitting that — bypass the factory classes and instantiate the plain mapper directly.dd($mapper->getClassPaths()) (or whatever enumeration method the version exposes) and diff it against require 'vendor/composer/autoload_classmap.php'. If your class is missing from both, the problem is composer.json, not the mapper.tests/Fixtures/ tree rather than app/, so refactors of your real namespaces don't break mapper tests.How can I help you explore Laravel packages today?