Laravel Packages - How to build and deploy

Why using packages (Introduction)

Developing with a package approach in Laravel is a great way to split different logic apart in your application. By time as your application grow it will be easier to maintain when the code has been kept in separation, if the feature turns out to be generic and useful in another application it's also easier to pull it out to a independent project, that can be used accross several of your applications.

Prerequisities

Before reading this article, I assume you are already familiar with some of the basic Laravel. You don't need to know it in depth, but knowing terms such as schedulers, migrations, routes will help you.

Also I assume that you have set up composer globally so that you can type composer in your terminal and it's list of commands will output.

You should also be familiar with using a terminal and how to navigate directories. On Linux and MacOS it's simply called Terminal, on Windows you could be using Command Prompt or, as I prefer, Git Bash, but that choice is yours of course.

I don't expect you to know anything about Composer packages before reading this article.

My reasons for developing with packages in mind

At first when I started using Laravel, I wrote my code directly in the installation insides folders as app or resources. This was what felt most natural to me, as it's where the boilerplate invites you to start. However as a project grows it gets more complex to differ features and maintain which areas should have what responsibilty. It eventually leads to a lot of breaking changes.

Structuring a package

How you structure a package is an individual choice, but you must at a minimum include a composer.json file in it's root to have a valid Composer package. It's also optional where to store them. I like to keep my packages in a folder called packages in the root of my project, but you could place it anywhere you want, as long as it's inside your project. For this article I will use packages. If you later give the package it's own repository, it will of course be moved to vendors.

Set up your package environment

First create a folder packages in the root of your project. This is where folders such as app, public and vendorsare too.

First time I made packages, that was not vendors, I mistakenly defined the second level as my namespace, this isn't really adding anything as you could see the project itself as the namespace, or simply the term packages as a namespace. Instead, inside packages folder, create the name of your desired package. This could be permission. Whether your choose singular or plural naming is optional, but as far as I can tell, Laravel tend to use singular.

Creating composer.json

This is fairly easy, when located in your package folder type composer init and composer will guide you through the steps.

Here's an example on how to initialize the package permission given the name of the folder is so too:

[] indiciates a keyboard key, "" indicates a string value.

Composer outputActionNote
Package name (<vendor>/<name>) [emil/permission]:[Enter]Will then use what's inside the brackets
Description []:"Handling user permissions."
Author [Emil Moe **@**.**, n to skip]:[Enter]Again using what's inside the brackets.
Minimum Stability []:"dev"Suggested, you might prefer something different.
Package Type (e.g. library, project, metapackage, composer-plugin) []:[Enter]Skip.
License []:"MIT"MIT for open source, Proprietary if not, or you might prefer something different.
Would you like to define your dependencies (require) interactively [yes]?"n"We don't dependencies for this example.
Do you confirm generation [yes]?[Enter]Create composer.json

Folders

In your package folder (permission) create 2 folders src and tests so that the content is now:

  • src/
  • tests/
  • composer.json

As I will not cover here how to write tests, this folder is left empty, but that's where your tests should be.

Inside src is where all your custom code is going.

Structure inside src

I like to keep the structure as close to how Laravel does it. This is not necessary, but it makes it easier when I come back to remember how it's structure and also work as a fixed guideline for, how I should structure my files. If you are working multiple people on the package(s), then you also have a ruleset on how your files should be placed.

Efficient integration

Down to the actual implementation of your package. Most code will be just like you are used to do it in Laravel, but inside the package. Where the magic differs is often in your ServiceProvider. I will walk you through the different options you have to integrate as much here, making the package more seamless to incorporate.

The following section you will be working in the file src/PermissionServiceProvider.php.

The base of your PermissionServiceProvider.php looks like this:

<?php

namespace Packages\Permission;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;

class AccountServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any package services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

When to use boot() and when to use register()

This question is something I asked myself several times and can best be answered by quoting this answer: https://laracasts.com/discuss/channels/general-discussion/difference-between-boot-and-register-method

The register one is just for binding. The boot one is to actually trigger something to happen.

It's concluded from Taylor Otwells book where he states:

“After all providers have been registered, they are “booted”. This will fire the boot method on each provider. A common mistake when using service providers is attempting to use the services provided by another provider in the register method. Since, within the register method, we have no gurantee all other providers have been loaded, the service you are trying to use may not be available yet. So, service provider code that uses other services should always live in the boot method. The register method should only be used for, you guessed it, registering services with the container. Within the boot method, you may do whatever you like: register event listeners, include a routes file, register filters, or anything else you can imagine.”

If you keep the first quote in mind, it should be without a headache to continue.

