Getting Started

Installing

You can install Idephix in several ways:

As a composer dependency

$ composer require ideato/idephix --dev

Globally using homebrew

$ brew tap ideatosrl/php
$ brew install idephix

Basic Usage

Idephix is a tool for running tasks. As a developer your main focus will be on writing tasks (as php functions) inside a file called idxfile.php. You will also need to specify some configurations inside a file called idxrc.php.

Fortunately you won’t need to create those files manually, Idephix can generate them for you.

$ idx initFile

This will generate an idxfile.php and a idxrc.php file that you can use as a boiler plate for your automated tasks.

Basically Idephix is a tool for running tasks either remote or local. Remote tasks can be run against a chosen environment connecting to it through ssh (see Configuration for more information on ssh connection and environments).

Local tasks are run on the local host without any need to establish an ssh connection.

Configuration

All Idephix configurations are defined within the idxrc.php file. By default Idephix will look for a file named idxrc.php in the root directory of your project, but you can store it wherever you want and name it whatever you want. If you want to use a custom configuration file you need to specify id by using -c option with Idephix CLI.

The file must return an array of configurations. Idephix uses 3 main configuration elements:

  • environments
  • ssh_client
  • extensions

None of them are mandatory, you’ll need environments (at least one) and ssh_client only to execute remote tasks and extensions only if you want to register some extension.

This example of idxrc.php file will give you and idea of how define environments, ssh clients and extensions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

$environments = array(
    'prod' => array(
        'hosts' => array('127.0.0.1', '33.33.33.10'),
        'ssh_params' => array(
            'user' => 'ideato'
        ),
    ),
    'stage' => array(
        'hosts' => array('192.168.169.170'),
        'ssh_params' => array(
            'user' => 'ideato'
        ),
    ),
    'test' => array(
        'hosts' => array('127.0.0.1'),
        'ssh_params' => array('user' => 'kea'),
    ),
);

return array(
    'envs' => $environments,
    'ssh_client' => new \Idephix\SSH\SshClient(),
    'extensions' => array(),
);

Idephix use ssh-agent to authenticate to remote computers without password. Otherwise you can specify the password in your script or use CLISshProxy (instead of the default PeclSsh2Proxy) that ask you the password.

Once you have defined several environments you can specify which one you want to run your remote task against, using --env CLI option.

Defining Tasks

To define a new task you just need to define a function within the idxfile.php and it will be automatically mounted as an Idephix command.

1
2
3
4
5
6
<?php

function myNewTask()
{
    echo 'I am a brand new task' . PHP_EOL;
}

Now running idx you’ll get

$ bin/idx

$ Available commands:
$ help            Displays help for a command
$ initFile        Init idx configurations and tasks file
$ list            Lists commands
$ myNewTask

And you can execute it with:

$ bin/idx myNewTask
I am a brand new task

You can even execute a task within another task:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php

function anotherTask()
{
}

function myNewTask(\Idephix\Context $context)
{
    $context->anotherTask();
}

Hint

Every task can define a special arguments: $context. If you define an argument and type hint it as \Idephix\Context an instance of the context object will be injected at runtime. The context object allows you to execute tasks and access configuration parameters. For more info about Context check out Scripting with Idephix section

Adding task arguments

Function parameters will be used as the task arguments.

1
2
3
4
5
6
<?php

function yell($what)
{
    echo $what . PHP_EOL;
}

Mandatory Arguments

The parameter $name will be a mandatory option to be specified in the command execution.

$ bin/idx help yell
Usage:
    yell what

Arguments:
    what

You can add as many arguments as you need, just adding function parameters.

Optional Arguments

If you want to add optional arguments, just define a default value for the parameter, as:

1
2
3
4
5
6
<?php

function yell($what = 'foo')
{
    echo $what . PHP_EOL;
}

Optional arguments as task flags

A flag is a special parameter with default value false. Using flags should be useful to implement a dry-run approach in your script

1
2
3
4
5
6
7
8
   <?php

   function deploy($go = false){
        if ($go) {
            //bla bla bla
        return;
    }
}

Documenting tasks

Tasks and arguments can have a description. You can define descriptions using simple and well known phpdoc block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

/**
 * This command will yell at you
 *
 *
 * @param string $what What you want to yell
 */
function yell($what = 'foo')
{
    echo $what . PHP_EOL;
}

Configure a task like

$ bin/idx help yell
Usage:
    yell [what]

Arguments:
    what    What you want to yell (default: "foo")

Scripting with Idephix

With Idephix you compose your script basically:

  • executing local commands
  • executing remote commands
  • executing other tasks you have already defined
  • sending some output to the console

