Building command line interactions for your application can get complicated quickly - writing standalone tasks is relatively straightforward but you soon realise you need a standardised structure and call signature, handling output gets incredibly fudgy, supporting different environments is a pain in the butt and chaining tasks is a total minefield.
DecodeLabs Effigy and the libraries it was built upon were designed to ease all of these pains in the simplest form we could come up with.
Written as a standalone application in its own right, it's a departure from the usual DecodeLabs library format, instead installed globally via composer so that it can be run directly from the CLI wherever you happen to be.
composer global require decodelabs/effigy
You will also need to have added your global Composer installation bin directory to your $PATH
in your ~/.bash_profile
or ~/.bashrc
file:
export PATH=~/.config/composer/vendor/bin:$PATH
Note, earlier versions of Composer may store global config in ~/.composer/
- adapt your $PATH as necessary. You can find Composer's home path with composer global config home
Once installed, you can call Effigy from any location - it will look up the directory tree for the nearest Composer project JSON file.
Talking to Effigy
Effigy's job is to receive an instruction, work out where and how that instruction should be executed, set up the required environment then hand off to that environment to complete the command.
There are a few ways a command can be interpreted:
A built-in Effigy task
An installed Composer bin
A script defined in
composer.json
Finally, a task handled by your application
For example, effigy format
will run the built-in Effigy task to apply Easy Coding Standards formatter across your codebase. effigy composer update
will run the Composer update command on your project. If you have PHPStan
installed in your project, effigy phpstan
would run the phpstan
bin directly, though effigy analyze
offers a more comprehensive analysis support task.
The most interesting bit comes when the command you have asked to run is not a built-in task or script; it's at this point where Effigy will attempt to run your application as a CLI tool and ask it to handle the command itself.
You must tell Effigy how your application expects to do this. This is done by adding some configuration to your composer.json
file.
Say for example, you can currently run commands in your project through webroot/index.php
as your primary entry point:
php webroot/index.php run-task
Define your entry point in your composer.json file:
{
"extra": {
"effigy": {
"entry": "webroot/index.php"
}
}
}
Effigy will then load webroot/index.php
as the entry point and pass the command to whatever structure your application has in place to handle it. See DecodeLabs Clip for the underlying structure that Effigy uses to run CLI tasks in a reproducible structured format - it can be used as a plug-and-play system for CLI tasks in your application.
Should you need per-environment entry files, specify template keys in your composer config entry path:
{
"extra": {
"effigy": {
"entry": "entry/{{env}}.php"
}
}
}
Then on first run, Effigy will ask for the "env" parameter and save it in a local config file (which gets added to your .gitignore).
Configuration
Effigy can be configured both through the extra.effigy
structure in composer.json
or your environment-specific effigy.json
config file (make sure to add it to .gitignore if you create it yourself!)
In addition to the entry path explained above, the following configuration directives can be set up to help you control your application:
php
- Path to your PHP binary. This should only be configured ineffigy.json
as it is environment-specific. It can be set viaeffigy set-php
codeDirs
- A list of paths in your project folder that should be considered as having code within them. This is used by built-in tasks such asanalyze
andformat
to distinguish which folders they should process.params
- A list of template keys used in resolving your entry pathexports
- A list of paths in your project folder that should be exported in your project's distribution. This is used to create and check your.gitattributes
file.
So, an example composer.json
may contain:
{
"extra": {
"effigy": {
"entry": "entry/{{env}}.php",
"codeDirs": [
"src",
"tests",
"examples"
],
"exports": [
"src",
"README.md"
]
}
}
}
And your example effigy.json
may contain:
{
"php": "/usr/bin/php8.1",
"params": {
"env": "my-environment-name"
}
}
Built-in tasks
Effigy comes with a set of pre-defined tasks that cover a bunch of regularly needed functionality:
analyze
- Setup and runPHPStan
analysis on your projectcheck-executable-permissions
- Ensure only files defined in your Composer bin list are executablecheck-git-exports
- Ensure your.gitattributes
file exists and makes sensecheck-non-ascii
- Look for problematic non-ascii characters in your PHP code. Comment line with// @ignore-non-ascii
for non-ascii characters you want to keepeclint
- Setup and run theeclint
linter tool to check your code conforms to your.editorconfig
fileformat
- Setup and run Easy Coding Standards formattergenerate-changelog
- Generate a changelog file from a templategenerate-composer-config
- Generate acomposer.json
file from a templategenerate-ecs-config
- Generate an Easy Coding Standard config from a templategenerate-editor-config
- Generate a.editorconfig
file from a templategenerate-git-attributes
- Generate a.gitattributes
file from a templategenerate-github-workflow
- Generate a GitHub workflow file to check, test and analyze your project automatically via GitHub actionsgenerate-gitignore
- Generate a.gitignore
file from a templategenerate-phpstan-config
- Generate aPHPStan
config from a templategenerate-readme
- Generate a README.md file from a templateinit-package
- Callinit-repo
and all the above file generate tasksinit-repo
- Prepare your project's git repository and initialisegit-flow
if it's availableinstall-local
/remove-local
- Install / uninstall a localeffigy
bin file in your project so Effigy can be called without being available globallylint
- Setup and run PHPparallel-lint
on your project codemount
/unmount
- Mount / unmount a dependency package with a symbolic link for direct development, see belowprep
- Prepare your package for release - update dependencies, analyze, format, test and check everything, thoroughlyself-update
- Ensure Effigy is up to dateset-php
- Defined a custom version of PHP to use for your projectupgrade
- Update Composer packages and tidy up afterwardsveneer-stub
- Create stubs (for editor autocompletion) for libraries with Veneer frontagesversion
- Get the current version of Effigy
Mounting Dependencies
Sometimes it is necessary to work on a secondary package in context of your main project - but this usually requires having to set up a specialised testing environment and is a total pain to achieve.
Effigy offers the mount
and unmount
tasks which will add special configuration to your composer.json
which enables temporary symbolic links to a local copy of that package to allow development in-situ.
Once your development is complete, you must unmount
before committing anything to your main project's repository as the configuration in your composer.json
is specific to your environment.
For example, say you had this in your composer.json
:
{
"require": {
"mycompany/shared-library": "^2.5"
}
}
If you needed to add a feature to mycompany/shared-library
you can:
effigy mount shared-library
Effigy will then ask for the path to your local copy of this package and update your composer.json
to include a local repository definition. You may then make your changes to your local copy of shared-library
and test it in context of your project.
effigy unmount
This will return your configuration back to normal, reinstalling the latest compatible version of your dependency. You may wish to finalise, commit and release the update to the dependency package before unmounting (no config is changed in shared-library
in the mounting process) so that the updated version is pulled in during the unmount command.
Roadmap
At the time of writing, Effigy is at v0.4.0
, and still in pre-release state. It is however stable and reasonably feature-complete and in use in production environments internally.
A final v1
release is pending, awaiting final documentation and full test coverage however due to the standalone nature of the package, there's little reason not to give Effigy a try in your current project.