Schedulers

Task Scheduling is Laravels interface for your cronjobs. In the past you would have to manage a dozen of cronjobs on your server, making it more complex to migrate your application to a new server or install it on multiple. With Task Scheduling you define 1 cronjob on the server pointing to Laravel, from there Laravel takes care of the jobs. Even better as you define those inside Laravel, they benefit from human readable methods such as everyMinute or hourlyAt(17) instead of a bunch of asterixes (*) and slashes (/).

In order to use the scheduler, you'll first have to import the namespace:

use Illuminate\Console\Scheduling\Schedule;

The schedulers has to be placed in the boot() method and must be encapsulated in a closure method booted:

$this->app->booted(function () {
    $schedule = $this->app->make(Schedule::class);

    // Your schedulers goes here...
});

For the actual schedulers you need to use, I will refer to Laravels documentation: laravel.com/docs/5.8/scheduling

For example if you want to run a command every midnight, your PermissionServiceProvider.php might look like this:

<?php

namespace Packages\Permission;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;

class AccountServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->booted(function () {
            $schedule = $this->app->make(Schedule::class);

            $schedule->command('permission:command')->daily()->at('00:00');
        });
    }

    /**
     * Register any package services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Config

Sometimes you want to allow applications to modify variables or behaviour of your packages. To do this config files are a great option. They function in the way that you define a default config, or multiple configs, inside your package and register them to your Service Provider. When installed in an application they can be published and changed, then the published config will be used. You have probably already experienced this from other packages you have installed.

To find out more about config files, it's very well explained in the official documentation: laravel.com/docs/5.8/packages#configuration

Let's say you create a folder in src/config, just like Laravel, and in that folder you could create permission.php. Then in your register() method in your ServiceProvider you load the config file:

$this->mergeConfigFrom(
        __DIR__ .'/config/permission.php', 'permission'
);

Blade Namespaces

Blades are great and using them in a package is also great. You can either provide Blade components or or utilize views from the package, that would be necessary for most installations or you might create a package that provides layout templates that your applications inherits.

When I first started with Blades in my packages, I had to create symbolic links (symlinks) from my resources/views folder to my package in order for them to be loaded. Later I discovered how you can create view namespaces that allows you to work with Blades inside your package and without any symlinks or other dark magic.

Using the namespaces

First create a controller in src/Http/Controllers. Add a constructor and set the namespace to i.e. Packages\Permission and define where your views are for this namespace:

/**
 * Controller constructor.
 */
public function __construct()
{
    view()->addNamespace('Packages\Permission', base_path('packages/permission/src/resources/views'));
}

Now in order to use it, instead of referering to just a Blade, you prefix it your namespace like this, if you wanted to refer to index.blade.php in your package:

view('Packages\Permission::index')

Migrations

If you want to add tables to the database, this is done in your migrations folder src/database/migrations.

You will find the official documentation for the migrations at laravel.com/docs/5.8/packages#migrations.

Migrations are working just like in your main app but should be placed in the before mentioned folder. To make your package able to find the migrations you must define it it's boot() method where they are located:

/**
 * Perform post-registration booting of services.
 *
 * @return void
 */
public function boot()
{
    $this->loadMigrationsFrom(__DIR__ .'/database/migrations');
}

I prefer to prefix my package migrations with the name of the package, in this case it would be permission_. Let's say I wanted to store some additional configurations in the database, this might be configurations that can be changed from the interface and not only in a config by a technical administrator, that would be permission_configurations. Whether you call it configs or configurations is up to you, but I like my naming to be explicit for better readability.

Rember that Laravel expects your database tables to be in plural, except if it's a relation between 2 tables

Models

Since we prefix the tables, the tradoff is that you need to be explicit in your models. For the before mentioned configurations example, it would be tempting to call the model PermissionConfiguration, but I find this tedious as the package is called Permission and would prefer to use Configuration, letting the import be use Packages\Permission\Configuration; and not Permission twice.

As the name of the model and the database table are inconsistent, you must therefor define the table name as a property:

/**
 * Related database table name.
 * 
 * @var String
 */
protected $table = 'permission_configurations';

Console Commands

If you want to implement maintenance to your package or doing administrative tasks that doesn't require a web interface because not everyone should access it, Console Commands are a great way to achieve this. You will find the official documentation at laravel.com/docs/5.8/packages#commands, but here I will explain how to add it to your package. Commands are often defined in group and command like group:command. You might know it from various Laravel commands such as php artisan cache:clear. My preference here is to name the group after your package, and whatever the command might be, that could be permission:refresh or permission:clear.

The Console Commands should be defined in your boot() method in the ServiceProvider.

