My process for writing Laravel packages

my-process-for-writing-laravel-packages

Introduction

On my machine, I have a package folder. From here I make a folder for each package.

Laravel Packages

I then load these into my projects using composer.json

In the require section load the package by its vendor name followed by the package name then using @dev to load the main version

"dcblogdev/laravel-module-generator": "@dev"

Next in order to load this, tell composer where to find the files locally using a repository path:

"repositories": [
    {
        "type": "path",
        "url": "../../packages/laravel-module-generator"
    }
]

In this case, I need composer to look two folders above the current Laravel project.

Now run composer update to install the package into your project.

Whats good about this approach is you can do this with third-party packages. This is useful as you can run tests and add features that you plan to raise pull requests for.

Build a package

You can use package starter kits such as https://github.com/spatie/package-skeleton-laravel

I tend to build packages from scratch or copy one of my existing packages and remove what I dont need.

In this post, I’m imagining building a package called laravel-tags.

The folder structure will be:

Package Structure

license.md

For the license, I use The MIT licence which allows users to freely use, copy, modify, merge, publish, distribute, sublicense, and sell the software and its associated documentation.

this file contains:

# The license

The MIT License (MIT)

Copyright (c) 2023 dcblogdev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

composer.json

I’ll start by adding a composer.json file that contains:

{
  "name": "dcblogdev/laravel-tags",
  "description": "A Laravel package for adding tags to your content",
  "license": "MIT",
  "authors": [
    {
      "name": "David Carr",
      "email": "dave@dcblog.dev",
      "homepage": "https://dcblog.dev"
    }
  ],
  "homepage": "https://github.com/dcblogdev/laravel-tags",
  "keywords": [
    "Laravel",
    "Tags"
  ],
  "require-dev": {
    "orchestra/testbench": "^8.0",
    "pestphp/pest": "^v2.24.2",
    "pestphp/pest-plugin-type-coverage": "^2.4",
    "laravel/pint": "^1.13",
    "mockery/mockery": "^1.6"
  },
  "autoload": {
    "psr-4": {
      "Dcblogdev\Tags\": "src/",
      "Dcblogdev\Tags\Tests\": "tests"
    }
  },
  "autoload-dev": {
    "classmap": [
      "tests/TestCase.php"
    ]
  },
  "extra": {
    "laravel": {
      "providers": [
        "Dcblogdev\Tags\TagsServiceProvider"
      ],
      "aliases": {
        "Tags": "Dcblogdev\Tags\Facades\Tags"
      }
    }
  },
  "config": {
    "allow-plugins": {
      "pestphp/pest-plugin": true
    }
  },
  "scripts": {
    "pest": "vendor/bin/pest --parallel",
    "pest-coverage": "vendor/bin/pest --coverage",
    "pest-type": "vendor/bin/pest --type-coverage",
    "pint": "vendor/bin/pint"
  }
}

Breaking this down, first, the package needs a name in the convention of vendor name and package name dcblogdev/laravel-tags in my case the vendor name is dcblogdev and package name is laravel-tags

Add a require section if your package has dependencies

In a require-dev section, I import these third-party packages for development only

testbench package allows you to use Laravel conventions for testing.

pest is my testing framework of choice

pint to apply styling conventions to my codebase.

"orchestra/testbench": "^8.0",
"pestphp/pest": "^v2.24.2",
"pestphp/pest-plugin-type-coverage": "^2.4",
"laravel/pint": "^1.13",
"mockery/mockery": "^1.6"

Inside autoload to any folders that you want composer to autoload. I have a folder called src which will contain the classes and tests for all the tests

One important aspect of this package name I’ll often give the package a nickname to use so instead of using laravel-tags I’ll use tags

I’ll do this by using Tags in any classes namespace and creating an alias:

"extra": {
  "laravel": {
    "providers": [
      "Dcblogdev\Tags\TagsServiceProvider"
    ],
    "aliases": {
      "Tags": "Dcblogdev\Tags\Facades\Tags"
    }
  }
}

Tests

Now I will create 2 folders:

  1. src

  2. tests

Inside tests I’ll create these files:

TestCase.php

set('database.default', 'mysql');
        $app['config']->set('database.connections.mysql', [
            'driver' => 'sqlite',
            'host' => '127.0.0.1',
            'database' => ':memory:',
            'prefix' => '',
        ]);
    }

    protected function defineDatabaseMigrations()
    {
        $this->loadLaravelMigrations();

        $this->loadMigrationsFrom(dirname( __DIR__ ).'/src/database/migrations');
    }
}

The methods getEnviromentSetup and defineDatabaseMigrations are not needed by default. They are only required if you need to use a database and load migrations.

Inside getPackageProviders the main package service provider is loaded.

Now create a file called Pest.php

in( __DIR__ );

This file calls a uses() function to load the tests case I’ll use __DIR__ inside the in() method to make Pest use the TestCase class in the root of this directory.

Inside the package root create a phpunit.xml file containing:



  
    
      src/
    
  
  
    
      ./tests
    
  
  
    
  

This sets the location for tests to read from, it’s rare this file will need to be changed from this default.

I set an APP_KEY as standard for running tests, its value is not important.

Pint

To setup find create a folder in the package root called pint.json I use the Laravel preset:

{ 
    "preset": "laravel" 
}

ReadMe

inside the project root create a file called readme.md to document the project, I typically use this format:

# Laravel Tags

Explain what the package does.

# Documentation and install instructions 
[https://dcblog.dev/docs/laravel-tags](https://dcblog.dev/docs/laravel-tags)

## Change log

Please see the [changelog][3] for more information on what has changed recently.

## Contributing

Contributions are welcome and will be fully credited.

Contributions are accepted via Pull Requests on [Github][4].

## Pull Requests

- **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0][5]. Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

## Security

If you discover any security related issues, please email dave@dcblog.dev email instead of using the issue tracker.

## License

license. Please see the [license file][6] for more information.





ServiceProvider

Inside src create the service provider in my case TagsServiceProvider.php

configurePublishing();
    }

    public function configurePublishing(): void
    {
        if (! $this->app->runningInConsole()) {
            return;
        }

        $this->publishes([
            __DIR__.'/../config/tags.php' => config_path('tags.php'),
        ], 'config');
    }

    public function register(): void
    {
        $this->mergeConfigFrom( __DIR__.'/../config/tags.php', 'tags');

        // Register the service the package provides.
        $this->app->singleton('tags', function () {
            return new Tags;
        });
    }

    public function provides(): array
    {
        return ['tags'];
    }
}

Inside the boot function set called to other methods, to begin with I only have one called configurePublishing This will publish any files defined in this page and will only run when the class is ran from a CLI

The register method allows a published config file to be merged in with a local ./config/tags.php config file

And set up the main class to be instantiated.

Facades

If you want to make use of facade ie have static called to a class that would normally be instantiated.

Inside src create a folder called facades and your class such as Tags.php

Resources

For more details read https://www.laravelpackage.com

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
is-content-marketing-more-effective-than-native-advertising?

Is Content Marketing More Effective Than Native Advertising?

Next Post
how-to-check-if-a-value-is-an-object-in-javascript

How to Check if a Value is an Object in JavaScript

Related Posts