Finally a sane way to register available commands and arguments and match your command line in PHP

Overview

clue/commander

CI status installs on Packagist

Finally a sane way to register available commands and arguments and match your command line in PHP.

You want to build a command line interface (CLI) tool in PHP which accepts additional arguments and you now want to route these to individual functions? Then this library is for you!

This is also useful for interactive CLI tools or anywhere where you can break up a command line string into an array of command line arguments and you now want to execute individual functions depending on the arguments given.

Table of contents

Support us

We invest a lot of time developing, maintaining and updating our awesome open-source projects. You can help us sustain this high-quality of our work by becoming a sponsor on GitHub. Sponsors get numerous benefits in return, see our sponsoring page for details.

Let's take these projects to the next level together! 🚀

Quickstart example

The following example code demonstrates how this library can be used to build a very simple command line interface (CLI) tool that accepts command line arguments passed to this program:

$router = new Clue\Commander\Router();
$router->add('exit [<code:uint>]', function (array $args) {
    exit(isset($args['code']) ? $args['code'] : 0);
});
$router->add('sleep <seconds:uint>', function (array $args) {
    sleep($args['seconds']);
});
$router->add('echo <words>...', function (array $args) {
    echo join(' ', $args['words']) . PHP_EOL;
});
$router->add('[--help | -h]', function () use ($router) {
    echo 'Usage:' . PHP_EOL;
    foreach ($router->getRoutes() as $route) {
        echo '  ' .$route . PHP_EOL;
    }
});

$router->execArgv();

See also the examples.

Usage

Router

The Router is the main class in this package.

It is responsible for registering new Routes, matching the given args against these routes and then executing the registered route callback.

$router = new Router();

Advanced usage: The Router accepts an optional Tokenizer instance as the first parameter to the constructor.

add()

The add(string $route, callable $handler): Route method can be used to register a new Route with this Router.

It accepts a route expression to match and a route callback that will be executed when this route expression matches.

This is very similar to how common PHP (micro-)frameworks offer "HTTP routers" to route incoming HTTP requests to the corresponding "controller functions":

$route = $router->add($path, $fn);

The route expression uses a custom domain-specific language (DSL) which aims to be so simple that both consumers of this library (i.e. developers) and users of your resulting tools should be able to understand them.

Note that this is a left-associative grammar (LAG) and all tokens are greedy. This means that the tokens will be processed from left to right and each token will try to match as many of the input arguments as possible. This implies that certain route expressions make little sense, such as having an optional argument after an argument with ellipses. For more details, see below.

You can use an empty string like this to match when no arguments have been given:

$router->add('', function() {
    echo 'No arguments given. Need help?' . PHP_EOL;
});
// matches: (empty string)
// does not match: hello (too many arguments)

You can use any number of static keywords like this:

$router->add('user list', function () {
    echo 'Here are all our users…' . PHP_EOL;
});
// matches: user list
// does not match: user (missing required keyword)
// does not match: user list hello (too many arguments)

You can use alternative blocks to support any of the static keywords like this:

$router->add('user (list | listing | ls)', function () {
    echo 'Here are all our users…' . PHP_EOL;
});
// matches: user list
// matches: user listing
// matches: user ls
// does not match: user (missing required keyword)
// does not match: user list hello (too many arguments)

Note that alternative blocks can be added to pretty much any token in your route expression. Note that alternative blocks do not require parentheses and the alternative mark (|) always works at the current block level, which may not always be obvious. Unless you add some parentheses, a b | c d will be be interpreted as (a b) | (c d) by default. Parentheses can be used to interpret this as a (b | c) d instead. In particular, you can also combine alternative blocks with optional blocks (see below) in order to optionally accept only one of the alternatives, but not multiple.

You can use any number of placeholders to mark required arguments like this:

$router->add('user add <name>', function (array $args) {
    assert(is_string($args['name']));
    var_dump($args['name']);
});
// matches: user add clue
// does not match: user add (missing required argument)
// does not match: user add hello world (too many arguments)
// does not match: user add --test (argument looks like an option)

// matches: user add -- clue     (value: clue)
// matches: user add -- --test   (value: --test)
// matches: user add -- -nobody- (value: -nobody-)
// matches: user add -- --       (value: --)

