Skip to content

Instantly share code, notes, and snippets.

@AHS12
Last active February 7, 2026 15:04
Show Gist options
  • Select an option

  • Save AHS12/2fd9ccf5f667e0f84abdfea37a4ea4aa to your computer and use it in GitHub Desktop.

Select an option

Save AHS12/2fd9ccf5f667e0f84abdfea37a4ea4aa to your computer and use it in GitHub Desktop.

Revisions

  1. AHS12 revised this gist May 21, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions how to create a Laravel package.md
    Original file line number Diff line number Diff line change
    @@ -516,7 +516,7 @@ Good documentation is crucial for package adoption. Update your `README.md` with

    Here's a sample structure for your README:

    ```markdown
    ````markdown
    # Your Package Name

    [![Latest Version on Packagist](https://img.shields.io/packagist/v/your-vendor/your-package-name.svg?style=flat-square)](https://packagist.org/packages/your-vendor/your-package-name)
    @@ -608,7 +608,7 @@ Please review [our security policy](../../security/policy) on how to report secu
    ## License

    The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
    ```
    ````

    ## 6. Publishing Your Package

  2. AHS12 created this gist May 21, 2025.
    673 changes: 673 additions & 0 deletions how to create a Laravel package.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,673 @@
    # Complete Guide to Creating a Laravel Package

    This guide will walk you through creating a Laravel package from scratch using Spatie's package skeleton. We'll create a settings management package, organized as a proper Laravel package that can be shared and reused.

    ## Table of Contents

    1. [Setting up the Package Skeleton](#1-setting-up-the-package-skeleton)
    2. [Understanding the Package Structure](#2-understanding-the-package-structure)
    3. [Implementation of the Settings Package](#3-implementation-of-the-settings-package)
    4. [Testing Your Package](#4-testing-your-package)
    5. [Documentation](#5-documentation)
    6. [Publishing Your Package](#6-publishing-your-package)
    7. [Maintaining Your Package](#7-maintaining-your-package)

    ## 1. Setting up the Package Skeleton

    ### Prerequisites

    - Composer installed
    - Git installed
    - GitHub account (optional but recommended)

    ### Creating a New Package

    1. Use Composer to create a new package based on Spatie's skeleton:

    ```bash
    composer create-project spatie/package-skeleton-laravel your-package-name
    ```

    2. Navigate to the package directory:

    ```bash
    cd your-package-name
    ```

    3. The skeleton will ask you several questions to set up your package:
    - Vendor name (typically your GitHub username or organization name)
    - Package name (e.g., "laravel-settings")
    - Package description
    - Author name and email
    - GitHub repository details

    4. Initialize git and make your first commit:

    ```bash
    git init
    git add .
    git commit -m "Initial commit"
    ```

    5. If you have a GitHub account, create a repository and push your code:

    ```bash
    git remote add origin https://github.com/your-username/your-package-name.git
    git push -u origin main
    ```

    ## 2. Understanding the Package Structure

    The skeleton creates a standard package structure:

    ```
    .
    ├── .github/ # GitHub workflows and templates
    ├── config/ # Configuration files
    ├── database/ # Migrations
    ├── resources/ # Views, language files, etc.
    ├── src/ # Your PHP code
    ├── tests/ # Test files
    ├── .editorconfig # Editor settings
    ├── .gitattributes # Git attributes
    ├── .gitignore # Git ignore rules
    ├── CHANGELOG.md # Change log
    ├── composer.json # Composer configuration
    ├── LICENSE.md # License
    ├── README.md # Documentation
    ├── phpunit.xml.dist # PHPUnit configuration
    ```

    Key files and directories:

    - `src/`: This is where all your main PHP code will reside
    - `config/`: Configuration files that users can publish
    - `database/migrations/`: Migration files that users can publish
    - `tests/`: All your test files
    - `composer.json`: Defines your package dependencies and metadata

    ## 3. Implementation of the Settings Package

    Let's implement our settings management package. We'll create the following components:

    ### Step 1: Define the Package Service Provider

    Edit `src/YourPackageNameServiceProvider.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName;

    use Illuminate\Support\ServiceProvider;
    use YourVendor\YourPackageName\Commands\RefreshSettingsCommand;
    use YourVendor\YourPackageName\Services\SettingsService;

    class YourPackageNameServiceProvider extends ServiceProvider
    {
    public function configurePackage(Package $package): void
    {
    $package
    ->name('your-package-name')
    ->hasConfigFile()
    ->hasMigration('create_settings_table')
    ->hasCommand(RefreshSettingsCommand::class);
    }

    public function packageRegistered()
    {
    // Register the settings service in the container
    $this->app->singleton(SettingsService::class, function ($app) {
    return new SettingsService();
    });

    // Register an alias for easier access
    $this->app->alias(SettingsService::class, 'settings');
    }
    }
    ```

    ### Step 2: Create the Configuration File

    Create `config/your-package-name.php`:

    ```php
    <?php

    return [
    /*
    |--------------------------------------------------------------------------
    | Settings Cache
    |--------------------------------------------------------------------------
    |
    | This option controls whether the settings are cached.
    |
    */
    'cache' => [
    'enabled' => env('SETTINGS_CACHE_ENABLED', true),
    'ttl' => env('SETTINGS_CACHE_TTL', 3600), // 1 hour
    ],

    /*
    |--------------------------------------------------------------------------
    | Settings Table
    |--------------------------------------------------------------------------
    |
    | The database table used to store settings.
    |
    */
    'table' => 'settings',
    ];
    ```

    ### Step 3: Create the Migration File

    Create `database/migrations/create_settings_table.php.stub`:

    ```php
    <?php

    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;

    return new class extends Migration
    {
    public function up()
    {
    Schema::create(config('your-package-name.table', 'settings'), function (Blueprint $table) {
    $table->id();
    $table->string('key')->unique();
    $table->text('value')->nullable();
    $table->text('description')->nullable();
    $table->string('type')->default('boolean');
    $table->timestamps();
    });
    }

    public function down()
    {
    Schema::dropIfExists(config('your-package-name.table', 'settings'));
    }
    };
    ```

    ### Step 4: Create the Setting Model

    Create `src/Models/Setting.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName\Models;

    use Illuminate\Database\Eloquent\Model;

    class Setting extends Model
    {
    protected $fillable = ['key', 'value', 'description', 'type'];

    public function getValueAttribute($value)
    {
    return match($this->type) {
    'boolean' => (bool) $value,
    'integer' => (int) $value,
    'json' => json_decode($value, true),
    default => $value,
    };
    }

    public function setValueAttribute($value)
    {
    if ($this->type === 'json' && is_array($value)) {
    $this->attributes['value'] = json_encode($value);
    } else {
    $this->attributes['value'] = $value;
    }
    }

    public function getTable()
    {
    return config('your-package-name.table', parent::getTable());
    }
    }
    ```

    ### Step 5: Create the Settings Service

    Create `src/Services/SettingsService.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName\Services;

    use Illuminate\Support\Collection;
    use Illuminate\Support\Facades\Cache;
    use YourVendor\YourPackageName\Models\Setting;

    class SettingsService
    {
    protected Collection $settings;

    public function __construct()
    {
    $this->loadSettings();
    }

    protected function loadSettings(): void
    {
    $cacheEnabled = config('your-package-name.cache.enabled', true);
    $cacheTtl = config('your-package-name.cache.ttl', 3600);

    if ($cacheEnabled) {
    $this->settings = Cache::remember('settings', $cacheTtl, function () {
    return $this->getSettingsFromDatabase();
    });
    } else {
    $this->settings = $this->getSettingsFromDatabase();
    }
    }

    protected function getSettingsFromDatabase(): Collection
    {
    $settings = collect();
    Setting::all()->each(function ($setting) use ($settings) {
    $settings->put($setting->key, $setting->value);
    });

    return $settings;
    }

    public function get(string $key, $default = null)
    {
    return $this->settings->get($key, $default);
    }

    public function set(string $key, $value): void
    {
    $setting = Setting::firstOrCreate(['key' => $key]);
    $setting->value = $value;
    $setting->type = is_bool($value) ? 'boolean' : (is_int($value) ? 'integer' : (is_array($value) ? 'json' : 'string'));
    $setting->save();

    $this->settings->put($key, $value);

    if (config('your-package-name.cache.enabled', true)) {
    Cache::put('settings', $this->settings, config('your-package-name.cache.ttl', 3600));
    }
    }

    public function all(): Collection
    {
    return $this->settings;
    }

    public function has(string $key): bool
    {
    return $this->settings->has($key);
    }

    public function forget(string $key): void
    {
    Setting::where('key', $key)->delete();
    $this->settings->forget($key);

    if (config('your-package-name.cache.enabled', true)) {
    Cache::put('settings', $this->settings, config('your-package-name.cache.ttl', 3600));
    }
    }
    }
    ```

    ### Step 6: Create a Facade for Easy Access

    Create `src/Facades/Settings.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName\Facades;

    use Illuminate\Support\Facades\Facade;
    use YourVendor\YourPackageName\Services\SettingsService;

    /**
    * @method static mixed get(string $key, mixed $default = null)
    * @method static void set(string $key, mixed $value)
    * @method static \Illuminate\Support\Collection all()
    * @method static bool has(string $key)
    * @method static void forget(string $key)
    *
    * @see \YourVendor\YourPackageName\Services\SettingsService
    */
    class Settings extends Facade
    {
    protected static function getFacadeAccessor()
    {
    return SettingsService::class;
    }
    }
    ```

    ### Step 7: Create the Command

    Create `src/Commands/RefreshSettingsCommand.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName\Commands;

    use Illuminate\Console\Command;
    use YourVendor\YourPackageName\Models\Setting;

    class RefreshSettingsCommand extends Command
    {
    protected $signature = 'settings:refresh {--force : Force update of all settings to default values}';
    protected $description = 'Refresh settings structure';

    public function handle()
    {
    $settings = config('your-package-name.default_settings', []);
    $force = $this->option('force');

    foreach ($settings as $key => $default) {
    $setting = Setting::firstOrCreate(['key' => $key]);

    if ($setting->wasRecentlyCreated || $force) {
    $setting->value = $default['value'] ?? null;
    $this->info("Setting '{$key}' value set to default");
    }

    $setting->description = $default['description'] ?? '';
    $setting->type = $default['type'] ?? 'string';
    $setting->save();

    if ($setting->wasRecentlyCreated) {
    $this->info("Setting '{$key}' created.");
    } else {
    $this->line("Setting '{$key}' updated.");
    }
    }

    $this->info('Settings refresh completed!');

    return Command::SUCCESS;
    }
    }
    ```

    ### Step 8: Update Package-Specific Config

    Update the config file to include default settings:

    ```php
    // In config/your-package-name.php add:

    'default_settings' => [
    'auto_clear_empty_inventory' => [
    'value' => true,
    'description' => 'Automatically remove inventory items when quantity reaches zero',
    'type' => 'boolean',
    ],
    // Add more default settings as needed
    ],
    ```

    ## 4. Testing Your Package

    ### Writing Tests

    The skeleton comes with a basic test setup. Let's add some tests for our settings package:

    Create `tests/SettingsServiceTest.php`:

    ```php
    <?php

    namespace YourVendor\YourPackageName\Tests;

    use YourVendor\YourPackageName\Facades\Settings;
    use YourVendor\YourPackageName\Models\Setting;

    class SettingsServiceTest extends TestCase
    {
    public function setUp(): void
    {
    parent::setUp();

    // Migrate the settings table
    $this->artisan('migrate');
    }

    /** @test */
    public function it_can_get_and_set_settings()
    {
    // Set a setting
    Settings::set('test_key', 'test_value');

    // Check it was saved to the database
    $this->assertDatabaseHas('settings', [
    'key' => 'test_key',
    ]);

    // Check we can retrieve it
    $this->assertEquals('test_value', Settings::get('test_key'));
    }

    /** @test */
    public function it_handles_different_types_of_settings()
    {
    // Boolean
    Settings::set('boolean_setting', true);
    $this->assertIsBool(Settings::get('boolean_setting'));
    $this->assertTrue(Settings::get('boolean_setting'));

    // Integer
    Settings::set('integer_setting', 123);
    $this->assertIsInt(Settings::get('integer_setting'));
    $this->assertEquals(123, Settings::get('integer_setting'));

    // JSON/Array
    $array = ['key' => 'value', 'nested' => ['data' => true]];
    Settings::set('json_setting', $array);
    $this->assertEquals($array, Settings::get('json_setting'));
    }

    /** @test */
    public function it_returns_default_value_when_setting_not_found()
    {
    $this->assertEquals('default', Settings::get('nonexistent', 'default'));
    }

    /** @test */
    public function it_can_forget_settings()
    {
    Settings::set('temporary', 'value');
    $this->assertTrue(Settings::has('temporary'));

    Settings::forget('temporary');
    $this->assertFalse(Settings::has('temporary'));
    $this->assertDatabaseMissing('settings', [
    'key' => 'temporary',
    ]);
    }
    }
    ```

    ### Running Tests

    Run your tests with:

    ```bash
    composer test
    ```

    ## 5. Documentation

    Good documentation is crucial for package adoption. Update your `README.md` with:

    - Installation instructions
    - Configuration details
    - Usage examples
    - API documentation
    - Contributing guidelines

    Here's a sample structure for your README:

    ```markdown
    # Your Package Name

    [![Latest Version on Packagist](https://img.shields.io/packagist/v/your-vendor/your-package-name.svg?style=flat-square)](https://packagist.org/packages/your-vendor/your-package-name)
    [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/your-vendor/your-package-name/run-tests?label=tests)](https://github.com/your-vendor/your-package-name/actions?query=workflow%3Arun-tests+branch%3Amain)
    [![Total Downloads](https://img.shields.io/packagist/dt/your-vendor/your-package-name.svg?style=flat-square)](https://packagist.org/packages/your-vendor/your-package-name)

    A Laravel package for managing application settings with database storage and caching.

    ## Installation

    You can install the package via composer:

    ```bash
    composer require your-vendor/your-package-name
    ```

    You can publish and run the migrations with:

    ```bash
    php artisan vendor:publish --tag="your-package-name-migrations"
    php artisan migrate
    ```

    You can publish the config file with:

    ```bash
    php artisan vendor:publish --tag="your-package-name-config"
    ```

    ## Usage

    ```php
    // Get a setting with a default fallback value
    $value = Settings::get('auto_clear_empty_inventory', true);

    // Set a setting
    Settings::set('auto_clear_empty_inventory', false);

    // Check if a setting exists
    if (Settings::has('feature_x')) {
    // Do something
    }

    // Get all settings
    $allSettings = Settings::all();

    // Remove a setting
    Settings::forget('temporary_feature');
    ```

    ## Configuration

    The configuration file allows you to customize:

    - Cache behavior for settings
    - Database table name
    - Default settings values

    ```php
    // In a service provider or middleware
    if (app('settings')->get('auto_clear_empty_inventory')) {
    // Do something
    }
    ```

    ## Testing

    ```bash
    composer test
    ```

    ## Changelog

    Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

    ## Contributing

    Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

    ## Security Vulnerabilities

    Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

    ## Credits

    - [Your Name](https://github.com/yourusername)
    - [All Contributors](../../contributors)

    ## License

    The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
    ```

    ## 6. Publishing Your Package

    ### Preparing for Release

    1. Make sure all tests pass
    2. Update your README.md with complete documentation
    3. Update the CHANGELOG.md file
    4. Check that your composer.json is complete and accurate

    ### Publishing to Packagist

    1. Create a release on GitHub
    2. Submit your package to Packagist: https://packagist.org/packages/submit
    3. Connect your Packagist account to GitHub for automatic updates

    ## 7. Maintaining Your Package

    ### Version Control

    Follow semantic versioning:
    - MAJOR version for incompatible API changes
    - MINOR version for new functionality that is backward-compatible
    - PATCH version for backward-compatible bug fixes

    ### Supporting Multiple Laravel Versions

    In your `composer.json`, you can specify Laravel version compatibility:

    ```json
    "require": {
    "php": "^8.0",
    "illuminate/support": "^8.0|^9.0|^10.0"
    },
    ```

    ### Continuous Integration

    Use GitHub Actions (already set up in the skeleton) to:
    - Run tests on multiple PHP and Laravel versions
    - Check code style
    - Generate code coverage reports

    ### Handling Community Contributions

    - Be responsive to issues and pull requests
    - Keep a CONTRIBUTING.md file with guidelines
    - Maintain a CODE_OF_CONDUCT.md file

    ## Conclusion

    Creating a Laravel package allows you to share your code with the community and use it across multiple projects. This settings package is a perfect example of functionality that can be extracted into a reusable component.

    By following the structure and principles outlined in this guide, you'll create a well-organized, tested, and documented package that follows Laravel best practices.

    Remember that the key factors for package adoption are:
    1. Solving a common problem
    2. Excellent documentation
    3. Comprehensive testing
    4. Active maintenance

    Good luck with your package development journey!