Last active
February 7, 2026 15:04
-
-
Save AHS12/2fd9ccf5f667e0f84abdfea37a4ea4aa to your computer and use it in GitHub Desktop.
Revisions
-
AHS12 revised this gist
May 21, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 # Your Package Name [](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 -
AHS12 created this gist
May 21, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 [](https://packagist.org/packages/your-vendor/your-package-name) [](https://github.com/your-vendor/your-package-name/actions?query=workflow%3Arun-tests+branch%3Amain) [](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!