How to Generate a Package in Laravel and Why It's Important
Introduction
As a developer working on multiple projects, I noticed that some functionalities are often the same. To save time and ensure consistency, I built several packages for personal use. These include:
- CRUD Generator with Command Line: Automates CRUD operations via the command line.
- Enhanced CRUD Generator with UI: Adds a user interface for generating packages with options like relationships, datatables (normal or Livewire), and frontend frameworks (Bootstrap or Tailwind).
- Domain-Driven Design Generator: Helps structure existing projects based on domain-driven design principles.
- Test Case Generator: Automatically generates basic test cases for CRUD operations.
All these projects are currently private. In this blog, we'll focus on creating a simple package to understand the process better.
Why Create Packages?
Creating packages in Laravel can greatly enhance productivity by:
- Reusability: Common functionalities can be reused across multiple projects.
- Consistency: Ensures the same standards and practices are followed.
- Modularity: Helps keep the codebase clean and organized.
- Ease of Maintenance: Bugs and updates can be managed in a single place.
Creating a Laravel Package
Here’s a step-by-step guide to creating a Laravel package.
Step 1: Setup
Create a new folder named package-generator
. Inside this folder, create a composer.json
file with the following content:
{
"name": "thecodersprint/package-generator",
"description": "A package to generate Laravel packages",
"autoload": {
"psr-4": {
"Thecodersprint\\\\PackageGenerator\\\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Thecodersprint\\\\PackageGenerator\\\\PackageGeneratorServiceProvider"
]
}
},
"require": {}
}
Step 2: Directory Structure
We will create the following directory structure inside your package:
package-generator/
├── config/
│ └── config.php
├── src/
│ ├── Console/
│ │ └── Commands/
│ ├── Http/
│ │ ├── Controllers/
│ ├── Resources/
│ │ ├── views/
│ ├── Routes/
│ │ └── web.php
│ └── PackageGeneratorServiceProvider.php
└── composer.json
Step 3: Service Provider
Create a PackageServiceProvider.php
in the src
folder:
<?php
namespace Thecodersprint\\PackageGenerator;
use Illuminate\\Support\\ServiceProvider;
class PackageGeneratorServiceProvider extends ServiceProvider
{
public function boot()
{
$this->publishes([
__DIR__.'/../config/config.php' => config_path('package-generator.php'),
], 'config');
}
public function register()
{
//
}
}
Step 4: Configuration File
Create a config.php
file in the config
folder:
<?php
return [
// Configuration options
];
Step 5: Test the Package
Update the composer.json
of your existing Laravel project to include your package:
"repositories": [
{
"type": "path",
"url": "/path/to/package-generator",
"options": {
"symlink": true
}
}
],
"require": {
"thecodersprint/package-generator": "dev-master"
}
-
repositories: This section tells Composer where to find the package.
-
type:
"path"
indicates the package is in a specific directory on your system. - url: This is the path to the package directory.
-
options:
{ "symlink": true }
allows the package to be symlinked, so changes in the package directory will be reflected in the project immediately.
-
type:
-
require: This section lists the packages your project depends on.
- thecodersprint/package-generator: This is the name of your package.
-
"dev-master": This specifies the version of the package to use.
"dev-master"
means it will use the latest version from the master branch.
Run composer update
to install the package.
Then, we will create a route to test if it is working properly:
Route::get('test', function() {
return "Welcome to our package";
});
Access your-project.test/test
to see the message "Welcome to our package."
Now we will try to publish our config file to check if the code is working.
php artisan vendor:publish
Then, we will select our service provider.
Step 6: Create a Command to Generate a Package
To create a command that generates the package boilerplate, we need to follow these steps:
- Create Stub Files
- Create the Command Class
- Create Directory Structure
- Create Essential Files
- Create Optional Components
6.1 Create Stub Files
We need to create stub files that will serve as templates for generating the package files.
If you don’t know about stubs, I'll explain. Stubs are template files used to generate code. They contain placeholders for elements like class names, namespaces, and configurations. When creating a new package, stubs help quickly generate the necessary files with the correct structure and content. This ensures consistency across all generated packages and saves time by automating repetitive tasks.
Create a resources/stubs
directory and add the following stub files:
-
composer.stub
-
ServiceProvider.stub
-
MainClass.stub
-
Config.stub
-
Facade.stub
-
Controller.stub
-
Middleware.stub
-
LivewireComponent.stub
-
Routes.stub
-
BladeView.stub
-
Lang.stub
-
ExampleTest.stub
Here are examples of what these stub files might contain:
composer.stub
{
"name": "{{ vendor }}/{{ name }}",
"description": "A Laravel package",
"autoload": {
"psr-4": {
"{{ vendor }}\\\\{{ name }}\\\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"{{ vendor }}\\\\{{ name }}\\\\PackageServiceProvider"
]
}
},
"require": {}
}
ServiceProvider.stub
<?php
namespace {{ vendor }}\\{{ name }};
use Illuminate\\Support\\ServiceProvider;
class PackageServiceProvider extends ServiceProvider
{
public function boot()
{
$this->publishes([
__DIR__.'/../config/config.php' => config_path('{{ name }}.php'),
], 'config');
}
public function register()
{
//
}
}
MainClass.stub
<?php
namespace {{ vendor }}\\{{ name }};
class {{ name }}
{
//
}
Config.stub
<?php
return [
// Configuration options
];
Create similar stub files for the other components as needed.
6.2 Create the Command Class
To generate the package structure, we need to create a command class that encapsulates the package generation logic. This command will be responsible for creating the necessary directories and files for the package based on the provided vendor and package names.
First, create a new file named GeneratePackage.php
in the src/Console/Commands
directory with the following content:
<?php
namespace Thecodersprint\\\\PackageGenerator\\\\Console\\\\Commands;
use Illuminate\\\\Console\\\\Command;
use Illuminate\\\\Support\\\\Str;
use File;
class GeneratePackage extends Command
{
// Define the command signature and description
protected $signature = 'make:package {vendor} {name}';
protected $description = 'Generate a Laravel package structure';
// The handle method is the entry point for the command execution
public function handle()
{
// Retrieve the vendor and package name arguments
$vendor = $this->argument('vendor');
$name = $this->argument('name');
$packagePath = base_path("packages/{$vendor}/{$name}");
// Inform the user that the package structure is being created
$this->info("Creating package structure for {$vendor}/{$name}");
// Create the necessary directories and files for the package
$this->createDirectories($packagePath);
$this->createFiles($packagePath, $vendor, $name);
// Inform the user that the package has been successfully generated
$this->info("Package {$vendor}/{$name} generated successfully.");
}
}
In this GeneratePackage
class:
-
Command Definition:
- The
protected $signature
defines how the command is called from the terminal, including the expected arguments (vendor
andname
). - The
protected $description
provides a brief description of what the command does.
- The
-
Handle Method:
- The
handle
method is the main entry point for the command execution. When the command is run, this method is called. - It retrieves the
vendor
andname
arguments provided by the user. - It constructs the path where the package will be created (
$packagePath
). - It then calls helper methods to create the necessary directories and files for the new package.
- The
-
Creating Directories and Files:
- The
createDirectories
method (to be defined later) will handle the creation of the required directory structure. - The
createFiles
method (to be defined later) will handle the creation of the essential files using predefined stubs.
- The
We will register our command in our service provider boot method.
// Register the command if we are running in the console
if ($this->app->runningInConsole()) {
$this->commands([
\Thecodersprint\PackageGenerator\Console\Commands\GeneratePackage::class,
]);
}
6.3 Create Directory Structure
Next, we need to create the directory structure for the package. Add the createDirectories
method to the command class:
private function createDirectories($packagePath)
{
$directories = [
"{$packagePath}/config",
"{$packagePath}/src/Facades",
"{$packagePath}/src/Console/Commands",
"{$packagePath}/src/Http/Controllers",
"{$packagePath}/src/Http/Middleware",
"{$packagePath}/src/Models",
"{$packagePath}/src/Resources/lang/en",
"{$packagePath}/src/Resources/views",
"{$packagePath}/src/Routes",
"{$packagePath}/tests/Feature",
"{$packagePath}/tests/Unit",
];
foreach ($directories as $dir) {
if (!File::exists($dir)) {
File::makeDirectory($dir, 0755, true);
}
}
}
This method creates the necessary directories for the package. It checks if each directory exists and creates it if it doesn't.
6.4 Create Essential Files
Now, we need to create the essential files for the package. Add the createFiles
method to the command class:
private function createFiles($packagePath, $vendor, $name)
{
$this->createFile("{$packagePath}/composer.json", $this->getStub('composer.stub', $vendor, $name));
$this->createFile("{$packagePath}/src/PackageServiceProvider.php", $this->getStub('ServiceProvider.stub', $vendor, $name));
$this->createFile("{$packagePath}/src/{$name}.php", $this->getStub('MainClass.stub', $vendor, $name));
$this->createFile("{$packagePath}/config/config.php", $this->getStub('Config.stub'));
}
private function createFile($path, $content)
{
File::put($path, $content);
}
private function getStub($stub, $vendor = null, $name = null)
{
$stubPath = __DIR__ . "/../../../resources/stubs/{$stub}";
$stubContent = File::get($stubPath);
if ($vendor && $name) {
$stubContent = str_replace(
['{{ vendor }}', '{{ name }}'],
[$vendor, $name],
$stubContent
);
}
return $stubContent;
}
The createFiles
method generates the essential files for the package using stub templates. The getStub
method reads the stub file and replaces placeholders with the actual vendor and package names.
6.5 Create Optional Components
Next, we will prompt the user for additional components and generate them accordingly. Add the createOptionalComponents
method to the command class:
private function createOptionalComponents($packagePath, $vendor, $name)
{
if ($this->confirm('Do you need a facade?')) {
$this->createFile("{$packagePath}/src/Facades/{$name}Facade.php", $this->getStub('Facade.stub', $vendor, $name));
}
if ($this->confirm('Do you need a controller?')) {
$this->createFile("{$packagePath}/src/Http/Controllers/{$name}Controller.php", $this->getStub('Controller.stub', $vendor, $name));
}
if ($this->confirm('Do you need middleware?')) {
$this->createFile("{$packagePath}/src/Http/Middleware/{$name}Middleware.php", $this->getStub('Middleware.stub', $vendor, $name));
}
if ($this->confirm('Do you need Livewire components?')) {
$this->createFile("{$packagePath}/src/Http/Livewire/{$name}Component.php", $this->getStub('LivewireComponent.stub', $vendor, $name));
$this->updateComposerJsonForLivewire($packagePath);
}
if ($this->confirm('Do you need routes?')) {
$this->createFile("{$packagePath}/src/routes/web.php", $this->getStub('Routes.stub'));
}
if ($this->confirm('Do you need views?')) {
$this->createFile("{$packagePath}/src/resources/views/example.blade.php", $this->getStub('BladeView.stub'));
}
if ($this->confirm('Do you need language files?')) {
$this->createFile("{$packagePath}/src/resources/lang/en/messages.php", $this->getStub('Lang.stub'));
}
if ($this->confirm('Do you need tests?')) {
$this->createFile("{$packagePath}/tests/Feature/ExampleTest.php", $this->getStub('ExampleTest.stub'));
}
}
private function updateComposerJsonForLivewire($packagePath)
{
$composerPath = "{$packagePath}/composer.json";
$composerContent = json_decode(File::get($composerPath), true);
$composerContent['require']['livewire/livewire'] = "^3.0";
File::put($composerPath, json_encode($composerContent, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
This method prompts the user for each optional component and generates the necessary files if the user confirms.
Here is the complete class code:
<?php
namespace Thecodersprint\\PackageGenerator\\Console\\Commands;
use Illuminate\\Console\\Command;
use Illuminate\\Support\\Str;
use File;
class GeneratePackage extends Command
{
protected $signature = 'make:package {vendor} {name}';
protected $description = 'Generate a Laravel package structure';
public function handle()
{
$vendor = $this->argument('vendor');
$name = $this->argument('name');
$packagePath = base_path("packages/{$vendor}/{$name}");
$this->info("Creating package structure for {$vendor}/{$name}");
// Create directories and files
$this->createDirectories($packagePath);
$this->createFiles($packagePath, $vendor, $name);
$this->info("Package {$vendor}/{$name} generated successfully.");
}
private function createDirectories($packagePath)
{
$directories = [
"{$packagePath}/config",
"{$packagePath}/src/Facades",
"{$packagePath}/src/Console/Commands",
"{$packagePath}/src/Http/Controllers",
"{$packagePath}/src/Http/Middleware",
"{$packagePath}/src/Models",
"{$packagePath}/src/Resources/lang/en",
"{$packagePath}/src/Resources/views",
"{$packagePath}/src/Routes",
"{$packagePath}/tests/Feature",
"{$packagePath}/tests/Unit",
];
foreach ($directories as $dir) {
if (!File::exists($dir)) {
File::makeDirectory($dir, 0755, true);
}
}
}
private function createFiles($packagePath, $vendor, $name)
{
$this->createFile("{$packagePath}/composer.json", $this->getStub('composer.stub', $vendor, $name));
$this->createFile("{$packagePath}/src/PackageServiceProvider.php", $this->getStub('ServiceProvider.stub', $vendor, $name));
$this->createFile("{$packagePath}/src/{$name}.php", $this->getStub('MainClass.stub', $vendor, $name));
$this->createFile("{$packagePath}/config/config.php", $this->getStub('Config.stub'));
}
private function createFile($path, $content)
{
File::put($path, $content);
}
private function getStub($stub, $vendor = null, $name = null)
{
$stubPath = __DIR__ . "/../../../resources/stubs/{$stub}";
$stubContent = File::get($stubPath);
if ($vendor && $name) {
$stubContent = str_replace(
['{{ vendor }}', '{{ name }}'],
[$vendor, $name],
$stubContent
);
}
return $stubContent;
}
}
Step 7: Test the Command
Run the command to generate a new package:
php artisan make:package thecodersprint sample
Follow the prompts to customize your package structure. Here is the result of the above command:
Conclusion
By following these steps, you can create a Laravel package that automates repetitive tasks and improves your workflow. This example demonstrates the basics, but you can expand it to fit your specific needs.
Creating packages not only saves time but also ensures that your projects adhere to consistent standards. Happy coding!
- Tags:
- #Clean Code
- #Laravel
- #Prompts
Author: Manish Kumar
I've been working with PHP for over 11 years and focusing on Laravel for the last 6 years. During this time, I've taken part in big projects and gotten to know tools like Laravel Livewire really well. Besides working on projects, I enjoy writing about Laravel on LinkedIn and Medium to share what I've learned. I'm always looking to learn more and share my knowledge with others. Right now, I'm diving into new areas like using Docker to make web apps run better and exploring how to build them using microservices. My goal is to keep learning and help others do the same in the world of web development.
Recent Posts
Writing Clean Code in Laravel: Principles and Tools
Manish Kumar
15 Jun 2024
Basic Useful Commands for Developers
Manish Kumar
12 Jun 2024
Exploring Key Design Patterns in Software Development
Manish Kumar
29 May 2024
Laravel Telescope: Debugging Made Easy (Part - 2)
Sonu Singh
25 Apr 2024
Laravel Telescope: Debugging Made Easy (Part - 1)
Sonu Singh
24 Apr 2024
Subscribe to Newsletter
Provide your email to get email notification when we launch new products or publish new articles