DEV Community

Cover image for Create a Laravel package on your local environment
Capsules Codes
Capsules Codes

Posted on • Updated on • Originally published at capsules.codes

Create a Laravel package on your local environment

TL;DR: How to set up a local development environment to test your package classes or utilities within a local Laravel project.

 
 

A sample Laravel project can be found on this Github Repository.
Find out more on Capsules or X.

 
 

It shouldn't surprise you to learn that we use hundreds of packages during web tool development. To acquire them, you simply need visit a package manager like Packagist, which had 382,000 of them in October 2023.

 
 

If you want to develop your own package, it's entirely reasonable to wonder how to test it under real conditions. Publishing the package on Packagist during development is not an option. Another approach would be to integrate it into a fresh project without using Composer. The method in this article closely simulates a real-world scenario but does require some environment setup.

 
 

Create a folder that will serve as the foundation for your package.

 

mkdir package
Enter fullscreen mode Exit fullscreen mode

 

Create a composer.json file, which forms the essential foundation of the package.

 

package/composer.json

{
    "name": "capsulescodes/package",
    "description": "Capsules Codes Package",
    "type": "library",
    "autoload": { "psr-4" : { "CapsulesCodes\\Package\\" : "src/" } }
}
Enter fullscreen mode Exit fullscreen mode
  • name: The name should be structured with the entity on the left and the package name on the right. It is highly recommended to use a descriptive package name to facilitate user searches.
  • description: The package description should be clear and concise.
  • type: There are 4 types: library, project, - metapackage, and composer-plugin. The library type is the most commonly used. Project represents a project, such as a framework template.
  • autoload: This is the core element of our package, defining a namespace to access data located at the root of the project. The class autoloading specification protocol is defined by PSR-4. This is a crucial step. Make sure to include the \\, especially at the end of the statement.

 
 

It is advisable to ensure that the name information matches the namespace. Additionally, it is recommended to use src as the folder name at the root of the project.

 
 

Furthermore, if you run composer init in a folder that does not contain a composer.json file, a wizard will guide you through the process of creating your composer.json file.

 
 

Create a folder src inside the package folder.

 

cd package
mkdir src
Enter fullscreen mode Exit fullscreen mode
  • The arrangement of the files is not really important, except for their proximity, both for you and for this article.

 
 

Create a PHP class named Greeter containing a function greet that returns the phrase Hello world!.

 

package/src/Greeter.php

<?php

namespace CapsulesCodes\Package;