<?php

namespace Packages\Permission;

use Illuminate\Support\ServiceProvider;
use Package\Permission\Console\Commands\Clear;

class AccountServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                Clear::class,
            ]);
        }
    }

    /**
     * Register any package services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Routes

Since we have already looked at Blades it's probably a good idea to take a look at the routes as well.

Laravel has 4 different types of routes:

  • API
  • Channel
  • Console
  • Web

In this article I will only be focusing on the Web as these are the most essential. API and Channel are better suited for their own article and Console is partly covered in the section above, although this is the new way Laravel suggests defining your Console Commands.

The documentation for the routes can be found at laravel.com/docs/5.8/packages#routes.

Routes should be loaded from the register() method in your ServiceProvider and be located insrc/routes :

/**
 * Register any package services.
 *
 * @return void
 */
public function register()
{
    $this->loadRoutesFrom(__DIR__ .'/routes/web.php');
}

For the content of your web.php file you should remember to define your package folder, so Laravel knows it's not in it's applications default:

Route::resource('permission/list', 'Packages\Permission\Http\Controllers\PermissionController')
    ->middleware(['web', 'auth'])
    ->only(['index']);

I prefer to prefix all my packages routes with the name of the package, in this case permission/.

Environments

When the time has come and you have a package ready for release, or beta release, there are 3 scenarios where you could choose to use your package.

  1. Local in the application only
  2. In several of your applications, but only for internal use
  3. Public available through packagist

Local in the application only

In case of the first (1) you are already ready to go, as you developed it inside your application and that's where it is and is going to start, so you are good to go.

In several of your applications, but only for internal use

If the case is second (2) you could use GitHub, but their pricing is often a reason for choosing providers such as BitBucket or GitLab instead. Fortunately this is also very easy to to, but you have to be aware that since it's a private package, you will not publish it to packagist.org and that means you can't have versions, but only dev-master. You could set up your own private Packagist, but that isn't free and out of the scope for this article.

Private hosted at BitBucket

In this guide I will take you through how it's private hosted at BitBucket, if you want to use GitLab the proces should be fairly similar.

First create a new repository at BitBucket.

As long as it's only being used in your development and all developers have acces to the repository it's good to go. As soon as your start pushing to production, you must remember to add the production servers public ssh key to your package list of access keys.

In your composer.json add the following section somewhere after require-dev, and remember to change the lines to fit accordingly. I added an extra line just to show how to add multiple:

"repositories": [
   {"type": "vcs", "url":  "git@bitbucket.org:your_name/permission.git"},
   {"type": "vcs", "url":  "git@bitbucket.org:your_name/another_package.git"}
],

This will allow Composer to include your repository outside Packagist to be included.

Now you can install your package with the usual require comment:

composer require your_name/permission

Public available through packagist

If you choose to distribute your package publicly so anyone can benefit from it, you need to publish your package ot GitHub and register it at Packagist.org. You should upload your package to Github before you move forward to Packagist.

First go to Packagist.org and create an account.

Next you must connect this account to your Github account, it should ask for doing so right after you login. You might have to sign in through Github too first.

Next click Submit in the top right corner in order to add your package from your Github repository. And that's it.

Worth noting is that Packagist uses the tags you create on Github as it's versioning system. So use tags such as 1.0, 1.5, 2.0 and so on to properly versioning your package.

Testing packages

As previously mentioned tests should go to the tests directory in your package folder, same folder where src is located. As long as you have your packages locally you can perform testing to them as well. After deploy and installing them in another project, it shouldn't be necessary anymore to do testing.

In the root directory of your Laravel installation, open the file phpunit.xml. Find the section testsuites and change it took be accordingly to this, where the package folder is included:

<testsuites>
    <testsuite name="Feature">
        <directory suffix="Test.php">./tests/Feature</directory>
        <directory suffix="Test.php">./packages/*/tests/Feature</directory>
    </testsuite>

    <testsuite name="Unit">
        <directory suffix="Test.php">./tests/Unit</directory>
        <directory suffix="Test.php">./packages/*/tests/Unit</directory>
    </testsuite>

    <testsuite name="Acceptance">
        <directory suffix="Test.php">./packages/*/tests/Acceptance</directory>
    </testsuite>
</testsuites>

Phpunit will now also search through your packages folder for tests cases.


This was written a while ago hence the reference to 5.8

Comments (1)

Emil Moe's photo

I wrote a dedicated article about sharing to Packagist on my other blog, feel free to take a look there emil.moe/2019/10/deploy-laravel-packages-to..

It also refers to this one regarding the package development itself.