popphp/pop-kettle

A CLI helper application for the Pop PHP Framework.

Maintainers

Package info

github.com/popphp/pop-kettle

pkg:composer/popphp/pop-kettle

Transparency log

Statistics

Installs: 5 001

Dependents: 3

Suggesters: 0

Stars: 4

Open Issues: 0

3.0.6 2026-08-19 05:45 UTC

README

Build Status Coverage Status

Join the chat at https://discord.gg/TZjgT74U7E

Overview

pop-kettle is a CLI-helper application for the Pop PHP Framework that allows a user to quickly build the scaffolding for an application. It is included with the Pop PHP Framework as the command kettle within the main project directory.

Top

Install

The pop-kettle component comes automatically installed when you install the full Pop PHP Framework. You should see the kettle script in the main project directory. However, if that is not the case, and you need to install it manually, you can place a copy of the kettle script from the vendor/popphp/pop-kettle/kettle location in the main project folder (adjacent to the vendor folder):

$ cp vendor/popphp/pop-kettle/kettle .

Once you've copied the script over, you have to change the reference to the script's config file from:

    $config = include __DIR__ . '/config/app.console.php';

to

    $config = include __DIR__ . '/vendor/popphp/pop-kettle/config/app.console.php';

since that config file (which just returns the routes array) lives in the pop-kettle package, not in your project's own config folder, and make sure the newly copied kettle script is set to execute (755)

$ chmod 755 kettle

Top

Initializing an Application

By running the following command, you can set up the basic files and folders required to run an application:

./kettle app:init

app:init takes no parameters or flags - everything is gathered through a series of prompts.

You're first asked for the namespace of your application - it defaults to MyApp if left blank. It's normalized into a valid PHP namespace: each \- or /-separated segment is split into words on hyphens, underscores, and camelCase boundaries and re-cased, so my-user-app becomes MyUserApp and My\Users\App stays a three-segment namespace. If your namespace contains backslashes, quote it (e.g. "My\Users\App") - typed unquoted at the shell, the backslashes are stripped by bash itself before kettle ever sees the value.

Next you're prompted for the app's display name, defaulting to a human-readable version of the namespace (e.g. My\Users\AppMy Users App).

You're then asked which application type(s) to install:

Which application type(s)?

1: Web
2: API
3: CLI

Select one or more, comma-separated: [1]

Enter one or more numbers separated by commas (e.g. 1,3), or leave it blank to accept the default of Web alone. These create the related files and folders to run the application as a normal web application, an API-driven web application, a CLI-driven console application, or any combination thereof. The default route for the web application or the API application is /. However, if both are initialized, then the default route for the API application becomes /api. The web application will deliver a placeholder HTML page and the API application will deliver a placeholder JSON response.

If Web and/or API were selected, the front controller will be located in public/index.php (there is no public folder, and therefore no public/index.php, for a CLI-only install). If CLI was selected, you'll be prompted to optionally initialize a stand-alone CLI application as well; if you accept, its main script will be located at script/myapp, renamed to a kebab-case slug built from the same normalized namespace (e.g. My\Users\Appscript/my-users-app). See Application Console Scripts below for more on that stand-alone script versus registering one-off commands directly with kettle.

