How to Build and Autoload Your Own PHP Package Locally

Posted on May 11, 2020

If you've never built a PHP package before, it might seem a bit daunting to figure out how to start. Where does the package actually go? Your other packages are in your vendor/ directory, but that folder is Git ignored, so that's not it...

Your options three

There are three primary ways you can work on your package:

  1. Create a new git repo for it, push it up to GitHub and add it to Packagist, and consume it via Composer as you develop.
    Complex, and puts a very-incomplete package on Packagist
  2. Create a new folder for it that's parallel to your project's folder, and then update your Composer to point to it.
    Less complex, but requires the correct setup on any server you deploy to
  3. Store your package in your project's source until it's good enough to release.
    Simplest option, and what we'll cover today

Our sample scenario

A friend told me today they've been building a custom provider for Laravel Socialite, a tool for adding social authentication to your Laravel apps.

So, let's use that as an example. How would I go about building a custom provider, with its own unique namespace, that I might eventually want to release as a package?

Decide your namespace

First, let's pick the namespace. Socialite just requires my custom code to extend a Socialite class and implement a Socialite interface, but it doesn't care what namespace I put it in. So, let's imagine I'm releasing a collection of custom Socialite providers.

I'll maybe imagine making a package on Packagist as mattstauffer/socialite-providers, so the namespace would probably be Mattstauffer/SocialiteProviders.

My class for today will be MaceBookProvider, providing the ability for users to log into MaceBook.com, the premier social network for medieval weapon aficionados.

Build your file

Let's create the file now. It'll look something like this:

<?php

namespace Mattstauffer\SocialiteProviders;

use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;

class MaceBookProvider extends AbstractProvider implements ProviderInterface
{
    // ...
}

But where does this file go?

It's a very common pattern to have a folder in your application with the folder name of src/ which contains your custom PHP code. That's one option—throw it all in there.

If you don't want to put it in src/, because you already have plans for it, you can also create a new folder like src/ and name it something like packages/. We'll assume for the rest of this article you've put it in packages/.

You'll want to treat that packages/ folder as if it's representative of your top-level Mattstauffer namespace. So, we'll add a subfolder SocialiteProviders to represent our Composer repo, and then our file will live at packages/SocialiteProviders/MaceBookProvider.php.

Add your own autoloader

We're almost there! Now, we need to teach Composer that any files in packages/ should be treated as being in Mattstauffer/ and then use their directory tree and filenames to define the rest of their namespaces.

Thankfully, that's exactly how PSR-4 works! So we'll tell Composer to PSR-4 autoload the packages/ directory and map it to the Mattstauffer namespace and we're good to go!

Note: You could be more precise by instead loading packages/SocialiteProviders as the Mattstauffer/SocialiteProviders namespace. Your call.

Edit your composer.json and scroll down to the autoload key. If you're using a modern framework like Laravel, you'll likely already see a PSR-4 entry there, looking something like this:

    "autoload": {
        "psr-4": {
            "App\\": "app/"
        },

So, let's modify that to add ours:

    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Mattstauffer\\": "packages/"
        },

Run composer dump-autoload and you're good to go!

You can stop right there and you'll be good to go! But... if you want to take your package loading to the next level, read on...

What if my package needs dependencies?

If you're definitely going to eventually distribute this package, the solution described here might not be enough. For example, your package might have Composer dependencies of its own. You want to keep that list separate, right, instead of just adding them to the parent application's composer.json?

If this is the case, it's time for you to move up one level. You'll need to create a folder adjacent to your application's folder, and outside of the Git repository. (Watch a free video from my friend Marcel here)

So, if the package were named BestPackage and the site were named BestProject, they'd both be under the same parent directory, Sites, like this:

\Users
    \mattstauffer
        \Sites
            \BestPackage
            \BestProject

How to autoload a package outside of the Git repo using "path"

To autoload your new package, you'll need to modify your composer.json to treat the "path" to that folder (../BestPackage) as a valid Composer source.

Make your package composer-ready

But first, you'll need to ensure that your new package has a valid composer.json in it. You can create that by moving to your package's directory and running composer init.

You can choose which of the prompts you want to follow to create this file, but the most important thing is to give this package a valid "name" that is in a namespace that you own on Packagist.

Here's what my "BestPackage" composer.json might look like:

{
    "name": "mattstauffer/best-package",
    "description": "The best package!",
    "type": "library",
    "require-dev": {
        "tightenco/tlint": "^4.0"
    },
    "license": "MIT",
    "authors": [
        {
            "name": "Matt Stauffer",
            "email": "matt@tighten.co"
        }
    ],
    "require": {}
}

Load the package using the "path" type repository key

Next, back in the original project's composer.json, create a repositories key, which will contain an array of objects, and add your first object:

{
    // require, require-dev, etc.
    "repositories": [
        {
            "type": "path",
            "url": "../BestPackage"
        }
    ]
}

Require that package

Finally, you can require that package!

composer require mattstauffer/best-package

One caveat

The downside of this method is that your package won't deploy along with your repo, so you'll have to set the package up parallel to your app on other servers as well.

But this is a great option for preparing for real package distribution.


Do you want to dig deeper into PHP Package Development? My friend Marcel has built an entire course around it entitled PHP Package Development.


Comments? I'm @stauffermatt on Twitter


Tags: php  •  composer  •  autoload  •  package

Subscribe

Quick links to fresh content, and more thoughts that don't make it to the blog