qce/core-bundle
Core bundle for the QCE package suite, providing shared foundations for Laravel/PHP applications. It centralizes common services, configuration, and integration points so related QCE packages work together consistently.
Read composer.json before you run anything. With 0 stars and 0 dependents this is almost certainly an internally-published bundle from la-quincaillerie, not a maintained community package. The composer.json answers the questions that decide whether you have a ten-minute install or a two-day port:
composer require qce/core-bundle
composer show qce/core-bundle
Check three keys:
type — symfony-bundle means Laravel will not auto-discover it. There is no extra.laravel.providers block and no Illuminate\Support\ServiceProvider subclass waiting to be registered.require — if it pulls symfony/framework-bundle, symfony/dependency-injection, or doctrine/orm, you are looking at Symfony's container/config tree compiled at kernel boot. Those concepts do not map onto Laravel's container; plan for a hand-written adapter, not an install-and-go.autoload.psr-4 — this gives you the real root namespace (Qce\Core\ or similar) and tells you where the base classes and interfaces live.Look at the directory layout next. src/DependencyInjection/*Extension.php and src/DependencyInjection/Configuration.php indicate Symfony. src/Providers/*ServiceProvider.php and a config/*.php with mergeConfigFrom indicate a Laravel package. Everything downstream depends on which one you find — check this before writing a single line of wiring.
If it is Laravel-shaped, publish the config immediately and read it. It is the fastest map of what the package exposes:
php artisan vendor:publish --tag=qce-core-config
php artisan about
If it is Symfony-shaped, do not install it into your app and hope. Vendor it into a read-only branch and map the surface instead:
git clone https://github.com/la-quincaillerie/QceCoreBundle /tmp/qce-core
grep -rn "class \|interface \|trait " /tmp/qce-core/src | sort
grep -rn "public function " /tmp/qce-core/src | head -50
Your first use case should be one class, not the framework. Pick a single concrete service — a manager, registry, DTO factory, or value object — that you actually need. Bind it behind your interface so the package stays swappable:
// app/Contracts/RateRegistry.php
interface RateRegistry
{
public function for(string $currency): Rate;
}
// app/Providers/QceCoreServiceProvider.php
public function register(): void
{
$this->app->singleton(RateRegistry::class, function ($app) {
return new QceRateRegistryAdapter(
new \Qce\Core\Registry\RateRegistry(config('qce-core.rates', []))
);
});
}
Register that provider in bootstrap/providers.php (Laravel 11) or config/app.php (10 and below) — auto-discovery will not do it for you. Then write one feature test that resolves RateRegistry from the container. That test is your proof the integration is real; it is also the only documentation you will have.
Adapter-first, always. Treat the package as an implementation detail behind an interface you own. This is the single pattern that makes every other risk in this assessment survivable — abandonment, a breaking change with no semver, a fork that drifts. If the package ships its own contracts (the description advertises "supplied contracts"), implement those only if you also keep your own interface in front. Two layers of abstraction feels redundant for a week and saves a rewrite in a year.
final class QceRateRegistryAdapter implements RateRegistry
{
public function __construct(private \Qce\Core\Registry\RateRegistry $inner) {}
public function for(string $currency): Rate
{
return Rate::fromArray($this->inner->find($currency)->toArray());
}
}
Vendor it as a path repository when you are serious. If you intend to keep this, pin a commit hash and consume it locally, so you control upgrades and can patch without waiting on an upstream that does not exist:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/la-quincaillerie/QceCoreBundle", "no-api": true }
],
"require": {
"qce/core-bundle": "dev-main#a1b2c3d4"
}
}
Or copy the two or three files you actually use into app/Support/ and delete the rest. For a core bundle with zero dependents this is frequently the correct answer — the boilerplate it saves is often smaller than the adapter you write around it.
Configuration merges, not config sprawl. If the package does merge config, extend it rather than mutating it at call sites. Keep package-specific keys under one namespace so a future removal is a single grep:
// config/qce-core.php — published, then edited
return [
'registry' => [
'driver' => env('QCE_REGISTRY_DRIVER', 'array'),
'cache_ttl' => env('QCE_REGISTRY_TTL', 300),
],
];
Resolve it via config('qce-core.registry.driver') inside your adapter, not inside controllers. When you eventually replace the package, that config file is the checklist.
Use the DTO primitives only at the boundary. The description mentions "lightweight helpers and DTO primitives." Use them to hydrate from request/queue payloads at the edge, then immediately convert to your own domain objects. Never let a package DTO reach a Blade template, an API resource, or an Eloquent model's accessor — that is how vendor types leak into a surface you cannot then change.
Test with a fake, not the package. Because your adapter sits behind an interface, your test suite should never instantiate the package at all outside the one integration test:
$this->app->instance(RateRegistry::class, new FakeRateRegistry(['EUR' => Rate::of(1.0)]));
This keeps 95% of your suite fast and, more importantly, keeps the package's absence from being a test-suite catastrophe if you remove it.
Strangle your own boilerplate into it, not out of it. The strongest legitimate use is consolidation: if three services hand-roll the same base service provider or the same DTO base class, move those to the package's base classes and delete your copies. The reverse — adopting the package and then discovering what you want to standardize — produces an abstraction with no owners.
The Bundle suffix is not cosmetic. If type is symfony-bundle, there is no auto-discovery and there is no php artisan vendor:publish tag. Symfony bundles register services via Extension::load() at kernel compile time; Laravel has nothing equivalent. Anyone on the team who assumes composer require did something is wrong, and the resulting BindingResolutionException will look like a package bug rather than a missing provider. Verify with:
php artisan tinker --execute="var_dump(app()->getLoadedProviders()['Qce\\...\\Provider'] ?? null);"
Silent no-op config. Laravel's mergeConfigFrom performs a shallow merge — a nested array in your published config replaces the package's default wholesale rather than merging key-by-key. If you add one key under registry and suddenly lose the package's other registry defaults, that is why. Mirror the full default array in your published file, or merge manually in your provider.
Config and route caching hide package edits. After touching a published config or registering a new provider, php artisan config:clear and php artisan optimize:clear. In a cached production environment the provider registration itself can appear to vanish. This is the number one false positive when integrating an unfamiliar package.
Doctrine entity mapping is the landmine if the bundle ships entities. Two bundles mapping the same table — or the same table name with different column types — fails at doctrine:schema:validate, often only in a shared dev database. Run php artisan doctrine:schema:validate (or the Symfony equivalent) in a throwaway database before anything touches shared infrastructure. If the bundle ships mappings, assume conflict until proven otherwise.
Pin exactly; never use ^. With zero stars and zero dependents there is no observable semver discipline, no release history to sample, and nobody filing "this minor broke my app" issues. "^1.0" on a package whose version numbers are decorative is a production incident waiting for a Tuesday. Use a commit hash or an exact version and bump deliberately.
Class name collisions are likely with a "core" namespace. If your app has App\Core\* or you run any other *Core* package, PSR-4 does not warn you — you get whichever class the autoloader finds first, and behavior changes based on load order. Run composer dump-autoload -o and then composer why qce/core-bundle to confirm nothing else pulled it in transitively.
Debug what actually resolved, not what you think resolved.
dd(app()->bound(RateRegistry::class), get_class(app(RateRegistry::class)));
And for the container's own opinion of the package's concrete classes:
dd((new ReflectionClass(\Qce\Core\SomeManager::class))->getConstructor()?->getParameters());
That second one is usually faster than reading the source when a constructor has five dependencies you were not expecting.
Extension points are interfaces, not subclassing. The description advertises "supplied contracts" — implement those, but do not extend the package's base classes unless you accept that a future fork must carry your subclasses forward. Interfaces survive forks; inheritance chains do not.
Ramp-up cost is the dominant cost. No README of substance, no examples, no Stack Overflow corpus, no issue tracker history to learn from. Budget senior time to read the source — hours for a small bundle, a week if it carries domain logic — and treat that audit as the actual due-diligence artifact. If the audit does not turn up something specific and clearly valuable, write the thin equivalent yourself; the delta is usually a day, and the control is complete.
Write the exit plan before you write the adapter. One paragraph: who owns this internally, what commit is pinned, which config keys and interfaces would need reimplementing, and what the trigger for removal is. A zero-dependent package is a fork you have not admitted you are maintaining yet — naming that up front is the difference between a considered dependency and an orphan.
How can I help you explore Laravel packages today?