If Web and/or API were selected, you'll also be prompted for the app's URL (defaulting to http://localhost). Finally, you're asked whether you'd like to configure a database. If you accept, the application files and folders are copied over first, and then you'll immediately be walked through the database adapter/connection prompts to create the database configuration file. See Managing the Database below for the list of supported database adapters and what you'll be prompted for. The application environment always starts out as local - use app:env --set (below) to change it afterward.

app:init also registers your app's namespace directly in composer.json's autoload.psr-4 map (e.g. "App\\": "app/src/") and runs composer dump-autoload, so public/index.php, a stand-alone script/<app>, and kettle itself all pick up your classes from the one Composer-generated autoloader - no separate include file to keep in sync. If Composer isn't found on your PATH, you'll see a warning and can run composer dump-autoload yourself afterward.

Application Status

You can view and manage the status of the application with the following commands outlined below.

Check (or change) the current environment:

The environment is set in the .env file under the APP_ENV variable. Options available are:

  • local
  • dev
  • testing
  • staging
  • production (or prod)
./kettle app:env

To change it, pass --set and pick from the same numbered list:

./kettle app:env --set
1: local
2: dev
3: testing
4: staging
5: production

Please select an app environment from above:

Only one of those five values can be entered - anything else just re-prompts - so there's no risk of writing an invalid APP_ENV value into .env. Once set, it shows you the same colorized status box as plain app:env, reflecting the new value:

                                        
         Application in Staging        
                                        

Check (or change) the current status:

The status of the application can either be "live" or in "maintenance mode". The value is set in the .env file under the MAINTENANCE_MODE variable (true or false).

./kettle app:status

To put the application into maintenance mode, where it's not accessible, use the following command:

./kettle app:down

You can generate a "secret" key to allow a select set of users to view the application while still in maintenance mode:

./kettle app:down --secret

When the command finishes, it will output the auto-generated secret:

    The secret is SECRET_STRING

You can also provide your own secret:

./kettle app:down --secret=MY_SECRET_STRING

Use that string one time in the browser as a URL query parameter to view the application while it is still in maintenance mode. It will store in the browser's cookies so subsequent requests will be valid:

http://localhost:8000/?secret=MY_SECRET_STRING

To take the application out of maintenance mode and make it live again, use the following command:

./kettle app:up

Top

Front-End Assets

app:init will additionally prompt you to install a front-end whenever the application type(s) you selected include Web - that's Web on its own, Web + API, Web + CLI, all three together, or simply an empty selection at that prompt, since Web is assumed by default:

Would you like to install a front-end? [Y/N]

If you accept, you'll be shown a numbered list to pick from:

1: AlpineJS
2: Vue
3: React

Whichever framework you pick, Tailwind CSS v4 is scaffolded right alongside it, configured CSS-first via the @tailwindcss/vite plugin - there's no tailwind.config.js to maintain. Vite is the build tool for all three.

The scaffolded source assets land in app/assets/css and app/assets/js, and package.json and vite.config.js are placed in the project root, next to the kettle script itself. app/view/index.phtml is generated already pointing at the eventual build output. Vite is configured (via rollupOptions.output.entryFileNames/assetFileNames) to always write that output to the same, fixed, non-hashed paths - public/assets/js/app.js and public/assets/css/app.css - for both a watch build and a production build, so those <link>/<script> tags in index.phtml never need to change between the two.

Once the files are scaffolded, app:init automatically runs npm install in the project root, then npm run build (the same thing web:build runs), so the landing page is already built and styled the first time you hit it - no extra step needed before you see it working. If Node/npm isn't found on your PATH, app:init still finishes successfully - you'll just see a warning telling you to install Node and then run npm install/npm run build yourself before using the commands below.

./kettle web:watch   Watch/rebuild front-end assets on file changes
./kettle web:build   Build front-end assets for production

web:watch runs npm run watch (vite build --watch), which rebuilds public/assets/js/app.js and public/assets/css/app.css to disk on every save - there's no dev server or hot-module-reload, so you refresh the browser yourself to pick up changes. web:build runs npm run build, a one-shot production build. Both are thin convenience wrappers around those two npm scripts - you could just as easily run npm run watch/npm run build directly - and both simply print a message and do nothing if no front-end was installed for the project, or if Node/npm isn't on your PATH.

Top

Managing the Database

Once the application is initialized, you can manage the database, or multiple databases, by using the db and migrate commands. If you don't pass anything in the optional [<database>] parameter, it will default to the default database. Passing all in place of <database> (where supported above) runs the command against every database that has a folder under /database/migrations.

Both db:config and db:install (which runs db:config followed by db:test and db:seed) will present a numbered list of the database adapters available on your PHP install to choose from — typically some combination of PDO (MySQL, PostgreSQL, SQLite) and the native mysqli/sqlite adapters, depending on which PHP extensions are enabled. If you choose a MySQL or PostgreSQL adapter, you'll be prompted for the DB name, user, password, and host (defaults to localhost), and the connection is tested before the config file is written — you'll be re-prompted on failure. If you choose the SQLite adapter, you'll only be prompted for a DB name, and a corresponding .sqlite file is created under /database.

./kettle db:install [<database>]                 Install the database (Runs config, test & seed)
./kettle db:config [<database>]                  Configure the database
./kettle db:test [<database>]                    Test the database connection
./kettle db:create-seed <seed> [<database>]      Create database seed class
./kettle db:seed [<database>]                    Seed the database with data
./kettle db:export [<database>]                  Export the database to a file (MySQL only)
./kettle db:import <file> [<database>]           Import the database from a file (MySQL only)
./kettle db:reset [<database>]                   Reset the database with original seed data
./kettle db:clear [<database>]                   Clear the database of all data

./kettle migrate:create <class> [<database>]     Create new database migration class
./kettle migrate:run [<steps>] [<database>]      Perform forward database migration
./kettle migrate:rollback [<steps>] [<database>] Perform backward database migration
./kettle migrate:point [<id>] [<database>]       Point to specific migration, w/o running
./kettle migrate:reset [<database>]              Perform complete rollback of the database

Seeding the Database

You can seed the database with data in one of two ways. You can either utilize a SQL file with the extension .sql in the /database/seeds/<database> folder, or you can write a seeder class using PHP. To create a seeder class, you can run:

./kettle db:create-seed <seed> [<database>]

Where the <seed> is the base class name of the seeder class that will be created. The template seeder class will be copied to the /database/seeds/<database> folder:

<?php

use Pop\Db\Adapter\AbstractAdapter;
use Pop\Db\Sql\Seeder\AbstractSeeder;

class MyFirstSeeder extends AbstractSeeder
{

    public function run(AbstractAdapter $db): void
    {
        
    }

}

From there, you can fill in the run() method in the seeder class with the SQL you need to seed your data:

<?php

use Pop\Db\Adapter\AbstractAdapter;
use Pop\Db\Sql\Seeder\AbstractSeeder;

class DatabaseSeeder extends AbstractSeeder
{
    
    public function run(AbstractAdapter $db): void
    {
        $sql = $db->createSql();
        
        $sql->insert('users')->values([
            'username' => 'testuser',
            'password' => '12test34',
            'email'    => 'test@test.com'
        ]);
        
        $db->query($sql);
    }
    
}

Then running the following command will execute any SQL in the seeder classes or any raw SQL in SQL files:

./kettle db:seed

Database Migrations

You can create the initial database migration that would modify your database schema as your application grows by running the command:

./kettle migrate:create <class> [<database>]

Where the <class> is the base class name of the migration class that will be created. You will see your new migration class template in the /database/migrations/<database> folder:

<?php

use Pop\Db\Sql\Migration\AbstractMigration;

class MyFirstMigration5dd822cdede29 extends AbstractMigration
{

    public function up(): void
    {

    }

    public function down(): void
    {

    }

} 

From there, you can populate the up() and down() with the schema to modify your database:

<?php

use Pop\Db\Sql\Migration\AbstractMigration;

class MyFirstMigration5dd822cdede29 extends AbstractMigration
{

    public function up(): void
    {
        $schema = $this->db->createSchema();
        $schema->create('users')
            ->int('id', 16)->increment()
            ->varchar('username', 255)
            ->varchar('password', 255)
            ->varchar('email', 255)
            ->primary('id');
        
        $schema->execute();     
    }

    public function down(): void
    {
        $schema = $this->db->createSchema();
        $schema->drop('users');
        $schema->execute();
    }

}

You can run the migration and create the users table by running the command:

./kettle migrate:run

And you can rollback the migration and drop the users table by running the command:

./kettle migrate:rollback

Migration State Storage

The migration state storage can be stored in one of two places. By default, it will store in a file called .current in the database migration folder, for example:

/database/migrations/default/.current

However, it can also be stored in the database itself in a separate migrations table. This requires a file called .table to be placed in the database migration folder:

/database/migrations/default/.table

The contents of the table will be the table class name for the migrations table in the database, for example:

MyApp\Table\Migrations

Top

Queue Commands

Kettle can configure and run a pop-queue worker against your application's own command routes. Creating jobs and scheduled tasks is entirely your application's responsibility ($queue->addJob() / $queue->addTask(), wherever that makes sense in your own code) — these commands only configure the connection and run/administer what's already there.

./kettle queue:config [<queue>]                              Configure a queue
./kettle queue:work [-o|--once] [-s|--sleep=] [<queue>]      Run the queue worker to process jobs
./kettle queue:scheduler [-o|--once] [-s|--sleep=] [<queue>] Run scheduler to process tasks
./kettle queue:clear [-f|--failed] [-t|--tasks] [<queue>]    Clear the queue
./kettle queue:jobs [<queue>]                                List pending and dead-letter queue jobs
./kettle queue:tasks [<queue>]                               List scheduled queue tasks
./kettle queue:config [<queue>]

Configure a queue (File, Database, or Redis adapter). Defaults to default. Pass any other name to configure an additional queue, the same way db:config <database> works.

./kettle queue:work [-o|--once] [-s|--sleep=] [<queue>]

Run the worker. Without --once, runs as a daemon until stopped (Ctrl+C). With --once, processes a single pass and exits - useful for a cron-driven setup instead of a supervised daemon. Pass all as <queue> to service every configured queue in one worker, weighted by each queue's configured weight.

./kettle queue:scheduler [-o|--once] [-s|--sleep=] [<queue>]

Same shape as queue:work, but for scheduled tasks.

./kettle queue:clear [-f|--failed] [-t|--tasks] [<queue>] 

Clear pending and in-flight (leased) jobs by default - not completed ones; --failed clears the dead-letter queue instead, --tasks clears scheduled tasks instead. Flags combine. Use with caution: running this against a queue a worker is currently servicing drops undone work, including jobs that worker has already leased.

./kettle queue:jobs [<queue>] 

Show pending and dead-letter job counts, and list dead-letter jobs with their failure reason.

./kettle queue:tasks [<queue>]  

List scheduled tasks with their cron expression and grace period.

Top

Creating Application Files

You can create skeleton application files with the create commands to assist you in wiring up various MVC-based components, such as commands, controllers, models and views:

./kettle create:command [-a|--app] <command>        Create a new CLI command
./kettle create:ctrl [--web] [--api] [--cli] <ctrl> Create a new controller class
./kettle create:model [-d|--data] <model>           Create a new model class
./kettle create:view <view>                         Create a new view file

(See Creating Custom Commands below for more on create:command.)

For create:ctrl, --web, --api, and --cli may be combined to create more than one controller class at once (one per flag), targeting whichever of those flavors were installed by app:init. If none of the three flags are passed, a single generic HTTP controller is created under app/src/Http/Controller/ instead (regardless of whether the app was installed with --web, --api, or both).

Once the respective class files or view scripts are created in the appropriate folders, you can then open them up and begin writing your application code.

Data Model

The --data option for the create:model command creates a model class that extends the Pop\Utils\AbstractDataModel class, as well as a table class to interface with the corresponding table in the database. For example, assuming the namespace of the application is MyApp, the command:

./kettle create:model --data User

will create class files for MyApp\Model\User (extending the data model class Pop\Db\Model\AbstractDataModel) and MyApp\Table\Users. From there, you can begin to store and retrieve data from the users table in the database with very little additional coding.

Top

Creating Custom Commands

There are two ways to add custom, application-specific CLI commands to your project: registering lightweight Kettle Commands directly with the kettle script itself, or building out a full, separate Application Console Script of your own. Which one you reach for depends on the shape of what you're building — and, as covered below, it's a decision you effectively make once, up front, when you initialize the application.

Kettle Commands

If your application only needs a handful of one-off CLI commands (e.g. "send a welcome email", "deactivate a stale account"), you can register them directly with kettle without having to build and wire up a separate console application. Each command is its own class with a single handle() action — a 1:1 relationship between class and command.

./kettle create:command [-a|--app] <command>

This requires that the application has already been initialized with CLI selected at the app:init application-type prompt (alone or combined with Web/API), since the command class is scaffolded into app/src/Console/Command/, which is only created for CLI-enabled installs.

The <command> value becomes both the CLI command signature and (in title case) the generated class name — e.g. ./kettle create:command send-email produces app/src/Console/Command/Kettle/SendEmail.php, namespaced MyApp\Console\Command\Kettle:

<?php

namespace MyApp\Console\Command\Kettle;

class SendEmail extends \Pop\Console\Command\AbstractCommand
{

    public ?string $name = 'send-email';
    public ?string $params = null;
    public ?string $help = 'This is the send-email command';

    public function handle()
    {
        /** Add command code here. */
    }

}

You can namespace the command signature itself by including a colon, e.g. ./kettle create:command email:send, which still produces a class named Send (based on the part after the last :), but registers the full email:send string as the command. If the command takes CLI arguments or options, add them to the $params property using the standard Pop\Router CLI syntax (<required>, [<optional>], [--flag], [-s|--long=]), for example:

public ?string $params = '<to> [--cc=]';

By default (without -a/--app), commands are scaffolded into the Kettle subfolder shown above. Every class found there is automatically discovered and merged into kettle's own route table on every run — there's nothing further to wire up, and the command shows up in ./kettle help alongside Kettle's own built-in commands. Once created, run it like any other kettle command:

./kettle send-email
./kettle email:send test@test.com --cc=someone@test.com

Even though it's invoked through kettle, the command doesn't actually run as Kettle. Once kettle matches the route to a non-native controller, it hands execution off to your own application's Application class (the one at app/src/Application.php) and runs the command through that instead — so inside handle(), $this->application is your app, with access to whatever services, config and database connection your app's own Application::load() sets up, not Kettle's internal ones. This "boot through Kettle, then switch to your app" behavior is what makes it possible to register commands against your own application's namespace without building and maintaining a second, separate console script.

Pass -a/--app to scaffold the command into app/src/Console/Command/ directly instead (no Kettle subfolder, and no Kettle namespace segment). Commands created this way are not merged into kettle's route table and can't be run as ./kettle <command> — they only exist for the stand-alone Application Console Script described below, which auto-discovers them the same way kettle auto-discovers its own Kettle-subfolder commands. That means a stand-alone script must already exist for an --app command to be reachable at all — see the note about that in the next section before using this flag.

Application Console Scripts

For a CLI application with a larger number of related commands, it's often cleaner to build a fully separate, self-contained console application than to keep piggybacking Kettle Commands onto kettle itself. Whether that stand-alone script exists at all is a one-time decision made during app:init when CLI is selected at the application-type prompt: you're prompted "Initialize a stand-alone CLI application?", and if you accept, its main script is scaffolded at script/<namespace> (see Initializing an Application above for the exact naming rule). This choice isn't easily reversible after the fact — decide up front whether you want a second, independent console application, or to keep everything running through kettle as Kettle Commands.

If you accepted the prompt, the stand-alone script's route table is built from three sources: its own baseline help/error handling (routed to a Console\Controller\ConsoleController class), any additional controllers you add with ./kettle create:ctrl --cli <ctrl> (grouping related commands together under a class — e.g. an EmailController housing several email-related actions, instead of a separate SendEmail/QueueEmail/RetryEmail command class for each one), and any -a/--app commands created as described above. See Accessing the Application below for how to run the script.

If you declined the prompt, none of that scaffolding exists — app/src/Console/Controller and script/ are never created — and ./kettle create:ctrl --cli <ctrl> will refuse with an explicit error rather than silently failing:

Error: This application was not initialized with a stand-alone console application.

In short: reach for a Kettle Command for a small number of standalone commands you want available immediately with no extra wiring, running through your app's own Application class via kettle; reach for an Application Console Script when you want a larger, fully independent CLI application with its own namespaced groups of commands, decided once up front at app:init time.

Top

Running the Web Server

pop-kettle also provides a simple way to run PHP's built-in web-server, by running the command:

./kettle web:serve [--host=] [--port=] [--folder=]

If omitted, --host defaults to localhost, --port defaults to 8000, and --folder defaults to public.

This is for development environments only and it is strongly advised against using the built-in web server in a production environment in any way.

Top

Getting Help and Version

To see the full list of available commands with their descriptions:

./kettle help

Pass --raw (or -r) to print the help screen without ANSI color codes, useful when piping the output somewhere that doesn't render them:

./kettle help --raw

Pass a <command> to narrow the list down to just the commands under that namespace, instead of the full list - ./kettle help db (the trailing : is optional) only shows db:* commands:

./kettle help db
./kettle db:install [<database>]             Install the database (Runs config, test & seed)
./kettle db:config [<database>]              Configure the database
./kettle db:test [<database>]                Test the database connection
./kettle db:create-seed <seed> [<database>]  Create database seed class
./kettle db:seed [<database>]                Seed the database with data
./kettle db:export [<database>]              Export the database to a file (MySQL only)
./kettle db:import <file> [<database>]       Import the database from a file (MySQL only)
./kettle db:reset [<database>]               Reset the database with original seed data
./kettle db:clear [<database>]               Clear the database of all data

--raw and <command> combine freely (./kettle help --raw db), and a <command> that doesn't match anything just prints an empty list rather than erroring.

To see the currently installed version of pop-kettle:

./kettle version

Top

Accessing the Application

Web/API

If you have wired up the beginnings of an application, you can then access the default routes in the following ways. Assuming you've started the web server as described above using ./kettle web:serve, you can access the web application by going to the address http://localhost:8000/ in any web browser and seeing the default index HTML page.

If you create both a web and API application, the HTML application will be accessible at http://localhost:8000/. If you want to access the API application, the default route for that is http://localhost:8000/api and you can access it like this to see the default JSON response:

$ curl -i -X GET http://localhost:8000/api

CLI: Through Kettle

If you have created commands to register with Kettle, then you can access them by calling them directly through ./kettle (they will display at the top of the ./kettle help command):

./kettle myapp:custom-command

CLI: Stand-alone Application

If you initialized a stand-alone CLI application, you can cd script, you'll see the default CLI application that was created. The default route available to the CLI application is the help route:

./app help

Top

Shell Completion

Shell completion for both bash and zsh shells is available. Simply copy the correct shell completion file to your user home directory and add them via the source command to your shell's read command file.

BASH

cp .kettle.bash ~/

Edit the ~/.bashrc file and add this:

source ~/.kettle.bash

ZSH

cp .kettle.zsh ~/

Edit the ~/.zshrc file and add this:

source ~/.kettle.zsh

Once you've set up your preferred shell, close all terminal windows and re-open a new one. Change directory to any project that has the kettle script in it and the auto-completion should now be available.

Top

Using on Windows

Most UNIX-based environments should recognize the main kettle application script as a PHP script and run it accordingly, without having to explicitly call the php command and pass the script and its parameters into it. However, if you're on an environment like Windows, depending on your exact environment set up, you will most likely have to prepend all of the command calls with the php command, for example:

C:\popphp\pop-kettle>php kettle help