Start a Project

Let’s Swap Eloquent Models

Overview

In Bagisto, one package must never edit another package’s models. Swap Eloquent Models instead, and the override lives outside core code.

Our multi-vendor marketplace ships dozens of packages. Each owns its models, yet any of them can be replaced without a patched file.

The trick is three small pieces: an empty contract, a registry, and a proxy that resolves the class at the moment it is called.

Editing a model in place does not scale

A relation has to name a class. The obvious move is to name the concrete model and get on with the feature.

That line becomes the only truth. A plugin that needs a richer seller has nowhere to stand.

Swap Eloquent Models with a contract and a proxy

Every model implements an empty interface. Packages depend on the interface, so the class behind it stays free to change.

A registry maps each contract to whichever model is registered right now.

The container alias is the quiet half. Type-hint the interface anywhere and Laravel hands back the replacement.

A proxy then forwards every static call to whatever the registry currently holds.

That forwarding is plain static overloading, so the proxy needs no knowledge of the model at all.

Relations call SellerProxy::modelClass() rather than naming a class, so the answer is computed per call and never baked in.

Register your own model in a module of your own, and every relation, query and route binding follows it at once.

What to watch when you Swap Eloquent Models

A proxy is not a model. It cannot be instantiated or type-hinted, so the contract is what you inject.

The registry refuses any replacement that does not extend or implement the contract it is registered against.

Registering a model also resets that proxy’s cached instance, which is why service provider order matters.

Static analysis loses the trail, so proxies carry method doc blocks purely to keep the IDE and our earlier reflection notes honest.

Final Thought

Swap Eloquent Models once and extensibility stops being a fork. A package ships a default, an integrator ships a replacement.

Name the interface, register the class, resolve it late. That is the entire idea, and it costs one empty file per model.

Exit mobile version