Introducing: DecodeLabs Effigy

Introducing: DecodeLabs Effigy

Your new go-to CLI tool

·

7 min read

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 in effigy.json as it is environment-specific. It can be set via effigy 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 as analyze and format to distinguish which folders they should process.

  • params - A list of template keys used in resolving your entry path

  • exports - 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 run PHPStan analysis on your project

  • check-executable-permissions - Ensure only files defined in your Composer bin list are executable

  • check-git-exports - Ensure your .gitattributes file exists and makes sense

  • check-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 keep

  • eclint - Setup and run the eclint linter tool to check your code conforms to your .editorconfig file

  • format - Setup and run Easy Coding Standards formatter

  • generate-changelog - Generate a changelog file from a template

  • generate-composer-config - Generate a composer.json file from a template

  • generate-ecs-config - Generate an Easy Coding Standard config from a template

  • generate-editor-config - Generate a .editorconfig file from a template

  • generate-git-attributes - Generate a .gitattributes file from a template

  • generate-github-workflow - Generate a GitHub workflow file to check, test and analyze your project automatically via GitHub actions

  • generate-gitignore - Generate a .gitignore file from a template

  • generate-phpstan-config - Generate a PHPStan config from a template

  • generate-readme - Generate a README.md file from a template

  • init-package - Call init-repo and all the above file generate tasks

  • init-repo - Prepare your project's git repository and initialise git-flow if it's available

  • install-local / remove-local - Install / uninstall a local effigy bin file in your project so Effigy can be called without being available globally

  • lint - Setup and run PHP parallel-lint on your project code

  • mount / unmount - Mount / unmount a dependency package with a symbolic link for direct development, see below

  • prep - Prepare your package for release - update dependencies, analyze, format, test and check everything, thoroughly

  • self-update - Ensure Effigy is up to date

  • set-php - Defined a custom version of PHP to use for your project

  • upgrade - Update Composer packages and tidy up afterwards

  • veneer-stub - Create stubs (for editor autocompletion) for libraries with Veneer frontages

  • version - 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.