In order to perform such operations you will need an instance of the Idephix\\Context object. Idephix will inject it at runtime in each tasks that defines an argument type hinted as Idephix\\Context. A Context implements \Idephix\TaskExecutor and \Idephix\DictionaryAccess allowing you to execute commands and to access the configuration data related to the choosen env.

Executing local commands

\Idephix\TaskExecutor::local allows you to execute local commands. A local command will be executing without any need for a SSH connection, on your local machine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php

function buildDoc(\Idephix\Context $context, $open = false)
{
    $context->local('cp -r src/Idephix/Cookbook docs/');
    $context->local('make  -C ./docs html');

    if ($open) {
        $context->openDoc();
    }
}

If you need so you can execute the command in dry run mode

1
2
3
4
5
6
<?php

function buildDoc(\Idephix\Context $context, $open = false)
{
    $context->local('cp -r src/Idephix/Cookbook docs/', true);
}

In dry run mode the command will just be echoed to the console. This can be useful while debugging your idxfile to check the actual command that would be executed.

For local commands you can also specify a timeout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php

function buildTravis(\Idephix\Context $context)
{
    try {
        $context->local('composer install');
        $context->local('bin/phpunit -c tests --coverage-clover=clover.xml', false, 240);
        $context->runTask('createPhar');
    } catch (\Exception $e) {
        $context->output()->writeln(sprintf("<error>Exception: \n%s</error>", $e->getMessage()));
        exit(1);
    }
};

Executing remote commands

Running remote commands is almost the same as running local commands. You can do that using \Idephix\TaskExecutor::remote method. Dry run mode works quite the same as for local commands, but mind that at the moment is not possible to specify a timeout for remote commands.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php

function switchToNextRelease(Idephix\Context $context, $remoteBaseDir, $nextRelease, $go = false)
{
    $context->remote(
        "
        cd $remoteBaseDir && \\
        ln -nfs $nextRelease current",
        !$go
    );
}

In order to execute a remote command you must specify an environment using --env option. If you fail to specify a valid env name you will get an error and the command will not be executed.

Executing user defined tasks

Every task that you define will be accessible as a method of the Idephix\Context object. Mind that you don’t have to manually inject the Context object, Idephix will do that for you at runtime.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

function buildDoc(\Idephix\Context $context, $open = false)
{
    $context->local('cp -r src/Idephix/Cookbook docs/');
    $context->local('make  -C ./docs html');

    if ($open) {
        $context->openDoc();
    }
}

function openDoc(\Idephix\Context $context)
{
    $context->local('open docs/_build/html/index.html');
}

Accessing configuration from tasks

Idephix\Context object gives you also access to every configuration defined for the current environment. Imagine you have defined this configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php

