zpmlabs/laravel-package-quick-demo
| Install | |
|---|---|
composer require zpmlabs/laravel-package-quick-demo |
|
| Latest Version: | v1.0.0 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | May 1, 2026 |
| Links: | GitHub · Packagist |
Laravel Package Quick Demo
Reusable quick-demo installer for Laravel packages.
zpmlabs/laravel-package-quick-demo lets Laravel package authors ship isolated demo environments inside any host Laravel application.
It is built for packages that want a “try it now” experience without asking users to manually copy files, edit .env, create database connections, register routes, or pollute their main application database.
With this package, a user can install a package demo with one command:
php artisan quick-demo:install blog-demo
Features
- Isolated SQLite database per demo.
- Runtime Laravel database connection per demo.
- Demo migrations.
- Demo seeders.
- Demo routes.
- Demo route prefixes and route name prefixes.
- Demo Blade view namespaces.
- Demo view publishing.
- Demo asset publishing.
- Demo storage file publishing.
- Demo config file publishing.
- Install hooks.
- Uninstall hooks.
- Safe uninstall command.
- Config-based and runtime-based demo registration.
Installation
Install the package:
composer require zpmlabs/laravel-package-quick-demo
Publish the config file if needed:
php artisan vendor:publish --tag=package-quick-demo-config
This publishes:
config/package-quick-demo.php
Configuration
Default config:
<?php
return [
'database_directory' => database_path('quick-demos'),
'default_database_stub' => null,
'connection_prefix' => 'quick_demo_',
'route_prefix' => 'quick-demo',
'route_name_prefix' => 'quick-demo.',
'route_middleware' => ['web'],
'views_directory' => resource_path('views/vendor/quick-demos'),
'assets_directory' => public_path('vendor/quick-demos'),
'storage_directory' => storage_path('app/quick-demos'),
'config_directory' => config_path('quick-demos'),
'demos' => [
// Config-based demo definitions may be placed here.
],
];
Most package authors do not need users to publish this config. Demos can be registered directly from a package service provider.
Basic usage in another package
Example package: vendor/blog-package.
Inside the package service provider:
<?php
namespace Vendor\BlogPackage;
use Illuminate\Support\ServiceProvider;
use ZPMLabs\LaravelPackageQuickDemo\Facades\QuickDemo;
use ZPMLabs\LaravelPackageQuickDemo\Support\DemoDefinition;
class BlogPackageServiceProvider extends ServiceProvider
{
public function boot(): void
{
QuickDemo::register(
DemoDefinition::make(
key: 'blog-demo',
name: 'Blog Demo',
databaseName: 'blog_demo',
databaseStubPath: __DIR__ . '/../examples/database/demo.sqlite.stub',
migrationsPath: __DIR__ . '/../examples/migrations',
seeders: [
\Vendor\BlogPackage\Database\Seeders\BlogDemoSeeder::class,
],
routesPath: __DIR__ . '/../examples/routes/demo.php',
viewsPath: __DIR__ . '/../examples/views',
assetsPath: __DIR__ . '/../examples/assets',
storagePath: __DIR__ . '/../examples/storage',
configPath: __DIR__ . '/../examples/config/demo.php',
)
);
}
}
Then the user runs:
php artisan quick-demo:install blog-demo
Minimal database-only demo
A demo does not need routes, views, assets, storage, or config.
For a database-only demo, register only database, migrations, and seeders:
QuickDemo::register(
DemoDefinition::make(
key: 'users-demo',
name: 'Users Demo',
databaseName: 'users_demo',
databaseStubPath: __DIR__ . '/../examples/database/demo.sqlite.stub',
migrationsPath: __DIR__ . '/../examples/migrations',
seeders: [
\Vendor\UsersPackage\Database\Seeders\UsersDemoSeeder::class,
],
)
);
Install it:
php artisan quick-demo:install users-demo
This creates a separate SQLite database and runs only the configured migrations and seeders.
Full demo package structure
Recommended structure for a package that ships a complete mini demo app:
blog-package/
composer.json
src/
BlogPackageServiceProvider.php
Http/
Controllers/
BlogDemoController.php
Models/
DemoPost.php
Database/
Seeders/
BlogDemoSeeder.php
examples/
database/
demo.sqlite.stub
migrations/
2026_01_01_000001_create_demo_posts_table.php
routes/
demo.php
views/
index.blade.php
show.blade.php
assets/
demo.css
demo.js
storage/
images/
demo-cover.jpg
config/
demo.php
Commands
List all registered demos:
php artisan quick-demo:list
Show details for one demo:
php artisan quick-demo:show blog-demo
Install a demo:
php artisan quick-demo:install blog-demo
Reinstall a demo from scratch:
php artisan quick-demo:install blog-demo --fresh
Reinstall from scratch and overwrite generated files:
php artisan quick-demo:install blog-demo --fresh --force
Skip migrations:
php artisan quick-demo:install blog-demo --no-migrate
Skip seeders:
php artisan quick-demo:install blog-demo --no-seed
Skip view publishing:
php artisan quick-demo:install blog-demo --no-views
Skip asset publishing:
php artisan quick-demo:install blog-demo --no-assets
Skip storage publishing:
php artisan quick-demo:install blog-demo --no-storage
Skip config publishing:
php artisan quick-demo:install blog-demo --no-config
Uninstall generated demo files:
php artisan quick-demo:uninstall blog-demo
Drop only the generated SQLite database:
php artisan quick-demo:uninstall blog-demo --drop-database
Remove everything generated by the demo:
php artisan quick-demo:uninstall blog-demo --all --force
SQLite database isolation
Every registered demo gets its own SQLite database.
For example:
databaseName: 'blog_demo'
creates:
database/quick-demos/blog_demo.sqlite
The package also registers a runtime Laravel connection:
quick_demo_blog_demo
Another package can register:
databaseName: 'users_demo'
which creates:
database/quick-demos/users_demo.sqlite
and registers:
quick_demo_users_demo
These databases are independent.
The package does not modify .env and does not require users to edit config/database.php.
SQLite stub files
Each demo can provide a SQLite stub file:
examples/database/demo.sqlite.stub
The stub may be:
- an empty SQLite file,
- a prebuilt SQLite database with schema,
- a prebuilt SQLite database with schema and demo data.
Recommended approach:
- keep the stub empty,
- create schema through migrations,
- create sample data through seeders.
Create an empty stub:
mkdir -p examples/database
touch examples/database/demo.sqlite.stub
Demo migrations
Every demo migration should explicitly use the quick-demo connection.
examples/migrations/2026_01_01_000001_create_demo_posts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use ZPMLabs\LaravelPackageQuickDemo\Facades\QuickDemo;
return new class extends Migration
{
public function getConnection(): string
{
return QuickDemo::connectionName('blog-demo');
}
public function up(): void
{
Schema::connection($this->getConnection())->create('demo_posts', function (Blueprint $table): void {
$table->id();
$table->string('title');
$table->text('body')->nullable();
$table->boolean('is_published')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::connection($this->getConnection())->dropIfExists('demo_posts');
}
};
This prevents demo tables from being created in the host application's default database.
Demo seeders
Every demo seeder should write to the quick-demo connection.
src/Database/Seeders/BlogDemoSeeder.php
<?php
namespace Vendor\BlogPackage\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use ZPMLabs\LaravelPackageQuickDemo\Facades\QuickDemo;
class BlogDemoSeeder extends Seeder
{
public function run(): void
{
DB::connection(QuickDemo::connectionName('blog-demo'))
->table('demo_posts')
->insert([
[
'title' => 'First demo post',
'body' => 'This post belongs to the isolated quick demo database.',
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'title' => 'Draft demo post',
'body' => 'This is another demo record.',
'is_published' => false,
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}
Demo models
Demo models should use the quick-demo connection.
src/Models/DemoPost.php
<?php
namespace Vendor\BlogPackage\Models;
use Illuminate\Database\Eloquent\Model;
use ZPMLabs\LaravelPackageQuickDemo\Facades\QuickDemo;
class DemoPost extends Model
{
protected $table = 'demo_posts';
protected $guarded = [];
public function getConnectionName(): string
{
return QuickDemo::connectionName('blog-demo');
}
}
Demo routes
A package can provide routes for a demo UI.
examples/routes/demo.php
<?php
use Illuminate\Support\Facades\Route;
use Vendor\BlogPackage\Http\Controllers\BlogDemoController;
Route::get('/', [BlogDemoController::class, 'index'])->name('index');
Route::get('/posts/{post}', [BlogDemoController::class, 'show'])->name('show');
The package automatically mounts these routes under a safe prefix:
/quick-demo/blog-demo
Route names are prefixed too:
quick-demo.blog_demo.index
quick-demo.blog_demo.show
This avoids collisions with the host application.
Demo controller
Example controller:
<?php
namespace Vendor\BlogPackage\Http\Controllers;
use Vendor\BlogPackage\Models\DemoPost;
class BlogDemoController
{
public function index()
{
return view('quick-demo-blog-demo::index', [
'posts' => DemoPost::query()->latest()->get(),
]);
}
public function show(DemoPost $post)
{
return view('quick-demo-blog-demo::show', [
'post' => $post,
]);
}
}
Demo views
A package can ship Blade views in:
examples/views
Example:
examples/views/index.blade.php
examples/views/show.blade.php
Views are registered under a generated namespace:
quick-demo-blog-demo
Usage:
return view('quick-demo-blog-demo::index', [
'posts' => $posts,
]);
When installed, views are published to:
resources/views/vendor/quick-demos/blog-demo
The published path is checked first. The package source path is used as fallback.
Demo assets
A package can ship demo assets in:
examples/assets
Example:
examples/assets/demo.css
examples/assets/demo.js
Assets are published to:
public/vendor/quick-demos/blog-demo
Usage in Blade:
<link rel="stylesheet" href="{{ asset('vendor/quick-demos/blog-demo/demo.css') }}">
<script src="{{ asset('vendor/quick-demos/blog-demo/demo.js') }}" defer></script>
Demo storage files
A package can ship sample storage files in:
examples/storage
Example:
examples/storage/images/demo-cover.jpg
Files are copied to:
storage/app/quick-demos/blog-demo
Usage:
storage_path('app/quick-demos/blog-demo/images/demo-cover.jpg')
Demo config files
A package can ship a demo config file:
examples/config/demo.php
It is copied to:
config/quick-demos/blog-demo.php
The package does not modify existing application config files.
Config-based demo registration
Demos may also be registered through config/package-quick-demo.php.
'demos' => [
'blog-demo' => [
'name' => 'Blog Demo',
'database_name' => 'blog_demo',
'database_stub_path' => base_path('vendor/vendor/blog-package/examples/database/demo.sqlite.stub'),
'migrations_path' => base_path('vendor/vendor/blog-package/examples/migrations'),
'seeders' => [
\Vendor\BlogPackage\Database\Seeders\BlogDemoSeeder::class,
],
'routes_path' => base_path('vendor/vendor/blog-package/examples/routes/demo.php'),
'views_path' => base_path('vendor/vendor/blog-package/examples/views'),
'assets_path' => base_path('vendor/vendor/blog-package/examples/assets'),
'storage_path' => base_path('vendor/vendor/blog-package/examples/storage'),
'config_path' => base_path('vendor/vendor/blog-package/examples/config/demo.php'),
],
],
Runtime registration from a package service provider is usually preferred.
Install and uninstall hooks
A demo can run custom logic before or after installation and uninstallation.
DemoDefinition::make(
key: 'blog-demo',
name: 'Blog Demo',
databaseName: 'blog_demo',
beforeInstall: \Vendor\BlogPackage\Demo\BeforeInstallBlogDemo::class,
afterInstall: \Vendor\BlogPackage\Demo\AfterInstallBlogDemo::class,
beforeUninstall: \Vendor\BlogPackage\Demo\BeforeUninstallBlogDemo::class,
afterUninstall: \Vendor\BlogPackage\Demo\AfterUninstallBlogDemo::class,
)
Hook classes should have a handle method:
<?php
namespace Vendor\BlogPackage\Demo;
use ZPMLabs\LaravelPackageQuickDemo\Support\QuickDemoContext;
class AfterInstallBlogDemo
{
public function handle(QuickDemoContext $context): void
{
// Custom demo setup logic.
}
}
The context contains useful information:
$context->definition;
$context->databasePath;
$context->connectionName;
$context->routePrefix;
$context->routeNamePrefix;
$context->viewNamespace;
$context->assetsDestination;
$context->storageDestination;
$context->configDestination;
$context->options;
Hooks can also be closures or callable arrays.
DemoDefinition::make(
key: 'blog-demo',
name: 'Blog Demo',
databaseName: 'blog_demo',
afterInstall: function (QuickDemoContext $context): void {
// Custom logic.
},
)
Safe uninstall behavior
By default, uninstall does not remove everything automatically.
php artisan quick-demo:uninstall blog-demo
Use specific flags:
php artisan quick-demo:uninstall blog-demo --drop-database
php artisan quick-demo:uninstall blog-demo --remove-views
php artisan quick-demo:uninstall blog-demo --remove-assets
php artisan quick-demo:uninstall blog-demo --remove-storage
php artisan quick-demo:uninstall blog-demo --remove-config
Remove everything:
php artisan quick-demo:uninstall blog-demo --all --force
Generated paths
For demo key:
blog-demo
and database name:
blog_demo
Generated paths are:
database/quick-demos/blog_demo.sqlite
resources/views/vendor/quick-demos/blog-demo
public/vendor/quick-demos/blog-demo
storage/app/quick-demos/blog-demo
config/quick-demos/blog-demo.php
Generated route prefix:
/quick-demo/blog-demo
Generated route name prefix:
quick-demo.blog_demo.
Generated view namespace:
quick-demo-blog-demo
Generated connection:
quick_demo_blog_demo
Recommended naming
Good demo keys:
blog-demo
users-demo
orders-demo
Avoid vague keys:
demo
example
test
Good database names:
blog_demo
users_demo
orders_demo
Good table names:
demo_posts
demo_users
demo_orders
Avoid using real application table names for demo tables:
posts
users
orders
Safety rules
This package is designed to avoid polluting the host Laravel application.
It does not require users to:
- edit
.env, - edit
config/database.php, - edit
routes/web.php, - create a database connection manually,
- copy demo files manually.
Package authors should avoid:
- writing demo tables into the default database,
- registering unprefixed routes,
- using generic route names,
- publishing files directly into root public/resource/storage folders,
- overwriting files without
--force, - using generic demo keys.
Testing a demo locally
From a Laravel test application:
composer require vendor/blog-package
php artisan quick-demo:list
php artisan quick-demo:show blog-demo
php artisan quick-demo:install blog-demo --fresh --force
php artisan serve
Open:
http://localhost:8000/quick-demo/blog-demo
Validate:
php artisan route:list
Check that:
- the demo route is prefixed,
- the demo route names are prefixed,
- the SQLite database exists,
- migrations ran on the quick-demo connection,
- seeders inserted demo data,
- views render correctly,
- assets load correctly,
- storage files were copied if configured,
- config file was copied if configured,
- the host default database was not modified.
License
MIT
Related Packages
Powerful PHP database abstraction layer (DBAL) with many features for database s...
Laravel Serializable Closure provides an easy and secure way to serialize closur...
Cli error handling for console/command-line PHP applications.