Create and publish a Laravel package on Packagist

  • Samuel Ogundipe
September 14th, 2018
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 of vendor/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 be contactform

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:

  1. The namespace (which we defined in our composer.json autoload).
  2. The extension (the Laravel class which our service provider extends)
  3. The two compulsory methods every service provider must have (every Laravel package service provider must have at least two methods: boot() and register() ).

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 our src folder, not the default Laravel 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's contactform. So now whenever we want to load a view we reference it with this packagename::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!

  • no pusher tech

© 2018 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.