class Greeter
{
    public function greet() : string
    {
        return "Hello world!";
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Do not forget to indicate the namespace. Otherwise, the class will not be found.

 
 

It is now possible to test this package. To do this, you need to create a testing environment using a Laravel template project.

 
 

Return to the parent directory of the package and create a template Laravel project serving as a test.

 

cd ../../
composer create-project laravel/laravel template
Enter fullscreen mode Exit fullscreen mode
  • The composer create-project laravel/laravel template command generates a 'type': 'project' here.

 
 

To inform the template that our package is located in the same parent folder, it is necessary to add two pieces of information to the composer.json file.

 

template/composer.json

...
"minimum-stability": "dev",
"repositories": [ { "type" : "path", "url" : "../package" } ]
...
Enter fullscreen mode Exit fullscreen mode
  • minimum-stability: This option allows the installation of the package via composer without generating an exception. This is necessary when the package is not stable, in this case, our package is currently dev.
  • repositories: This array allows adding paths to other directories that composer should refer to in order to find a package locally.
  • type: The type of directory can be composer, package, - vcs, or path. The path option allows for the local use of a package, while the vcs option allows the use of a package through a version control system such as Github.

 
 

It is time to install our new package.

 

composer require capsulescodes/package
Enter fullscreen mode Exit fullscreen mode

 
 

It is now listed in the require dependencies.

 

package/composer.json

"require": {
    ...
    "capsulescodes/package": "dev-main",
    ...
}
Enter fullscreen mode Exit fullscreen mode
  • Since the package is still in development, the dev-main version is used.

 
 

Test via php artisan tinker

 

php artisan tinker

> CapsulesCodes\Package\Greeter::greet()
= "Hello world!"
Enter fullscreen mode Exit fullscreen mode

 
 

You can modify the web.php file to test the Greeter static class.

 

package/routes/web.php

<?php

use Illuminate\Support\Facades\Route;
use CapsulesCodes\Package\Greeter;

Route::get( '/', fn() => dd( Greeter::greet() ) );
Enter fullscreen mode Exit fullscreen mode
"Hello world!" // routes/web.php:7`
Enter fullscreen mode Exit fullscreen mode

 
 

The work environment is ready.

 
 

It would be interesting to add the additional method say() to test the tool in real time.

 
 

package/src/Greeter.php

<?php

namespace CapsulesCodes\Package;

class Greeter
{
    public static function greet() : string
    {
        return "Hello world!";
    }

    public static function say( string $something ) : string
    {
        return $something;
    }
}
Enter fullscreen mode Exit fullscreen mode

 
 

Test using php artisan tinker. You should probably reload it.

 

php artisan tinker

> CapsulesCodes\Package\Greeter::say( "That's a quick way to develop and test a package!" )
= "That's a quick way to develop and test a package!"
Enter fullscreen mode Exit fullscreen mode

 
 

You can modify the web.php file to test the Greeter static class.

 

package/routes/web.php

<?php

use Illuminate\Support\Facades\Route;
use CapsulesCodes\Package\Greeter;

Route::get( '/', fn() => dd( Greeter::say( "That's a quick way to develop and test a package!" ) ) );
Enter fullscreen mode Exit fullscreen mode
"That's a quick way to develop and test a package!"  // routes/web.php:7
Enter fullscreen mode Exit fullscreen mode

 
 

At this stage, everything is ready to develop a PHP Framework-agnostic package. Additional steps are required to create a Laravel package. For this article, the goal is to implement a php artisan greet command that will call the Greeter static class.

 
 

It is recommended to follow the typical project structure of Laravel when creating a Laravel package. This makes it easier for those who need it to find information.

 
 

First, it is necessary to create the GreetCommand by extending the Illuminate\Console\Command command specific to Laravel.

 

package/src/Commands/GreetCommand.php

<?php

namespace CapsulesCodes\Package\Console\Commands;

use Illuminate\Console\Command;
use CapsulesCodes\Package\Greeter;

class GreetCommand extends Command
{
    protected $signature = "greet";

        protected $description = "Greet people with a 'Hello world!'";

    public function handle()
    {
        dump( Greeter::greet() );
    }
}
Enter fullscreen mode Exit fullscreen mode
  • The base Illuminate\Console\Command class of Laravel is used as an extension.
  • The previously created static class Greeter is used in the handle() method. However, to test the package, you can simply replace return Greeter::greet() with return "Hello world!".

 
 

In order for the model project to recognize this command, it is necessary to notify it using a ServiceProvider.

 

package/src/Providers/PackageServiceProvider

<?php

namespace CapsulesCodes\Package\Providers;

use Illuminate\Support\ServiceProvider;
use CapsulesCodes\Package\Console\Commands\GreetCommand;

class PackageServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->commands( [ GreetCommand::class ] );
    }
}
Enter fullscreen mode Exit fullscreen mode
  • The base Illuminate\Support\ServiceProvider class of Laravel is used as an extension.
  • The newly created GreetCommand command is added to the array requested by the this->commands method, allowing the template project to access the command.

 
 

The package must now inform the model project that a new ServiceProvider is available for discovery. This avoids the need to manually add it to the list of ServiceProviders in the template app.php configuration file.

 

package/composer.json

"extra" : { "laravel" : { "providers" : [ "CapsulesCodes\\Package\\Providers\\PackageServiceProvider" ] } },
Enter fullscreen mode Exit fullscreen mode

 
 

Test the php artisan greet command.

 

php artisan greet

"Hello world!" // ../package/src/Console/Commands/GreetCommand.php:17
Enter fullscreen mode Exit fullscreen mode

 
 

If you encounter any issues, it is recommended to execute the composer update command to reload the new package.

 
 

You can modify the web.php file to test the greet command via Artisan.

 

package/routes/web.php

<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Artisan;

Route::get( '/', fn() => Artisan::call( 'greet' ) );
Enter fullscreen mode Exit fullscreen mode
"Hello world!" // ../package/src/Console/Commands/GreetCommand.php:17
Enter fullscreen mode Exit fullscreen mode

 
 

And if you want to make your GreetCommand available only in console mode, add this condition to the PackageServiceProvider.

 
 

package/src/Providers/PackageServiceProvider.php

public function boot()
{
        if( $this->app->runningInConsole() ) $this->commands( [ GreetCommand::class ] );
}
Enter fullscreen mode Exit fullscreen mode

 
 

It is also possible to implement a config file, migrations, tests, routes, or views. Perhaps, that's something to consider later.

 
 

Glad this helped.

Top comments (0)