$environments = array(
    'prod' => array(
        'hosts' => array('127.0.0.1'),
        'ssh_params' => array(
            'user' => 'ideato'
        ),
        'deploy' => array(
            'repository' => './',
            'branch' => 'origin/master',
            'shared_files' => array('app/config/parameters.yml'),
            'shared_folders' => array('app/cache', 'app/logs'),
            'remote_base_dir' => '/var/www/testidx',
            'rsync_exclude' => './rsync_exclude.txt',
        )
    ),
    'test' => array(//blablabla),
);

While executing a command using --env=prod option your tasks will receive a Context filled up with prod data, so you can access to it. Context allows you to access configuration data implementing php \ArrayAccess interface or through get \Idephix\DictionaryAccess::get method.

1
2
3
4
5
6
7
<?php

function deploy(Idephix\Context $context, $go = false)
{
    $sharedFiles = $context->get('deploy.shared_files', array());
    $repository = $context['deploy.repository'];
    //cut

Writing output to the console

Idephix is based on Symfony console component so you can send output to the user using the \Symfony\Component\Console\Style\SymfonyStyle. You can get the full SymfonyStyle component through the \Idephix\TaskExecutor::output method or you can use the shortcut methods: \Idephix\TaskExecutor::write and \Idephix\TaskExecutor::writeln.

Here is an example of you you can send some output to the console.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

/**
 * This command will yell at you
 *
 * @param string $what What you want to yell
 */
function yell(\Idephix\Context $context, $what = 'foo')
{
    $context->writeln(strtoupper($what));
    $context->write(strtoupper($what) . PHP_EOL);

    $output = $idx->output();

    // common output elements
    $output->title($what);
    $output->section($what);
    $output->text($what);
    $output->comment($what);
    $output->note($what);
    $output->caution($what);
    $output->listing([$what, $what, $what]);
    $output->success($what);
    $output->error($what);
    $output->warning($what);

    //table
    $headers = ['Parameter', 'Value', 'Value 3'];
    $rows = [
      ['Param1', 'Value1', 'Value 3'],
      ['Param2', 'Value2', 'Value 3']
    ];
    $output->table($headers, $rows);
}

Hint

For more information about SymfonyStyle read the official component documentation

Extensions

Extensions are meant to wrap reusable code into a class that you can wire to your next Idephix project. If you find yourself writing the same task over and over again you may want to put it into an Extension so you can easily reuse it in every projects.

Hint

Extensions should be used wisely, for most cases a bunch of tasks that you copy and paste across projects is the best solution. We in the first place dropped the Deploy solution to a standard recipe that we include in out idxfile for each project. This ease the readability and the hackability of the procedure. An Extension will allow you to define reusable code, but it will hide it a little bit, so take this in consideration before implementing one

An Extension is identified by a name, and is capable of:

  • registering new Tasks, so they will be directly available from CLI
  • registering methods that will be hooked into the Idephix instance so you can use them within other tasks

Writing Extensions

An Extension is simply a class implementing IdephixExtension interface. This will require you do define a name and, TasksCollection and a MethodCollection. If your extension don’t need to register new tasks or methods, you can simply return an empty collection (IdephixTaskTaskCollection::dry() or IdephixExtensionMethodCollection::dry()).

If you need an instance of the current IdephixContext within your extension, simply implement also the IdephixExtensionIdephixAwareInterface and you’ll get one at runtime.

Only method registered by ::methods() will be plugged into Idephix and will be available for other tasks to use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class DummyExtension implements Extension
{

    public function doStuff(IdephixInterface $idx, $foo, $go=false)
    {
        //do some stuff
    }

    /** @return array of callable */
    public function methods()
    {
        return Extension\MethodCollection::ofCallables(
            array(
                new Extension\CallableMethod('doStuff', array($this, 'doStuff'))
            )
        );
    }

//cut
}
1
2
3
4
5
6
7
8
<?php
//your idxfile.php

function deploy(IdephixInterface $idx, $go = false)
{
    //your deploy business logic here
    $idx->doStuff($foo, $go)
}

If you want to expose some of your methods as CLI commands you need to define them as a task:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

class DummyExtension implements Extension
{

    /** @return TaskCollection */
    public function tasks()
    {
        return TaskCollection::ofTasks(array(
            new Task(
            'doStuff',
            'DummyExtension helps you doing stuff',
            array($this, 'doStuff'),
            Collection::createFromArray(array(
                'foo' => array('description' => 'A nice description of foo')
            )),
        ));
    }

//cut
}

And the you’ll also get to execute it directly from cli:

$ idx doStuff bar

Hint

Check out our available extensions to see more complex examples ..

Execution priority

Idephix will always try to execute code from the idxfile first, so if some function within the idxfile conflicts with some registered method or task, the code from the idxfile will be executed and the extension code will be ignored.

Cookbook

Deploying with Idephix

Deploying a PHP application can be done in many ways, this recipe shows you our best strategy for a generic PHP application, and it is composed of several steps:

  • Preparing the local project
  • Preparing the remote server
  • Syncing the project to the server
  • Linking shared files across releases (configuration files, cache, logs, etc)
  • Switching the symlink for the new release, finalizing the deploy

The recipe organize your code on the server using a directory hierarchy borrowed from Capistrano:

├── current -> /var/www/my_app_name/releases/20150120114500/
├── releases
│   ├── 20150080072500
│   ├── 20150090083000
│   ├── 20150100093500
│   ├── 20150110104000
│   └── 20150120114500
└── shared
    └── <linked_files and linked_dirs>

So you can keep multiple releases on the server and switch the current release just creating a symlink to the actual one you want to make current. This allows you to easily rollback from one release to another.

idxfile.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php

function deploy(Idephix\Context $context, $go = false)
{
    $sharedFiles = $context->get('deploy.shared_files', array());
    $sharedFolders = $context->get('deploy.shared_folders', array());
    $remoteBaseDir = $context->get('deploy.remote_base_dir');
    $rsyncExclude = $context->get('deploy.rsync_exclude');
    $repository = $context->get('deploy.repository');
    $deployBranch = $context->get('deploy.branch');
    $nextRelease = "$remoteBaseDir/releases/" . time();
    $linkedRelease = "$remoteBaseDir/current";
    $localArtifact = '.deploy';
    $context->prepareArtifact($localArtifact, $repository, $deployBranch, $go);
    $context->prepareSharedFilesAndFolders($remoteBaseDir, $sharedFolders, $sharedFiles, $go);
    try {
        $context->remote("cd $remoteBaseDir && cp -pPR `readlink {$linkedRelease}` $nextRelease");
    } catch (\Exception $e) {
        $context->output()->writeln('<info>First deploy, sending the whole project</info>');
    }
    $dryRun = $go ? '' : '--dry-run';
    $context->rsyncProject($nextRelease, $localArtifact . '/', $rsyncExclude, $dryRun, $go);
    $context->linkSharedFilesAndFolders($sharedFiles, $sharedFolders, $nextRelease, $remoteBaseDir, $go);
    $context->switchToNextRelease($remoteBaseDir, $nextRelease, $go);
}

function prepareArtifact(Idephix\Context $context, $localArtifact, $repository, $deployBranch, $go = false)
{
    $context->local(
        "
        rm -Rf {$localArtifact} && \\
        git clone {$repository} {$localArtifact} && \\
        cd {$localArtifact} && \\
        git fetch && \\
        git checkout --force {$deployBranch} && \\
        composer install --no-dev --prefer-dist --no-progress --optimize-autoloader --no-interaction
        ",
        !$go
    );
}

function prepareSharedFilesAndFolders(Idephix\Context $context, $remoteBaseDir, $sharedFolders, $sharedFiles, $go = false)
{
    $context->remote(
        "mkdir -p {$remoteBaseDir}/releases && \\
         mkdir -p {$remoteBaseDir}/shared",
        !$go
    );
    foreach ($sharedFolders as $folder) {
        $context->remote("mkdir -p {$remoteBaseDir}/shared/{$folder}", !$go);
    }
    foreach ($sharedFiles as $file) {
        $sharedFile = "{$remoteBaseDir}/shared/{$file}";
        $context->remote("mkdir -p `dirname '{$sharedFile}'` && touch \"$sharedFile\"", !$go);
    }
}
function linkSharedFilesAndFolders(Idephix\Context $context, $sharedFiles, $sharedFolders, $nextRelease, $remoteBaseDir, $go = false)
{
    foreach (array_merge($sharedFiles, $sharedFolders) as $item) {
        $context->remote("rm -r $nextRelease/$item", !$go);
        $context->remote("ln -nfs $remoteBaseDir/shared/$item $nextRelease/$item", !$go);
    }
}

function switchToNextRelease(Idephix\Context $context, $remoteBaseDir, $nextRelease, $go = false)
{
    $context->remote(
        "
        cd $remoteBaseDir && \\
        ln -nfs $nextRelease current",
        !$go
    );
}

These tasks are based on several configuration options that you can define in your idxrc.php file:

idxrc.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

$environments = array(
    'prod' => array(
        'hosts' => array('127.0.0.1'),
        'ssh_params' => array(
            'user' => 'ideato',
//            'password'             => '',
//            'public_key_file'      => '',
//            'private_key_file'     => '',
//            'private_key_file_pwd' => '',
//            'ssh_port'             => '22'
        ),
        'deploy' => array(
            'repository' => './',
            'branch' => 'origin/master',
            'shared_files' => array('app/config/parameters.yml'),
            'shared_folders' => array('app/cache', 'app/logs'),
            'remote_base_dir' => '/var/www/testidx',
            'rsync_exclude' => './rsync_exclude.txt',
        )
    ),
);

return
    array(
        'envs' => $environments,
        'ssh_client' => new \Idephix\SSH\SshClient(),
        'extensions' => array(
            'rsync' => new \Idephix\Extension\Project\Rsync(),
        ),
    );

Migrating your idx file

Since version 0.2.0 we started supporting a new idxfile format. We think the new format is more easy to write and we will drop support for the old format soon. If you’re still using the old idxfile format you can avoid migrating right now, just be sure to create an instance of IdephixConfig to construct your Idephix instance instead of the array you’re using right now.

Implementing this should be easy enough as the IdephixConfig object can be created from an array, see Configuration for more information.

If you’re brave enough and want to jump on the innovation wagon right now read writing_tasks on how to update your idxfile.

Welcome to Idephix’s documentation!

Idephix is a PHP automation tool useful to perform remote and local tasks. It can be used to deploy applications, rotate logs, synchronize data repository across server or create a build system. The choice is up to you. Idephix is still in alpha, so things will change. You can report issues and submit PRs (greatly appreciated :-)) on the github repo

Basically what you’re going to do is define a bunch of function in a php file and execute them from the command line.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
/**
* This command will yell at you
*
* @param string $what What you want to yell
*/
function yell(\Idephix\Context $context, $what = 'foo')
{
   $context->writeln(strtoupper($what));
}
$ bin/idx yell "say my name"
SAY MY NAME

Requirements

PHP 5.3.2 or above, at least 5.3.12 recommended

License

Idephix is mantained by ideato, licensed under the MIT License.