Development log - 2022/48

Development log - 2022/48



4 min read

A huge set of updates for this log - driven primarily with the goal of delivering a seamless Vite server integration - begins with improvements to last dev-log's main focus, Effigy.

This handy tool has seen a succession of important updates to allow for seamless console interactions. A suite of project initialisation tasks, integrity checks and utility commands expands its usefulness while a restructuring of its internals ensures future readiness with a simple task development experience and dependable understanding of the environment in which it runs.

To achieve this, a new library, Integra was born to handle the intricacies of finding, inspecting and manipulating Composer projects. Effigy is also now based on Clip which acts as a base CLI kernel and hub on top of Genesis.


Sat underneath Effigy and Integra, the Systemic library (providing access to OS information and process handling) has received an almost from-scratch rewrite to dramatically improve the reliability, flexibility and simplicity of talking to outside processes and commands.

Build command structures, then execute them in various forms to achieve the desired result:

use DecodeLabs\Systemic;

$command = Systemic::command(['myscript', '--arg1', '--arg2=great'])
    ->addSignal('SIGINT'); // Pass this signal on if caught

// Capture the result in a buffer
$result = $command->capture();
echo $result->getOutput();

// Launch as a background process
$process = $command->launch();
echo $process->getProcessId();

// Connect the process to the active terminal 
// (or pipe it if there isn't one)
if(!$command->run()) {
    // Failed

// Start a programmatic interactive session
$result = $command->start(function($controller) {
    // Provide input
    yield 'Hello world';

    // Close input stream

    // Capture output from process in buffer
    yield from $controller->capture();

echo $result->getOutput();

Or, use the built-in shortcut methods, for example:

echo Systemic::capture(
        ['myscript', '--arg1', '--arg2=great'],


Moved from Systemic, the Locale and Timezone controller interfaces have become the first addition to a currently otherwise placeholder package, Cosmos, that will eventually become the hub for most internationalisation and localisation functionality.

For now, you can get and set the current local and timezone like so:

use DecodeLabs\Cosmos;

echo (string)Cosmos::$locale; // en_GB

echo (string)Cosmos::$timezone; // Europe/London

Cosmos also houses the shared number and time formatter interfaces found as plugins in Dictum and Tagged.


Another new package, Overpass provides similar functionality in nature to Integra, but with node.js packages as the focus. Package JSON inspection, dependency management and defined script execution all come together under one simple interface, alongside a handy bridge implementation that allows for managed invocation of your own oriented based javascript.

For example:

// test.js
module.exports = function (data) {
    // Just return what is being sent for now
    return data + ' from node.js';
use DecodeLabs\Overpass;

// Hello world from node.js
echo Overpass::bridge('test.js', 'Hello world');


The ultimate goal of this line of development, the Zest package provides console commands, utility functions and handy generators for getting set up with the Vite development server extremely quickly. Assuming you have Effigy installed:

# Install zest
composer require decodelabs/zest

# Initialize config and server with vue and legacy plugins
effigy zest init vue legacy

# Run the dev server - build will be run when stopped
effigy zest dev

When initialising Zest, a custom configuration PHP file is created which allows you to customise how Vite works as a whole (including what packages are needed, how it should be configured, etc). Changes can be made by customising this PHP file and re-running the init task - the whole node installation will be reconfigured with the correct dependencies and settings.

Vite will run on a dedicated port that is picked at random during init.

The only manual integration needed is in your view logic, to import the correct set of scripts and styles:

use DecodeLabs\Zest\Manifest;

// Adjust for your view implementation
$view = new View('Index.html');
$manifest = Manifest::load('path/to/vite/build/manifest.json');

foreach ($manifest->getCssData() as $file => $attributes) {
    // Add CSS links to head
    $view->linkCss($file, $attributes);

foreach ($manifest->getHeadJsData() as $file => $attributes) {
    // Add JS scripts to head
    $view->linkJs($file, $attributes);

foreach ($manifest->getBodyJsData() as $file => $attributes) {
    // Add JS scripts to body foot
    $view->embedBodyJs($file, $attributes);

Other additions / improvements


  • Added default fallback to resolve handler

  • Added normalizer interface structure

  • Renamed Local resolver to Generic

  • Added extension namespaces to Generic resolver


  • Added [] list extension type : ->as('string[]'): array


  • Resolve callable as default value in widgets

  • Added more argument inspection helpers


  • Added Fluidity Cast to Hub interface


  • Added replacePlugin() helper


  • Added binding resolution lookup when building instance from class-string


  • Added conditional nullable return types for tighter PHPStan analysis


  • Imported Signal class from Systemic