Package Development in PHP & Laravel
Posted on: November 17, 2014
Packages are great. For PHP, Composer has made modularising your code and managing dependencies within your app easy peasy. The topic of this blog post, however, is about what exactly constitutes a package, and where things do and don't belong. I will be talking in the context of the Laravel, as this is what we use at work, but these points should be roughly relatable to any modern framework that utilises Composer.
At Bozboz, we build websites for clients; we're a web agency. The websites we build naturally range in size and function, but essentially, they are all similar. All websites boil down to fundamental parts. Frameworks themselves exist as a means of reducing repetitive tasks required within the building of an application.
Within the context of a typical client's website, for example, there are familiar elements which are likely to crop up more than once. To name but a few; a CMS, a blog, e-commerce/payment, form processing, etc. Before I go any further, I should point out there are 2 schools of thought I want to go into, so please bear with me whilst I explain them both first.
Mindset #1: Every website is unique. Even considering websites share common functionality, from one client to the next, they - and their customers - consider their website to be standalone. And in a sense, it is. It has its own domain name, its own physical space on the World Wide Web, its own identity.
If two websites, being built concurrently or otherwise, happen to share the same functionality. To all intents and purposes, this is a coincidence. They may start off the same, but a few years down the line, there's every likelihood these two features may end up functioning in completely different ways.
There's no inherent between features on 2 sites, other than the fact they may look and behave in the same or similar ways.
Mindset #2: The majority of a website is built up of reusable, shared packages. Some websites we build do contain considerable bespoke, unique, one-off functionality, but this mindset assumes these cases are the exception, not the rule.
Everything built into a website should be considered, and possibly even imagined and designed, as a standalone component. Anything could be reused on another website.
This is an attractive concept, especially for managers. The idea a website can be "constructed" using familiar, reusable components.
Mindset #1 is the simpler of the two tacts, so I'll start with that. I should point out, this is by no means a no-package-manager approach. As I made abundantly clear at the start, I believe package managers are here to stay. God knows we've all been in dependency hell at some point, trying to download a fresh copy of a new library; copy and pasting the new library version over the old one and hoping for the best.
What this method advocated is that all parts of the app itself - basically anything but the framework and library/component functionality (Router, Mail, Curl, Auth, Image Manipulation, etc.) - belong in the app, not a package. In the case of a Laravel app, this would include routes, filters, controllers, composers, views, configuration, assets, the list goes on.
While this seems like a fairly logical code-split between apps and packages, what happens to the code shared across projects? It gets duplicated; plain a simple. As soon as it becomes part of an app, it is the app. it is no longer a versioned, stand-alone package. For example, lets say you've built a kickass blog for a site and a manager asks you to re-skin and include on an upcoming site, you're bang out of luck. You're looking at a copy and paste job at best. These two virtually identical blogs must now be managed entirely individually.
Which is where mindset #2 comes along. This approach attempts to target the common issue above. Essentially; "We're using this admin interface in most of our sites, let's package it up and just install it via Composer on sites that need it!" Perfect. We're utilising the same package manager we installed the framework with, to install the various common parts of an app. Simple!
Not so fast. By representing parts of an app as packages, we are delegating the intellectual property (if you like) of a particular feature of the website to that package. This is fine in the case of a Curl package. In this case, we don't care how a request is made, we just care about the end result, which (in the lifetime of the package) will always be reliably consistent. We are relying on the very tip of the package's functionality. The public API a Curl package offers is relatively very small.
If we rely on a blog package to form part of the app, we are passing over the responsibility of rendering a blog almost exclusively to the package. From the appearance of the listing pages, to the URLs used, to the structure of a blog post; almost everything about a blog package is public to the app that uses it. If one thing changes, the app would likely need to be very aware of it.
Now, I admit, I am being rather black and white in this instance. You might argue Laravel offers the ability to override views and publish (and modify) a package's assets, migrations and configuration. And you'd be right. But in my experience, this is offering a bandaid to a potential deep wound of a problem. Extending classes using the IoC container is an option, but not an elegant one. Routes cannot be deferred, or modified within the app without a clever but convoluted use of events. Packages, and the way they are situated in the context of an app, just don't seem to be designed to work in this manner.
Quickly, you see, what started as a solution to the problem of duplication has instead become a problem in itself. We've investigated various half-way approaches, notably keeping the business logic (in old skool terms, the models) organised in a versioned, installable package, and the app stuff (controllers, views, etc.) confined to the installing app. This approach gives greater flexibility of behalf of the app, but you're losing the quick install and cross-site porting of packages. Not to mention the line between core-package logic and app-logic is a blurry one at times.
In a way, it's not even so much a technical issue to overcome as it is a question of logic and philosophy even. If one site copies functionality belonging to another site, is this functionality now common and consistent to both sites, and will it continue to be in both app's lifetimes? Or is it now a unique (yet similar) instance, since being taken from its original context?
And, really, this is the constant development conundrum see-saw at Bozboz, since we opted for greater modularisation and Composer. What's the answer?