One of the most powerful features of Laravel is the Service Container, that part of Laravel which uses reflection to instantiate any class or interface. It allows for lots of flexibility in the structure of your site and how you make use of the Laravel framework. The service container is used to instantiate Controllers, for service injection into jobs and console commands, and even for injecting the Request and Models into controller methods. It can also be used just about anywhere in a Laravel application with the app() function.
Any class can be instantiated with the service container, provided the service container can also resolve all that class’s constructor’s arguments. If you do need an unresolvable argument (such as when an argument doesn’t have an explicit type), you can pass arguments in to the container: e.g. app(PackageBox::class, [‘tape’=>new DuctTape()]);
So obviously dependency injection and the service container are very powerful for customizing your app. But you can read about all that in the Laravel documentation linked above. What I’m here to talk about today is using the service container and dependency injection in packages. Making your laravel package binding-friendly.
To start off with, you can take advantage of bindings when building a package, using your package’s Service Provider(s). Remember that you can take advantage of automatic package discovery. Any class you bind can also be re-bound in the end-developer’s app, so they can extend your package even further.
You’ll also want to pay attention to where else you can be binding-friendly. Your package adds a model, and you’ll be saving new instances? Instead of $box = new PackageBox(), how about using the service container to get that class in the first place? $box = app(PackageBox::class). Are you adding a relationship, a listener, or otherwise need the class name? You can use $boxClass = get_class(app(PackageBox::class)). That one feels a bit roundabout but it’s not actually that much overhead and it’s seriously helpful to the developers using your package. That said, if what you’re writing is the final application - no one else will be overriding it - go ahead and use your app classes; it’ll be simpler to write and easier to read, and if you or another developer do end up needing to override the class, you’re already editing the app code.
Another useful trick, which I hinted at above, is binding controllers. Say you’re using a package which adds routes, but you need to change how one of those routes works. You could add a new route (with the same path as the old one), build your own controller, and probably extend the old controller. That works… unless you need to make a small change to each method of the controller. Then it’s just excessive. Instead, you can simply add a binding for that controller, extend the functions you need to change, and leave the routes alone.
Another thing to keep in mind is specifying argument types. When you write a constructor or other method which will be called with dependency injection, you can either specify a class, in which case any bindings to replace that class will have to extend that class, or else you can specify an Interface, in which case any class which implements that interface will do. If you go the interface route, you will probably want to bind the interface to your own class within your package, setting the default implementation.
So that about does it. You’re now ready to go out and write your own package full of binding points, knowing your end-developer can take full advantage of Laravel’s flexibility. Just be sure to write some documentation too.
Stay safe out there.