Note that arguments that start with a dash (-) are not simply accepted in the user input, because they may be confused with (optional) options (see below). If users wish to process arguments that start with a dash (-), they either have to use filters (see below) or may use a double dash separator (--), as everything after this separator will be processed as-is. See also the last examples above that demonstrate this behavior.

You can use one the predefined filters to limit what values are accepted like this:

$router->add('user ban <id:int> <force:bool>', function (array $args) {
    assert(is_int($args['id']));
    assert(is_bool($args['force']));
});
// matches: user ban 10 true
// matches: user ban 10 0
// matches: user ban -10 yes
// matches: user ban -- -10 no
// does not match: user ban 10 (missing required argument)
// does not match: user ban hello true (invalid value does not validate)

Note that the filters also return the value casted to the correct data type. Also note how using the double dash separator (--) is optional when matching a filtered value. The following predefined filters are currently available:

  • int accepts any positive or negative integer value, such as 10 or -4
  • uint accepts any positive (unsigned) integer value, such 10 or 0
  • float accepts any positive or negative float value, such as 1.5 or -2.3
  • ufloat accepts any positive (unsigned) float value, such as 1.5 or 0
  • bool accepts any boolean value, such as yes/true/1 or no/false/0

If you want to add a custom filter function, see also Tokenizer for advanced usage below.

You can mark arguments as optional by enclosing them in square brackets like this:

