20 December 2016

Quick and dirty PHP autoloading using Composer

Overview

Why write your own PHP class autoloader when Composer provides a perfectly good one?  It supports both PSR-4 and PSR-0 Recommendations (a.k.a. PHP standards for how namespaces should be used with autoloading).  Fun fact: it's based on the Symfony framework's autoloader and has lots of optimisations and features built in.

Composer is normally used to install/update third party components into a PHP project, from Git repos or packagist.org .  But the same functionality that Composer uses to let your project access classes etc. from within those components, can also be used to access your project's own classes etc.  In other words, depending on how you configure it, the autoloader that's generated for your project by Composer will provide access to class files defined in both third party components and your project.

I made a repo called template-PHP-project that has a composer.json already configured for both PSR-4 class autoloading and an include path.

Usage

Simply clone template-PHP-project, rename the directory and then delete its .git subdirectory. Then you can add web files, class files etc. and install third party components using Composer. You can then run git init and commit all the files ready to push to a blank repo on your favourite Git hosting site.  Or use Mercurial... I won't judge you.

If you like, instead of removing .git just edit .git/config and change the origin. Or run git config remote."origin".url your-repo-url .

If you have an existing project, you're better off moving your files around to fit the structure below than trying to squeeze template-PHP-project's functionality into it.

Directory structure

You will get all of these files and directories by default (thanks to .keep files) except vendor/ (which is managed by Composer):
|-- app/
|   |-- classes/
|   `-- include/
|-- bin/
|   `-- demo.php
|-- composer.json
|-- composer.lock
|-- config/
|-- docroot/
|   `-- index.php
`-- vendor/

Composer config

First, edit composer.json and change XYZ to your project's namespace.  You'll have to create a subdirectory in classes with this name, and that's where you put your class files -- name them according to the standard.  (You can add multiple namespaces if you like.  Any of your project namespaces automatically support a hierarchy of sub-namespaces that are inferred from the subdirectories in the project namespace's subdirectory.)  Don't forget to leave the trailing backslashes in the composer.json definition.

Before your code will work, you'll have to get composer to create/update vendor/autoload.php and vendor/composer/ by running this command:
composer update
This has to be done every time you add a new class file.  Or when you modify the require statement.

Please test all your code after doing this as it will update versions and dependencies of third party components.  Don't forget to commit afterwards; see section below.

Note

The supplied composer.json doesn't use a fallback directory for looking up namespace files.  All namespaces are expected to be explicitly declared.

Reminder regarding what to commit

This also covers what to keep in mind when deploying code.
  1. Always commit composer.lock
    Note: this will be erased by Composer if you don't require any third party components
  2. Never commit  vendor/
  3. On your server, Docker container or whatever, you are supposed to clone the repo (or extract it from a tarball) and then run composer install. If the repo is already there, run git pull and then composer install.
    composer.lock
    will ensure you get all the correct, working, tested versions of third party components on every deploy.  (Nobody likes surprises in production.)
  4. Never run composer update on production.

Example

See demo.php for an example of a PHP script that uses both an autoloaded class (assumes namespace Demo has been configured in composer.json) and a file in the pre-configured include path.

This script relies on the following files (re-creating them is left as an exercise for the reader):
  • app/include/foo.inc
  • app/classes/Demo/Util.php

Other features

Include path

As mentioned, my composer.json sets up app/include/ as a place from which you can include files without needing to specify the path.  The composer docs state that this feature is deprecated, because ultimately with an autoloader, you can do everything with classes.  Therefore you shouldn't need to write include files as part of a new project, given that:-
  • defines can be replaced by class constants, e.g. Foo::FROB_WEIGHT
  • functions can be moved to public static methods in a class, e.g. Foo::measure()
  • global variables are stupid; seriously, use a static class variable if you need one, but please consider writing some accessor methods
All of the above replacements can take advantage of namespaces and autoloading.

Automatic includes

I'm not currently using Composer's files feature, but it's easy enough to add to the autoload block for projects that should include a file into every PHP page that uses the autoloader.

Labels: