Develop Your Own Laravel Package
Guide on developing and testing Laravel packages locally and installing them using composer in a local project.
The Laravel ecosystem offers numerous convenient packages for various purposes.
However, what if you can’t find a suitable package or need to create a company-specific one?
This article demonstrates how to develop a package locally and install it in a Laravel project using composer, ensuring smooth development and testing.
Some articles suggest creating a packages folder within an existing Laravel project for package development.
However, this approach has drawbacks. Imagine deleting your project one dayβyour package project would be deleted too.
Additionally, this method doesn’t facilitate testing the installation via composer. This article presents an alternative approach.
1. Initialize Package Project
First, create a folder for your package; for example, we’ll use larapeko as a sample.
mkdir larapeko
Navigate to the newly created larapeko folder and run the following command to initialize the package.
cd larapeko
composer init
You’ll be prompted with a series of questions to configure basic package information.

Afterward, you’ll find the composer.json file in the folder.
{
"name": "ray247k/larapeko",
"description": "A package for demo peko",
"license": "MIT",
"authors": [
{
"name": "Ray",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"require": {}
}
2. Write Package Code
Create a src folder in the larapeko directory to host the package code.
Inside, create a file named LaraPeko.php and set the appropriate namespace.
For demonstration purposes, let’s start with a simple function.
<?php
# LaraPeko.php
namespace Ray247k\LaraPeko;
class LaraPeko
{
public function sayPeko()
{
echo "peko peko\n";
}
}
3. ServiceProvider
Create a ServiceProvider file in the src directory, named LaraPekoServiceProvider.php.
In the register method, use a singleton to register a class named LaraPeko and return a new instance of it.
<?php
# LaraPekoServiceProvider.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\ServiceProvider;
class LaraPekoServiceProvider extends ServiceProvider
{
public function boot()
{
}
// Register the package
public function register()
{
$this->app->singleton('LaraPeko', function ($app) {
return new LaraPeko();
});
}
}
4. Facade
Facades
provide a static interface to call registered classes directly.
Create a Facade file in the src directory, named LaraPekoFacade.php, and use the LaraPeko class registered in the ServiceProvider as the Laravel Facade object.
<?php
# LaraPekoFacade.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\Facades\Facade;
class LaraPekoFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'LaraPeko';
}
}
5. Default Config and Usage
Sometimes, users may want to customize parameters through a configuration file.
Create a config folder in the package directory and add a configuration file, lara_peko.php.
<?php
# lara_peko.php
return [
'best_girl' => 'Yagoo',
];
Edit the LaraPekoServiceProvider.php file to include publishing the configuration file.
<?php
# LaraPekoServiceProvider.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\ServiceProvider;
class LaraPekoServiceProvider extends ServiceProvider
{
public function boot()
{
$source = realpath($raw = __DIR__.'/../config/lara_peko.php') ?: $raw;
$this->publishes([
$source => config_path('lara_peko.php'),
]);
}
// Register the package
public function register()
{
$configPath = __DIR__ . '/../config/lara_peko.php';
$this->mergeConfigFrom($configPath, 'lara_peko');
$this->app->singleton('LaraPeko', function ($app) {
return new LaraPeko();
});
}
}
After running php artisan vendor:publish, choose the corresponding number for the package when prompted.
This will generate a configuration template file in the project’s config directory.
If you are sure which package’s config file you want to create, you can directly specify the provider tag.
php artisan vendor:publish --provider="Ray247k\LaraPeko\LaraPekoServiceProvider"
In the register method, the default and new configuration files are merged.
If users modify the project’s configuration file, it will override the default configuration, allowing for customization.
Clear Configuration Cache
When the configuration changes, use the following commands to clear the project’s configuration cache.
php artisan config:clear # Clear the configuration cache
php artisan cache:clear # Clear the general cache
Using Config Parameters
To use the configuration in your code, employ the config() method.
In the LaraPeko.php file, create a getBestGirl method to retrieve the best_girl parameter from the configuration.
<?php
# LaraPeko.php
namespace Ray247k\LaraPeko;
class LaraPeko
{
public function sayPeko()
{
echo "peko peko\n";
}
public function getBestGirl()
{
echo config('lara_peko.best_girl');
}
}
6. Package Auto-discovery
Introduced in Laravel 5.5, this feature allows users to reduce the steps required during package installation by configuring the package settings.
The goal is to eliminate manual editing of the config/app.php file’s providers and aliases arrays after running composer install.
Keywords: Package Auto-discovery, Laravel Extension Package Auto-discovery.
Procedure:
Add the following content to the autoload and extra sections in the package’s composer.json.
Due to length constraints, only the added parts are shown:
{
"autoload": {
"psr-4": {
"Ray\\LaraPeko\\": "src"
}
},
"extra": {
"laravel": {
"providers": [
"Ray\\LaraPeko\\LaraPekoServiceProvider"
],
"aliases": {
"LaraPeko": "Ray\\LaraPeko\\LaraPekoFacade"
}
}
}
}
Through the laravel definition in the extra section, a data entry is added to the providers and aliases arrays for registration.
The autoload uses the PSR-4 rule to point the specified namespace to the folder where our package code is located (src).
7. Package Dependencies
If your package depends on another package, such as the commonly used Guzzle, include the required item in the package’s composer.json:
{
"name": "ray247k/larapeko",
"description": "A package for demo peko",
"license": "MIT",
"authors": [
{
"name": "Ray",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"require": {
"php": ">=7.3",
"laravel/framework": "5.5.*||^6.0|^7.0|^8.0",
"guzzlehttp/guzzle": "^6.3"
}
}
In addition to requiring Guzzle, we have specified the required PHP version and Laravel framework version.
8. Package Testing
Setting up a Test Project
After the above process, our package is almost configured. Next, we’ll enter the testing phase.
Since we intend to use it in a Laravel project, let’s create a new Laravel project for testing, named “lara-ahoy”:
laravel new lara-ahoy
You can check the installed Laravel version with the following command:
cd <project directory>
php artisan -V
# Laravel Framework 6.20.30
Including the Package in the Project
Add Package Entry
To use the package in the project, use the following command to add the package’s version storage location to the composer.json:
composer config repositories.ray247k path ../larapeko
This assumes that the package (larapeko) and the test project (lara-ahoy) are in the same directory.
After running the command, open the composer.json file in the lara-ahoy project, and you’ll see a new section:
{
"repositories": {
"ray247k": {
"type": "path",
"url": "../larapeko"
}
}
}
You can manually add this section if you prefer not to use the command.
If the package is in a remote repository, change the type to vcs and add the URL:
composer config repositories.ray247k vcs https://github.com/ray247k/larapeko
Or manually adjust the repositories setting in composer.json:
{
"repositories": {
"ray247k": {
"type": "vcs",
"url": "https://github.com/ray247k/larapeko"
}
}
}
The remote URL can use either https or ssh. If the repository is private, use ssh
(e.g., [email protected]:ray247k/larapeko.git).
Add Dependency
You can use the following command:
composer require ray247k/larapeko @dev
Or manually add the following content to the require section in composer.json:
{
"require": {
"ray247k/larapeko": "@dev"
}
}
If an error occurs during the require command due to an issue with a dependency’s version, you can use the --ignore-platform-reqs parameter to ignore the platform:
composer require ray247k/larapeko --ignore-platform-reqs
After adding the local dependency, it will automatically fetch the latest code each time it is used.
If it doesn’t automatically fetch or if you prefer to run composer require every time to update, adjust the symlink setting in the test project’s composer.json under the repositories section:
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"options": {
"symlink": false
}
}
]
}
Install the Package
Install the package using:
composer install
In the output, you should see that the package has been discovered and installed:
Discovered Package: ray247k/larapeko
Package manifest generated successfully.
If a version error occurs similar to during the require command, use the --ignore-platform-reqs parameter:
composer install --ignore-platform-reqs
Open the composer.lock file in the lara-ahoy project to verify that the package has been successfully installed.
If you have previously run the project, you must clear the existing autoload cache:
composer dump-autoload
Testing the Package
In the previous steps, we installed our larapeko package into the test project lara-ahoy.
Now, we’ll test whether the package has been correctly integrated using two methods: file testing and unit testing.
Method 1: File Testing
Create a file named test-autoload.php directly in the Laravel project’s root directory.
In this file, load the autoload and call the package method:
<?php
# test-autoload.php
use Ray247k\LaraPeko\LaraPeko;
require_once './vendor/autoload.php';
LaraPeko::sayPeko();
Run the test file in the lara-ahoy project directory:
php test-autoload.php
If the autoload is successful, you should see the result of the sayPeko() method, even if a PHP Deprecated message appears:

