Blog / Create a Laravel package on your local environment
6 min - 16 Nov 2023
TL;DR: How to set up a local development environment to test your PHP package classes or utilities within a local Laravel project.
You will find the source code via this Github Repository. Find out more on Capsules, X or Bluesky.
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
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/" } }
}
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
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 static function greet() : string
{
return "Hello world!";
}
}
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
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" } ]
...
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.
cd template
composer require capsulescodes/package
It is now listed in the require
dependencies.
template/composer.json
"require": {
...
"capsulescodes/package": "dev-main",
...
}
dev-main
version is used.Test via php artisan tinker
php artisan tinker
> CapsulesCodes\Package\Greeter::greet()
= "Hello world!"
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() ) );
"Hello world!" // routes/web.php:7
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;
}
}
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!"
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!" ) ) );
"That's a quick way to develop and test a package!" // routes/web.php:7
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() );
}
}
Illuminate\Console\Command
class of Laravel is used as an extension.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 ] );
}
}
Illuminate\Support\ServiceProvider
class of Laravel is used as an extension.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" ] } },
Test the php artisan greet
command.
php artisan greet
"Hello world!" // ../package/src/Console/Commands/GreetCommand.php:17
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' ) );
"Hello world!" // ../package/src/Console/Commands/GreetCommand.php:17
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 ] );
}
It is also possible to implement a config
file, migrations
, tests
, routes
, or views
. Perhaps, that's something to consider later.
Glad this helped.