Create and publish a Laravel package on Packagist
You will need some knowledge of Laravel development and project structures. You will also need Composer installed on your machine.
Introduction
A Laravel package is a set of reusable classes created to add extra functionality to a Laravel website. In clearer terms, a package is to Laravel, what plugins are to WordPress. The primary goal of Laravel packages is to reduce development time by making reusable features into a set of standalone classes that can be used within any Laravel project.
In this tutorial, we will build a contact form Laravel package and be publishing it on Packagist. Let’s dive into it.
Perquisites
To follow this tutorial, you should have:
- Basic knowledge of Laravel v 5.5 upwards.
- Composer installed on your machine. If you don’t have Composer installed on your system you can download it here.
Getting started
Since a package is created to add extra functionality to a Laravel website, the first thing we need to do is set up a Laravel website. We’ll set it up using Composer
.
For other methods of installation you can check the official Laravel documentation here.
$ composer create-project --prefer-dist laravel/laravel packagetestapp
When it’s done you need to configure your env file and set your app key and other necessary details. In your terminal type:
$ cp .env.example .env
or manually copy the contents of .env.example
and save it in a new file as .env
. Then type:
$ php artisan key:generate
After generating your app key and configuring your database details, your .env
should look this:
At this point, our basic Laravel app is set up and it’s time to dive into developing our package.
Creating the bare bones of the package
We will install the bare bones of our package. There are primarily two ways of doing this either through the method of creating the individual files and folder or using a package like this CLI tool. We will manually create the files and folders so we can have a better understanding of how every piece fits together.
First, we need to create the folder structure for our package.
From your root directory create folders with this structure:
$ packages/MyVendor/MyPackage/
All our development will happen outside in our
packages/MyVendor/MyPackage/
directory instead ofvendor/MyVendor/MyPackage/
because it’s not good practice to change the code in the vendor folder.
When we are done publishing our package it will be downloadable to the vendor folder.
MyVendor
stands for the vendor’s name, which can be your name or the name of your client or organization you are creating the package for.
MyPackage
stands for the package name. In our case, it will becontactform
Now, let’s create the files and folders that will make up our package.
MyVendor
└── contactform
└── src
├── Database
│ └── migrations
├── Http
│ └── controllers
├── Models
├── resources
│ └── views
└── routes
Now our folders are set up we need to initialize Composer, so our package can be downloaded into our vendor folder later. At the root of your package, open a terminal and run:
$ composer init
Since it’s interactive, it will ask you a bunch of questions it will use to fill in your composer.json
file. Follow composer instructions, if you don’t know how to answer, press enter to use the default answer or you can change it later directly from the generated composer.json
.
Now your composer.json
should look like this:
Please note that you can subtitute
MyVendor
in the code below with your own vendor name. However, be sure to change the vendor name everywhere it is called.
{
"name": "MyVendor/Contactform",
"description": "A contact form package for laravel",
"authors": [{
"name": "samuel ogundipe",
"email": "email@email.com"
}],
"require": {}
}
In our composer.json
we need to tell it to autoload our files, add this code to your composer.json
:
"autoload": {
"psr-4": {
"MyVendor\\contactform\\": "src/"
}
}
At this point, our composer.json
file should look like this:
{
"name": "MyVendor/Contactform",
"description": "A contact form package for laravel",
"authors": [{
"name": "samuel ogundipe",
"email": "email@email.com"
}],
"require": {},
"autoload": {
"psr-4": {
"MyVendor\\Contactform\\": "src/"
}
}
}
Once that is done, create an empty git repository to keep track of changes (we’ll be adding the remote repo later). In your terminal type;
$ git init
Flesh out our package
Let’s add files to our package. First, we need to define a service provider for our package. A service provider is what Laravel
uses to determine the files that will be loaded and accessed by your package.
In your src/
folder create a file called ContactFormServiceProvider.php
. like this:
$ src/ContactFormServiceProvider.php
Inside our service provider we need to define a few things:
- The namespace (which we defined in our
composer.json autoload
). - The extension (the
Laravel
class which our service provider extends) - The two compulsory methods every service provider must have (every
Laravel
package service provider must have at least two methods:boot()
andregister()
).
Inside your service provider class add the following lines of code:
Please note that you can substitute
MyVendor
in the code below with your own vendor name. However, be sure to change the vendor name everywhere it is called.
<?php
// MyVendor\contactform\src\ContactFormServiceProvider.php
namespace MyVendor\contactform;
use Illuminate\Support\ServiceProvider;
class ContactFormServiceProvider extends ServiceProvider {
public function boot()
{
}
public function register()
{
}
}
?>
Since we haven’t deployed our package and it’s not yet inside our vendor folder we need to tell Laravel
how to load our package and use it’s functions, so inside the root of your Laravel app in the composer.json
add this code:
Please note that you can substitute
MyVendor
in the code below with your own vendor name. However, be sure to change the vendor name everywhere it is called.
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"MyVendor\\Contactform\\": "packages/MyVendor/contactform/src",
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"MyVendor\\Contactform\\": "packages/MyVendor/contactform/src",
"Tests\\": "tests/"
}
},
Depending on your Laravel version Laravel may automatically add it for you. Be sure to skip if it does.
After that on your terminal in the root of your app run:
$ composer dump-autoload
Now lets test and see if our package is being loaded correctly. Inside the boot method of your ContactFormServiceProvider.php
, let’s add a route and load it:
// MyVendor\contactform\src\ContactFormServiceProvider.php
$this->loadRoutesFrom(__DIR__.'/routes/web.php');
Please note that:
__DIR__
refers to the current directory where the file is.routes/web.php
refers to the routes folder we are to create for our package, which will live in oursrc
folder, not the defaultLaravel
routes.
In our package routes folder add the web.php
file and add the following code to it:
<?php
// MyVendor\contactform\src\routes\web.php
Route::get('contact', function(){
return 'Hello from the contact form package';
});
?>
Next, we need to add our new service provider in our root config/app.php
inside the providers
array:
// config/app.php
'providers' => [
...,
App\Providers\RouteServiceProvider::class,
// Our new package class
MyVendor\Contactform\ContactFormServiceProvider::class,
],
Now start your Laravel app using:
php artisan serve
On your browser navigate to localhost:8000/contact
and you should see this:
Now we know our package is loading properly we need to create our contact form. To do this we need to create a view and tell Laravel how to load that view. In your boot()
method type in the following;
// MyVendor\contactform\src\ContactFormServiceProvider.php
$this->loadViewsFrom(__DIR__.'/resources/views', 'contactform');
resources/views
refers to the resources folder we created for our package not the default Laravel resources folder.- To distinguish between the default Laravel views and package views, we have to add an extra parameter to our
loadviewsfrom()
function and that extra parameter should be the name of your package. In our case, it’scontactform
. So now whenever we want to load a view we reference it with thispackagename::view
syntax convention.
Adding the HTML structure
Now let’s create our contact form and adding the functionality. In your resources/views
folder create a file called contact.blade.php
then add the following lines of code to it:
<!-- MyVendor\contactform\src\resources\views\contact.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<title>Contact Us</title>
</head>
<body>
<div style="width: 500px; margin: 0 auto; margin-top: 90px;">
@if(session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<h3>Contact Us</h3>
<form action="{{route('contact')}}" method="POST">
@csrf
<div class="form-group">
<label for="exampleFormControlInput1">Your name</label>
<input type="text" class="form-control" name="name" id="exampleFormControlInput" placeholder="John Doe">
</div>
<div class="form-group">
<label for="exampleFormControlInput1">Email address</label>
<input type="email" class="form-control" name="email" id="exampleFormControlInput1" placeholder="name@example.com">
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">Enter Your Message</label>
<textarea class="form-control"name="message" id="exampleFormControlTextarea1" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
In our form action we defined a contact route and gave the form a post
method, we need to define it in our routes file else it will throw an error so in our routes/web.php
and replace with:
// MyVendor\contactform\src\routes\web.php
Route::get('contact', function(){
return view('contactform::contact');
});
Route::post('contact', function(){
// logic goes here
})->name('contact');
Now we can visit localhost:8000/contact and we’d see:
Now our routes/web.php
is containing code that should be in a controller, so we need to take our logic and place them into our controller files. First, we need to create the file in a new Http/controllers/
folder. Create a file called ContactFormController.php
.
Inside the ContactFormController.php
we will create two methods. One to show the page and the other one to send the mail inside the file add the following code:
<?php
// MyVendor\Contactform\src\Http\Controllers\ContactFormController.php
namespace MyVendor\Contactform\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use MyVendor\Contactform\Models\ContactForm;
class ContactFormController extends Controller {
public function index()
{
return view('contactform::contact');
}
public function sendMail(Request $request)
{
ContactForm::create($request->all());
return redirect(route('contact'));
}
}
Now change the code in your routes/web.php
file to:
<?php
// MyVendor\contactform\src\routes\web.php
Route::group(['namespace' => 'MyVendor\Contactform\Http\Controllers', 'middleware' => ['web']], function(){
Route::get('contact', 'ContactFormController@index');
Route::post('contact', 'ContactFormController@sendMail')->name('contact');
});
If you try loading the route without the namespace Laravel will throw an error because by default it looks in the base folder’s directory. So the namespace is added to tell it exactly where to load from.
Now let’s create a model that will help us relate with the database and some migrations alongside.
Inside the Models
folder, create a file named ContactForm.php
and add the following code to it.
<?php
// MyVendor\Contactform\src\Models\ContactForm.php
namespace MyVendor\Contactform\Models;
use Illuminate\Database\Eloquent\Model;
class ContactForm extends Model
{
protected $guarded = [];
protected $table = 'contact';
}
Now let’s create our migration so we can save the users details.
First, create the folder path Database/migrations
in your package’s src
folder.
Inside your terminal from the base directory of your app run this command:
Please note that you can substitute
MyVendor
in the code below with your own vendor name. However, be sure to change the vendor name everywhere it is called.
php artisan make:migration create_contact_table --path=packages/MyVendor/contactform/src/Database/migrations
Under your migrations folder you should now see the migration, add the following lines of code to it:
<?php
// // MyVendor\Contactform\src\Database\migrations\*_create_contact_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateContactTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('contact', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email');
$table->text('message');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('contact');
}
}
Now we need to run our migration. By default Laravel loads migrations from the base directory so we have to load in the migrations from the package so Laravel can load it too, in your serviceprovider
class add this code:
// MyVendor\contactform\src\ContactFormServiceProvider.php
$this->loadMigrationsFrom(__DIR__.'/Database/migrations');
now you can run
php artisan migrate
and it will populate your database.
Saving the mail to the database
Now let’s send the mail and save it in the database. In your controller, the sendMail
method already looks like this:
// MyVendor\Contactform\src\Http\Controllers\ContactFormController.php
public function sendMail(Request $request)
{
ContactForm::create($request->all());
return redirect(route('contact'));
}
Now go to your /contact
view on your browser and send a mail, it will be inserted into your database.
Adding a flash message on success
While we are sure that our contact form saves successfully, it will be a nice gesture to show a little message to our users telling them their mail has been sent successfully.
In our sendMail
method, let us replace the return statement with this:
return redirect(route('contact'))->with(['message' => 'Thank you, your mail has been sent successfully.']);
Next, in our view file, before the form declaration, let us print the message when we have one:
<!-- MyVendor\contactform\src\resources\views\contact.blade.php -->
@if(Session::has('message'))
{{Session::get("message")}}
@endif
Now we have been able to successfully save the data let’s upload it to Packagist.
Making the package available on Packagist
To make our package available on composer we need to upload it to Packagist. Before that, we need to update our remote repository. Head over to GitHub and create a new repository. Once done copy the clone link. In your terminal of your package folder enter:
git remote add origin [repository link]
After that, add everything to tracking and commit your code:
# add everything
git add .
# commit to git
git commit -m "commit message here"
Finally, push to your remote repo by typing;
git push origin master
Now go to Packagist and sign up. Click Submit. Copy the URL of your repository and submit it.
Now it is done, we need to tell Packgist to watch our repository for changes and always serve the latest version.
Goto: https://packagist.org/profile/ and get your API Token.
Now on your GitHub repository goto the settings tab. Under integration and services, click add a service and search for packagist.
Fill out the form with your name and token, skip URL and add the service.
Once that is done, click on test service and that’s it! Your package is now live.
Conclusion
In this tutorial, we’ve learnt how to create a Laravel package and publish it on Packagist. There are a lot more awesome things that can be built with this new knowledge.
The code base to this tutorial is available in this GitHub repository. Hack on!
14 September 2018
by Samuel Ogundipe