Although a PHP Deprecated message is displayed, the default string content is still successfully printed.
This indicates that the Facade aliases registered in the ServiceProvider were called successfully!
To suppress the PHP Deprecated message, add error_reporting(0); to the test-autoload.php file:
<?php
error_reporting(0);
use Ray247k\LaraP
eko\LaraPeko;
require_once './vendor/autoload.php';
LaraPeko::sayPeko();
Now, you won’t see the PHP Deprecated message:

Method 2: Unit Testing
Next, let’s use PHPUnit to perform unit tests. Run the following command in the project directory:
php vendor/bin/phpunit
If you encounter an error related to an invalid value for PHP_VERSION, consider using Homebrew to install PHP or running the tests in a Docker environment.
If php vendor/bin/phpunit works, proceed to build the unit test file.
Add a testing method getAhoy() to LaraPeko.php:
public function getAhoy()
{
return "ahoy";
}
Create a tests folder inside the package project and add a test file named LaraPekoTest.php:
<?php
namespace Ray247k\LaraPeko;
use PHPUnit\Framework\TestCase;
class LaraPekoTest extends TestCase
{
/**
* @test
*
* @return void
*/
public function testClassInstance()
{
$this->assertInstanceOf(LaraPekoTest::class, new LaraPekoTest);
}
public function testGetAhoy()
{
$larapeko = new LaraPeko();
$this->assertEquals('ahoy', $larapeko->getAhoy());
}
}
This test checks whether the class instance is correctly created and whether the getAhoy() method returns “ahoy”.
Run the test using the following command:
php vendor/bin/phpunit vendor/ray247k/larapeko/tests
You should see the PHPUnit results:

Note:
Suppose there is a private function testMethod() in the LaraPeko package:
private function testMethod()
{
return 'Haha';
}
To test a private function, you can use the bindTo() method within a closure.
After creating an object, manually inject it into the closure object to replace $this in the closure.
This way, it’s like directly calling the testMethod() method using the LaraPeko object:
/**
* @test
*
* @return void
*/
public function testMethodTest()
{
$contentSegment = new LaraPeko();
$closure = function () {
return $this->testMethod();
};
$closure_bind = $closure->bindTo($contentSegment, $contentSegment);
$this->assertEquals('Haha', $closure_bind());
}
This allows testing private methods effectively!
The above is a comprehensive guide for developing your own Laravel package.
If you have any further questions or topics to discuss, feel free to let me know. I’m happy to assist!