$router->add('user search [<query>]', function (array $args) {
    assert(!isset($args['query']) || is_string($args['query']));
    var_dump(isset($args['query']);
});
// matches: user search
// matches: user search clue
// does not match: user search hello world (too many arguments)

Note that square brackets can be added to pretty much any token in your route expression, however they are most commonly used for arguments as above or for optional options as below. Optional tokens can appear anywhere in the route expression, but keep in mind that the tokens will be matched from left to right, so if the optional token matches, then the remainder will be processed by the following tokens. As a rule of thumb, make sure optional tokens are near the end of your route expressions and you won't notice this subtle effect. Optional blocks accept alternative groups, so that [a | b] is actually equivalent to the longer form [(a | b)]. In particular, this is often used for alternative options as below.

You can accept any number of arguments by appending ellipses like this:

$router->add('user delete <names>...', function (array $args) {
    assert(is_array($args);
    assert(count($args) > 0);
    var_dump($args['names']);
});
// matches: user delete clue
// matches: user delete hello world
// does not match: user delete (missing required argument)

Note that trailing ellipses can be added to any argument, word or option token in your route expression. They are most commonly used for arguments as above. The above requires at least one argument, see the following if you want this to be completely optional. Technically, the ellipse tokens can appear anywhere in the route expression, but keep in mind that the tokens will be matched from the left to the right, so if the ellipse matches, it will consume all input arguments and not leave anything for following tokens. As a rule of thumb, make sure ellipse tokens are near the end of your route expression and you won't notice this subtle effect.

You can accept any number of optional arguments by appending ellipses within square brackets like this:

$router->add('user dump [<names>...]', function (array $args) {
    if (isset($args['names'])) {
        assert(is_array($args);
        assert(count($args) > 0);
        var_dump($args['names']);
    } else {
        var_dump('no names');
    }
});
// matches: user dump
// matches: user dump clue
// matches: user dump hello world

The above does not require any arguments, it works with zero or more arguments.

You can add any number of optional short or long options like this:

$router->add('user list [--json] [-f]', function (array $args) {
    assert(!isset($args['json']) || $args['json'] === false);
    assert(!isset($args['f']) || $args['f'] === false);
});
// matches: user list
// matches: user list --json
// matches: user list -f
// matches: user list -f --json
// matches: user -f list
// matches: --json user list

As seen in the example, options in the $args array can either be unset when they have not been passed in the user input or set to false when they have been passed (which is in line with how other parsers such as getopt() work). Note that options are accepted anywhere in the user input argument, regardless of where they have been defined. Note that the square brackets are in the route expression are required to mark this optional as optional, you can also omit these square brackets if you really want a required option.

You can combine short and long options in an alternative block like this:

$router->add('user setup [--help | -h]', function (array $args) {
    assert(!isset($args['help']) || $args['help'] === false);
    assert(!isset($args['h']) || $args['h'] === false);
    assert(!isset($args['help'], $args['h']); 
});
// matches: user setup
// matches: user setup --help
// matches: user setup -h
// does not match: user setup --help -h (only accept eithers, not both)

As seen in the example, this optionally accepts either the short or the long option anywhere in the user input, but never both at the same time.

You can optionally accept or require values for short and long options like this:

$router->add('[--sort[=<param>]] [-i=<start:int>] user list', function (array $args) {
    assert(!isset($args['sort']) || $args['sort'] === false || is_string($args['sort']));
    assert(!isset($args['i']) || is_int($args['i']));
});
// matches: user list
// matches: user list --sort
// matches: user list --sort=size
// matches: user list --sort size
// matches: user list -i=10
// matches: user list -i 10
// matches: user list -i10
// matches: user list -i=-10
// matches: user list -i -10
// matches: user list -i-10
// matches: user -i=10 list
// matches: --sort -- user list
// matches: --sort size user list
// matches: user list --sort -i=10
// does not match: user list -i (missing option value)
// does not match: user list -i --sort (missing option value)
// does not match: user list -i=a (invalid value does not validate)
// does not match: --sort user list (user will be interpreted as option value)
// does not match: user list --sort -2 (value looks like an option)

As seen in the example, option values in the $args array will be given as strings or their filtered and casted value if passed in the user input. Both short and long options can accept values with the recommended equation symbol syntax (-i=10 and --sort=size respectively) in the user input. Both short and long options can also accept values with the common space-separated syntax (-i 10 and --sort size respectively) in the user input. Short options can also accept values with the common concatenated syntax with no separator inbetween (-i10) in the user input. Note that it is highly recommended to always make sure any options that accept values are near the left side of your route expression. This is needed in order to make sure space-separated values are consumed as option values instead of being misinterpreted as keywords or arguments.

You can limit the values for short and long options to a given preset like this:

$router->add('[--ask=(yes | no)] [-l[=0]] user purge', function (array $args) {
    assert(!isset($args['ask']) || $args['sort'] === 'yes' || $args['sort'] === 'no');
    assert(!isset($args['l']) || $args['l'] === '0');
});
// matches: user purge
// matches: user purge --ask=yes
// matches: user purge --ask=no
// matches: user purge -l
// matches: user purge -l=0
// matches: user purge -l 0
// matches: user purge -l0
// matches: user purge -l --ask=no
// does not match: user purge --ask (missing option value)
// does not match: user purge --ask=maybe (invalid option value)
// does not match: user purge -l4 (invalid option value)

As seen in the example, option values can be restricted to a given preset of values by using any of the above tokens. Technically, it's valid to use any of the above tokens to restrict the option values. In practice, this is mostly used for static keyword tokens or alternative groups thereof. It's recommended to always use parentheses for optional groups, however they're not strictly required within options with optional values. This also helps making it more obvious [--ask=(yes | no)] would accept either option value, while the (less useful) expression [--ask=yes | no] would accept either the option --ask=yes or the static keyword no.

remove()

The remove(Route $route): void method can be used to remove the given Route object from the registered routes.

$route = $router->add('hello <name>', $fn);
$router->remove($route);

It will throw an UnderflowException if the given route does not exist.

getRoutes()

The getRoutes(): Route[] method can be used to return an array of all registered Route objects.

echo 'Usage help:' . PHP_EOL;
foreach ($router->getRoutes() as $route) {
    echo $route . PHP_EOL;
}

This array will be empty if you have not added any routes yet.

execArgv()

The execArgv(array $argv = null): void method can be used to execute by matching the argv against all registered routes and then exit.

You can explicitly pass in your $argv or it will automatically use the values from the $_SERVER superglobal. The argv is an array that will always start with the calling program as the first element. We simply ignore this first element and then process the remaining elements according to the registered routes.

This is a convenience method that will match and execute a route and then exit the program without returning.

If no route could be found or if the route callback throws an Exception, it will print out an error message to STDERR and set an appropriate non-zero exit code.

Note that this is for convenience only and only useful for the most simple of all programs. If you need more control, then consider using the underlying handleArgv() method and handle any error situations yourself.

handleArgv()

The handleArgv(array $argv = null): mixed method can be used to execute by matching the argv against all registered routes and then return.

You can explicitly pass in your $argv or it will automatically use the values from the $_SERVER superglobal. The argv is an array that will always start with the calling program as the first element. We simply ignore this first element and then process the remaining elements according to the registered routes.

Unlike execArgv() this method will try to execute the route callback and then return whatever the route callback returned.

$router->add('hello <name>', function (array $args) {
    return strlen($args[$name]);
});

$length = $router->handleArgv(array('program', 'hello', 'test'));

assert($length === 4);

If no route could be found, it will throw a NoRouteFoundException.

// throws NoRouteFoundException
$router->handleArgv(array('program', 'invalid'));

If the route callback throws an Exception, it will pass through this Exception.

$router->add('hello <name>', function (array $args) {
    if ($args['name'] === 'admin') {
        throw new InvalidArgumentException();
    }
    
    return strlen($args['name']);
});

// throws InvalidArgumentException
$router->handleArgv(array('program', 'hello', 'admin'));

handleArgs()

The handleArgs(array $args): mixed method can be used to execute by matching the given args against all registered routes and then return.

Unlike handleArgv() this method will use the complete $args array to match the registered routes (i.e. it will not ignore the first element). This is particularly useful if you build this array yourself or if you use an interactive command line interface (CLI) and ask your user to supply the arguments.

$router->add('hello <name>', function (array $args) {
    return strlen($args[$name]);
});

$length = $router->handleArgs(array('hello', 'test'));

assert($length === 4);

The arguments have to be given as an array of individual elements. If you only have a command line string that you want to split into an array of individual command line arguments, consider using clue/arguments.

$line = fgets(STDIN, 2048);
assert($line === 'hello "Christian Lück"');

$args = Clue\Arguments\split($line);
assert($args === array('hello', 'Christian Lück'));

$router->handleArgs($args);

If no route could be found, it will throw a NoRouteFoundException.

// throws NoRouteFoundException
$router->handleArgs(array('invalid'));

If the route callback throws an Exception, it will pass through this Exception.

$router->add('hello <name>', function (array $args) {
    if ($args['name'] === 'admin') {
        throw new InvalidArgumentException();
    }
    
    return strlen($args['name']);
});

// throws InvalidArgumentException
$router->handleArgs(array('hello', 'admin'));

Route

The Route represents a single registered route within the Router.

It holds the required route tokens to match and the route callback to execute if this route matches.

See Router.

NoRouteFoundException

The NoRouteFoundException will be raised by handleArgv() or handleArgs() if no matching route could be found. It extends PHP's built-in RuntimeException.

Tokenizer

The Tokenizer class is responsible for parsing a route expression into a valid token instance. This class is mostly used internally and not something you have to worry about in most cases.

If you need custom logic for your route expression, you may explicitly pass an instance of your Tokenizer to the constructor of the Router:

$tokenizer = new Tokenizer();

$router = new Router($tokenizer);

addFilter()

The addFilter(string $name, callable $filter): void method can be used to add a custom filter function.

The filter name can then be used in argument or option expressions such as add <name:lower> or --search=<address:ip>.

The filter function will be invoked with the filter value and MUST return a boolean success value if this filter accepts the given value. The filter value will be passed by reference, so it can be updated if the filtering was successful.

$tokenizer = new Tokenizer();
$tokenizer->addFilter('ip', function ($value) {
    return filter_var($ip, FILTER_VALIDATE_IP);
});
$tokenizer->addFilter('lower', function (&$value) {
    $value = strtolower($value);
    return true;
});

$router = new Router($tokenizer);
$router->add('add <name:lower>', function ($args) { });
$router->add('--search=<address:ip>', function ($args) { });

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version:

$ composer require clue/commander:^1.4

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's highly recommended to use PHP 7+ for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

$ composer install

To run the test suite, go to the project root and run:

$ php vendor/bin/phpunit

License

This project is released under the permissive MIT license.

Did you know that I offer custom development services and issuing invoices for sponsorships of releases and for contributions? Contact me (@clue) for details.

More

  • If you want to build an interactive CLI tool, you may want to look into using clue/reactphp-stdio in order to react to commands from STDIN.
  • If you build an interactive CLI tool that reads a command line from STDIN, you may want to use clue/arguments in order to split this string up into its individual arguments.
Comments
  • Issue with optional arguments

    Issue with optional arguments

    Thanks for a great package which I just discovered. I'm having a trivial issue with a simple route. Maybe you call help?

        $router->add('run [--verbose]', function (array $args) {
           ...
        });
    

    When starting with either php index.php run --verbose or php index.php run, I always have $args[verbose] set to false. What am I doing wrong?

    question 
    opened by 8ctopus 4
  • Fix HHVM build for now again and ignore future HHVM build errors

    Fix HHVM build for now again and ignore future HHVM build errors

    The HHVM build reports an error once again. Let's fix this once again by updating the base distro from precise to trusty and ignore any future HHVM build errors.

    See travis-ci/travis-ci#7712 (comment) Originally from clue/php-http-proxy-react#12

    maintenance 
    opened by clue 2
  • Support filtering placeholder and option values

    Support filtering placeholder and option values

    We should add some support for filtering placeholder and option values like this:

    base add <offset:int> [--wait[=<timeout:float>]]
    

    We should probably supply some common default filters and also provide an interface to define custom filter functions.

    help wanted new feature 
    opened by clue 2
  • Support preset option values

    Support preset option values

    We should add support for option values that accept only a limited preset like this:

    add <name> [--sign=(true|false|if-asked)]
    

    Should parenthesis be optional? Probably easier to distinguish this from #5.

    Depends on #3

    help wanted new feature 
    opened by clue 1
  • Support alternative options

    Support alternative options

    We should add support for alternative options like this:

    add [--verbose | -v]
    

    This should match either add --verbose or add -v, but not both.

    This is also useful for situations like this:

    install [--dev | --no-dev]
    

    Depends on #2

    help wanted new feature 
    opened by clue 1
  • Run tests on  PHP 8, PHP 7.4 and PHPUnit 9 and update PHPUnit configuration schema for PHPUnit 9.3

    Run tests on PHP 8, PHP 7.4 and PHPUnit 9 and update PHPUnit configuration schema for PHPUnit 9.3

    PHPUnit 9.3 released a new schema for the phpunit.xml configuration file. I had to migrate the file to the new format in order to avoid the warning. PHPUnit Versions older than 9.3 have to use the phpunit.xml.legacy configuration file, because the new format is unknown for them. For further details concerning this pull request look into https://github.com/graphp/graphviz/pull/46.

    It's also possible to run this code with PHP 8 🎉

    $ docker run -it --rm -v `pwd`:/data --workdir=/data php:8.0.0beta2-cli php vendor/bin/phpunit
    PHPUnit 9.5.0 by Sebastian Bergmann and contributors.
    
    ...............................................................  63 / 193 ( 32%)
    ............................................................... 126 / 193 ( 65%)
    ............................................................... 189 / 193 ( 97%)
    ....                                                            193 / 193 (100%)
    
    Time: 00:00.028, Memory: 6.00 MB
    
    OK (193 tests, 255 assertions)
    
    maintenance new feature 
    opened by SimonFrings 0
  • Add support for custom filter callbacks

    Add support for custom filter callbacks

    Add Tokenizer::addFilter() method to support adding custom filter callbacks:

    $tokenizer = new Tokenizer();
    $tokenizer->addFilter('ip', function ($value) {
        return filter_var($ip, FILTER_VALIDATE_IP);
    });
    $tokenizer->addFilter('lower', function (&$value) {
        $value = strtolower($value);
        return true;
    });
    
    $router = new Router($tokenizer);
    $router->add('add <name:lower>', function ($args) { });
    $router->add('--search=<address:ip>', function ($args) { });
    
    new feature 
    opened by clue 0
  • Lock Travis distro so new future defaults will not break the build

    Lock Travis distro so new future defaults will not break the build

    Travis is in the process of upgrading the base distro (https://blog.travis-ci.com/2017-07-11-trusty-as-default-linux-is-coming) and despite all PRs currently being "green", will soon start to mark the current master as broken.

    While updating the default distro, they also removed support for some older versions of PHP (travis-ci/travis-ci#7163). These versions are still supported by this project, so we now have to explicitly define the base distro to test against.

    Builds on top of #22 Originally from clue/php-connection-manager-extra#24

    maintenance 
    opened by clue 0
  • Fix: Use casted filter value for options with boolean values

    Fix: Use casted filter value for options with boolean values

    Matching the expression hello --force=<n:bool> with the user input hello --force=false will now return the correct (bool)false value intead of (string)"false".

    bug 
    opened by clue 0
  • Support preset option values

    Support preset option values

    Option values now accept any kind of token, which means they no longer simply accept any value. It is now possible to pass an alternative group to limit this to a preset of valid option values.

    The tokenizer/parser has been updated to properly recurse into option values and now accepts whitespace and optional parentheses as expected.

    The tokenizer/parser has also been updated to use an argument token if you still want to accept any value. This means we now share quite a bit of code between these, which is also a necessary preparation for #13.

    Closes #6.

    new feature 
    opened by clue 0
  • Refactor to use OptionToken class for both long and short options

    Refactor to use OptionToken class for both long and short options

    Long and short options differ only slightly, so it makes little sense keeping them separate. This also eases adding shared behavior for upcoming option values (#3)

    Also, simplify test suite by using data providers for common tests.

    Builds on top of #8

    maintenance 
    opened by clue 0
  • Add description text to Route

    Add description text to Route

    It would be nice to have the ability to add some optional description text to a route.

    Something like:

    $router->add('test', function () {
        //do something
    })->addDescription('This is a description');
    

    OR

    $router->add('test', function () {
        //do something
    }, 'This is a description');
    
    
    help wanted easy pick new feature 
    opened by davidwdan 2
  • Support combined short options from user input

    Support combined short options from user input

    We should support matching combined short options:

    add [-i] [-a] [-d=<X>] [-w=<X>]
    

    All add -i -a, add -ia and add -ai should result in the same match.

    Also, short options with values (depends on #3) may be even trickier.

    Depends on #2.

    help wanted new feature 
    opened by clue 1
Releases(v1.4.0)
  • v1.4.0(Dec 7, 2020)

    • Improve test suite and add .gitattributes to exclude dev files from exports. Add PHP 8 support, update to PHPUnit 9 and simplify test setup. (#26 by @andreybolonin, #27 and #28 by @SimonFrings and #29 by @clue)
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Aug 11, 2017)

    • Feature: Add support for custom filter callbacks (#25 by @clue)

      $tokenizer = new Tokenizer();
      $tokenizer->addFilter('ip', function ($value) {
          return filter_var($ip, FILTER_VALIDATE_IP);
      });
      $tokenizer->addFilter('lower', function (&$value) {
          $value = strtolower($value);
          return true;
      });
      
      $router = new Router($tokenizer);
      $router->add('add <name:lower>', function ($args) { });
      $router->add('--search=<address:ip>', function ($args) { });
      
    • Improve test suite by locking Travis distro so new future defaults will not break the build (#24 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v1.2.2(Jul 3, 2017)

    • Fix: Assume argv to be empty if not present (non-CLI SAPI mode) (#23 by @clue)

    • Improve test suite by adding PHPUnit to require-dev and ignoring HHVM build errors for now. (#21 and #22 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v1.2.1(Nov 14, 2016)

  • v1.2.0(Nov 7, 2016)

    • Feature: Add support for predefined filters to limit accepted values and avoid requiring double dash separator (#19 by @clue)
    • Feature: Support preset option values, option values now accept any tokens (#17 by @clue)
    • Feature: Unify handling ellipses after any token and whitespace around option values (#17 by @clue)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Nov 6, 2016)

    • Feature: Support alternative groups and optional parentheses (#15 by @clue)
    • Fix: Fix multiple arguments, only skip whitespace inbetweeen once (#16 by @clue)
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Nov 5, 2016)

    • First stable release, now following SemVer
    • Improve documentation and usage examples

    Contains no other changes, so it's actually fully compatible with the v0.2.0 release.

    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Nov 5, 2016)

    • Feature / BC break: Add support for long and short options with or without option values (#8, #11,# 12 by @clue)
    • Feature: More flexible recursive parser with support for optional keywords and required attributes (#9, #10 by @clue)
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Oct 14, 2016)

Owner
Christian Lück
Maintainer of @ReactPHP. Creator of Framework X. Head of @clue-engineering. Professional software engineer using open source to empower web-based projects.
Christian Lück
Brew PHP switcher is a simple shell script to switch your apache and CLI quickly between major versions of PHP

Brew PHP switcher is a simple shell script to switch your apache and CLI quickly between major versions of PHP. If you support multiple products/projects that are built using either brand new or old legacy PHP functionality. For users of Homebrew (or brew for short) currently only.

Phil Cook 872 Dec 22, 2022
Monitor for any changes in your php application and automatically restart it (suitable for async apps).

PHP-watcher PHP-watcher helps develop long-running PHP applications by automatically restarting them when file changes in the directory are detected.

Sergey Zhuk 373 Dec 21, 2022
A development tool for all your projects that is fast, easy, powerful and liberating

Lando A Liberating Dev Tool For All Your Projects The local development and DevOps tool trusted by professional developers across the galaxy. Free you

Lando 3.6k Jan 7, 2023
🎲Neard is a portable WAMP software stack involving useful binaries, tools and applications for your web development.

About Neard is a portable WAMP software stack involving useful binaries, tools and applications for your web development. It also offers several versi

Neard 335 Dec 22, 2022
Demo of how you can run your Laravel app with Docker Compose. Look at docker-compose.yml and the docker folder. The rest is just a clean Laravel + Horizon install.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Matt 5 Oct 22, 2021
A package that allows you to generate simple and fast Docker configurations for your Laravel application!

A package that allows you to generate simple and fast Docker configurations for your Laravel application!

Lucas Nepomuceno 3 Oct 8, 2022
Dockerized PHP development stack: Nginx, MySQL, MongoDB, PHP-FPM, HHVM, Memcached, Redis, Elasticsearch and RabbitMQ

PHP Dockerized Dockerized PHP development stack: Nginx, MySQL, MongoDB, PHP-FPM, HHVM, Memcached, Redis, Elasticsearch and RabbitMQ PHP Dockerized giv

Kasper Isager Dalsgarð 1.1k Dec 30, 2022
Laravel 5 with Dockerized Gulp, PHP-FPM, MySQL and nginx using docker-compose

docker-laravel Laravel 5 with Dockerized PHP-FPM, MySQL and nginx using docker-compose Usage Get Composer docker-compose run --rm phpnginx curl -O htt

Harsh Vakharia 83 Feb 8, 2022
Docker with PHP 7.4 fpm, Nginx, Composer, PhpUnit and MaridaDB

Clean Docker with PHP Docker with PHP 7.4 fpm, Nginx, Composer, PhpUnit and MariaDB Starting app docker-compose up -d Main page

Grzegorz Bielski 7 Nov 17, 2022
ServD - a Docker PHP development environment heavily inspired by Laravel Valet and Laradock

ServD ServD is a Docker PHP development environment heavily inspired by Laravel Valet and Laradock, it supports multiple projects within a working dir

Matt Clinton 2 May 13, 2022
Phansible - generate Vagrant + Ansible dev environments for PHP

Phansible Phansible is a simple generator for Vagrant projects, targeting PHP development environments, using Ansible as Provisioner. It was inspired

phansible 639 Nov 1, 2022
Full PHP development environment for Docker.

Full PHP development environment based on Docker. Use Docker First - Learn About It Later! Join Us Awesome People Laradock is an MIT-licensed open sou

laradock 11.7k Jan 7, 2023
Blazing fast macOS PHP development environment

Introduction Valet+ is a development environment for macOS. No Vagrant, no Docker, no /etc/hosts file. Go here for the valet+ documentation. Credits T

We Provide 1.5k Jan 2, 2023
DDEV-Local: a local PHP development environment system

ddev ddev is an open source tool that makes it simple to get local PHP development environments up and running in minutes. It's powerful and flexible

DDEV 1.6k Dec 29, 2022
Runs a PHP-based startpage in Docker

docker-php-startpage Runs a PHP-based startpage in Docker Source code: GitHub Docker container: Docker Hub Image base: PHP Init system: N/A Applicatio

Logan Marchione 25 Dec 28, 2022
Lamp Docker skeleton PHP + Nginx + Mysql + Redis

Stop installing the entire development stack on your local machine. This project will allow you to quickly start working with php. To install, you need to install docker locally.

Krepysh 9 Dec 27, 2022
Quickly deploy a seedbox with self-hosted services and a web portal using Docker and docker-compose.

Seedbox Quickly deploy and configure a seedbox with self-hosted services and a web portal using Docker and a single docker-compose.yml file. Screensho

null 6 Dec 7, 2022
Vagrant is a tool for building and distributing development environments.

Vagrant Website: https://www.vagrantup.com/ Source: https://github.com/hashicorp/vagrant HashiCorp Discuss: https://discuss.hashicorp.com/c/vagrant/24

HashiCorp 24.8k Jan 2, 2023