Create a Laravel package on your local environment
Tags: php, laravel, tutorial
Published: 2023-11-16 12:23:58

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

&nbsp;
&nbsp;

A sample Laravel project can be found on this [Github Repository](https://github.com/capsulescodes/articles).
Find out more on [Capsules](https://capsules.codes) or [X](https://x.com/capsulescodes).

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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

&nbsp;

```bash
mkdir package
```

&nbsp;

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

&nbsp;

`package/composer.json`


```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.

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

Create a folder `src` inside the `package` folder.

&nbsp;

```bash
cd package
mkdir src
```
- The arrangement of the files is not really important, except for their proximity, both for you and for this article.

&nbsp;
&nbsp;

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

&nbsp;

`package/src/Greeter.php`

```php
<?php

namespace CapsulesCodes\Package;

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

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

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

&nbsp;

```bash
cd ../../
composer create-project laravel/laravel template
```
- The `composer create-project laravel/laravel template` command generates a `'type': 'project'` here.

&nbsp;
&nbsp;

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.

&nbsp;

`template/composer.json`

```php
...
"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.

&nbsp;
&nbsp;

It is time to install our new package.

&nbsp;

```bash
composer require capsulescodes/package
```

&nbsp;
&nbsp;

It is now listed in the `require` dependencies.

&nbsp;

`package/composer.json`

```php
"require": {
	...
	"capsulescodes/package": "dev-main",
	...
}
```
- Since the package is still in development, the `dev-main` version is used.

&nbsp;
&nbsp;

Test via `php artisan tinker`

&nbsp;

```bash
php artisan tinker

> CapsulesCodes\Package\Greeter::greet()
= "Hello world!"
```

&nbsp;
&nbsp;

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

&nbsp;

`package/routes/web.php`

```php
<?php

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

Route::get( '/', fn() => dd( Greeter::greet() ) );
```

```
"Hello world!" // routes/web.php:7`
```

&nbsp;
&nbsp;

The work environment is ready.

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

`package/src/Greeter.php`

```php
<?php

namespace CapsulesCodes\Package;

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

    public static function say( string $something ) : string
    {
        return $something;
    }
}
```

&nbsp;
&nbsp;

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

&nbsp;

```bash
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!"
```

&nbsp;
&nbsp;

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

&nbsp;

`package/routes/web.php`

```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
```

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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.

&nbsp;
&nbsp;

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

&nbsp;

`package/src/Commands/GreetCommand.php`

```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() );
    }
}
```
- 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!"`.

&nbsp;
&nbsp;

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

&nbsp;

`package/src/Providers/PackageServiceProvider`

```php
<?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 ] );
    }
}
```
- 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.

&nbsp;
&nbsp;

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.

&nbsp;

`package/composer.json`

```php
"extra" : { "laravel" : { "providers" : [ "CapsulesCodes\\Package\\Providers\\PackageServiceProvider" ] } },
```

&nbsp;
&nbsp;

Test the `php artisan greet` command.

&nbsp;

```bash
php artisan greet

"Hello world!" // ../package/src/Console/Commands/GreetCommand.php:17
```

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

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

&nbsp;

`package/routes/web.php`

```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
```

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

`package/src/Providers/PackageServiceProvider.php`

```php
public function boot()
{
		if( $this->app->runningInConsole() ) $this->commands( [ GreetCommand::class ] );
}
```

&nbsp;
&nbsp;

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

&nbsp;
&nbsp;

Glad this helped.