Linters for PHP projects

Today’s projects are built up from dozens of different components, configuration files, third-party libraries, tests, build scripts, etc. And even if you have the greatest test suite, bad things can happen sometimes. It’s important to catch bugs as early as possible, and syntax validators can be a great (and easy) addition to your continuous integration system. You would be surprised at how many problems are caused by syntax errors. At ServerGrove, we see these kind of problems with our clients almost every day.

252312738_e025b7edfa_z

PHP code

The PHP binary provides a way to check the syntax of a given PHP file, by using the “-l” option:

$ php -l script.php

Wildcards can also be used to check more than one file. And to check all PHP files in a project, the following command can be used:

$ find . -type f -name "*.php" -exec php -l {} \;

If there are too many files and the process is slow, you may want it to execute it in parallel. For that, create a simple shell script that does the actual lint and then modify the “find” command:

#!/bin/bash
php -l $1
$ find . -type f -name "*.php" | parallel -j4 ./phplint.sh {}

Twig templates

When used with Symfony, the framework has a command to check one or more Twig templates:

$ php app/console twig:lint template.html.twig

All Twig extensions are taken into account, so custom functions, filters or tags are taken into account to validate the input template.

When using Twig without Symfony, there is no a direct way to validate a template. You can build your own linter or use asm89/twig-lint.

Composer

Composer provides a command to validate “composer.json” files in order to find both syntax errors and common issues.

$ composer validate composer.json

XML files

It is common to use XML files for configuration, translations or service definitions. xmllint is a command-line tool that checks that XML are well-formed and valid for a given DTD or XML Schema.

For example, you can check that an XML file is well formed with the following command:

$ xmllint --schema src/ServerGrove/DemoBundle/Resources/config/services.xml

Normally, tags and attributes that you can use in XML files are defined in a DTD or XML Schema. For example, when using XLIFF files for translations, you can both check that are well formed and valid:

$ xmllint --schema http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --noout src/ServerGrove/DemoBundle/Resources/translations/messages.fr.xlf

There are schemas for many file types:

  • Doctrine mapping: http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd
  • Symfony DIC services: http://symfony.com/schema/dic/services/services-1.0.xsd
  • Symfony routing: http://symfony.com/schema/routing/routing-1.0.xsd
  • Symfony validator constraint mapping: http://symfony.com/schema/dic/constraint-mapping-1.0.xsd
  • PHPUnit configuration: http://schema.phpunit.de/4.1/phpunit.xsd

Bash scripts

If you use bash scripts to automatize tasks, the “bash” command contains an option to check its syntax without executing the script:

$ bash -n script.sh

JSON files

JSON files are sometimes used for configuration and dependency management. While there are several tools that you can use, we created a simple Melody script that makes use of Seldaek/jsonlint to check JSON files:

$ melody run https://gist.github.com/raulfraile/bb3616174c52cd6f077a bower.json

YAML files

JSON files are widely used for configuration purposes as they are easier to write for a human being. When using Symfony, there is a “yaml:lint” command since 2.5. Without Symfony, you can build your own linter or use the Melody script we did for you:

$ melody run https://gist.github.com/raulfraile/47d3264fdc86cb3f2cd4 parameters.yml

Are you missing any other file type? Let us know!

Photo: “Syntax Error – Folded Up Beyond All Recognition”, by “Simon Pow”

June 02 / 2015
Author Raul Fraile
Category PHP, Symfony, Tutorials
Comments 2 Comments

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Satis: building your own Composer repository

We all love Composer. It changed dramatically the way we build PHP applications, based on small and reusable components, but this creates new challenges, especially when we have a single point of failure (SPO). With Satis, the deployment process can be made robust by adding redundancy in all potential SPOFs (Packagist and GitHub). Let’s see how it works.

How does Composer work?

The following graph shows how Composer works when using Packagist as a central repository:

satis.001

Unless we explicitly indicate that we want to use a different repository, Composer makes use of the default repository: Packagist. Composer will ask Packagist for information about all packages required in the composer.json file, as well as dependencies required by those packages. When Composer gets all that information, it solves the dependency graph using a SAT solver and generates the composer.lock file with the specific packages that must be installed in order to fulfil the requirements defined in the composer.json file. Finally, Composer downloads those packages from different sources: GitHub, Bitbucket, PEAR or any GIT/SVN/Mercurial repository.

This architecture has a few problems: what happens if Packagist is down? It won’t work. Composer won’t be able to solve the dependency graph. In addition to this problem, what happens if the required packages are hosted in GitHub, and it’s currently down? It won’t work either. Packages won’t be downloaded, affecting your development and deployments.

How can we minimize all these issues? By adding redundancy. We can create our own repository with Satis to use this repository instead (or in addition to) Packagist. The following graph shows the same schema but using a custom repository:

satis.002

Now, Composer asks our custom repository for information about the required packages and only in case that our repository does not have that information, it asks Packagist. We can even disable Packagist and configure our repository to download all the dependencies of the packages as we’ll see later. In addition, our repository can download the packages and act as a reverse proxy. That way, we no longer depend on GitHub to download packages.

Installation

We can install Satis using Composer:

$ composer create-project composer/satis --stability=dev

Repository configuration file

Let’s pretend that we are a company with a few developers and all our projects only need two dependencies: the Symfony HttpFoundation component and Twig. We want to have a custom repository with all versions (excluding development versions) of those two packages so we don’t have to rely on GitHub:

{
    "name": "ServerGrove repository",
    "homepage": "http://149.5.47.251:8001",
    "repositories": [
        {
            "type": "vcs",
            "url": "git@github.com:symfony/HttpFoundation.git"
        },
        {
            "type": "vcs",
            "url": "git@github.com:twigphp/Twig.git"
        }
    ],
    "require-all": true,
    "require-dependencies": true,
    "archive": {
        "directory": "dist",
        "format": "tar",
        "skip-dev": true
    }
}

The format of the file is quite straightforward, but let’s see what each of the fields mean:

  • “name”: the name of the repository.
  • “homepage”: URL of the repository.
  • “repositories”: contains the list of repositories we want to mirror.
  • “require-all”: download all packages, not only tagged ones.
  • “require-dependencies”: when “true”, Satis automatically resolves and adds all dependencies, so Composer won’t need to use Packagist.
  • “archive”: “tar” files will be stored in the “dist” directory and we won’t download “dev” packages.

Then, we tell Satis to build the repository:

$ ./bin/satis build satis.json servergrove-satis/

After this, we have a new directory called servergrove-satis, which contains two files: packages.json and index.html. The former is the repository configuration file, the one that will be read by Composer to determine what packages the repository offers. The index.html file is just a static html file with information about the repository. It also contains the dist directory with all packages so they won’t have to be downloaded from GitHub anymore.

And finally, we publish our repository using the PHP built-in server (for real repositories it is recommended to use Apache or nginx):

$ php -S localhost:8001 -t servergrove-satis/

Now, if we go to http://localhost:8001/, we should see something like this:

Screen Shot 2015-04-29 at 13.35.51

Using the repository in Composer

Once we have the repository up and running, the last step is to tell Composer that we want to use it. This can be done by adding the following lines to the composer.json file of your project.

{
    "repositories" : [
        {"type" : "composer", "url" : "http://localhost:8001/"}
    ],
    ...
}

Then, when executing composer install, our custom repository should be used:

composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing symfony/http-foundation (v2.6.6)
    Downloading: 100%

Writing lock file
Generating autoload files

We can see in the composer.lock file that download URLs are no longer from GitHub, but from our own repository:

{
    ...
    "packages": [
        {
            "name": "symfony/http-foundation",
            "version": "v2.6.6",
            "target-dir": "Symfony/Component/HttpFoundation",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/HttpFoundation.git",
                "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9"
            },
            "dist": {
                "type": "tar",
                "url": "http://localhost:8001/dist/symfony-http-foundation-8a6337233f08f7520de97f4ffd6f00e947d892f9-zip-3c2665.tar",
                "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9",
                "shasum": "04c8f9c294585c8fc982762f5181eab0083fdf3a"
            }
        }
    ...
}

Also, we can see the requests that Composer did in order to get the repository first (packages.json includes /include/all$e0f2af5bfe26328fcc97241cbd95de682f4fbfaa.json) and then the package itself:

[Wed Apr 29 13:38:56 2015] x.x.x.x:64826 [200]: /packages.json
[Wed Apr 29 13:38:56 2015] x.x.x.x:64827 [200]: /include/all$e0f2af5bfe26328fcc97241cbd95de682f4fbfaa.json
[Wed Apr 29 13:39:13 2015] x.x.x.x:64839 [200]: /dist/symfony-http-foundation-8a6337233f08f7520de97f4ffd6f00e947d892f9-zip-3c2665.tar

More info

April 29 / 2015
Author Raul Fraile
Category PHP
Comments 4 Comments

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Useful Linux command-line tools to work with PHP projects

Linux provides a lot of interesting command-line tools that we can use when working with PHP projects. In this post we give you some useful commands:

Find all PHP files in the current directory

$ find . -type f -name "*.php"
./index.php
./vendor/autoload.php

Obviously, we can use the same command to get another types of files, such as TWIG or JSON files:

$ find . -type f -name "*.twig"
./test.twig

$ find . -type f -name "*.json"
./composer.json

Check the syntax of all PHP files in the current directory

By itself, “php -l” checks the syntax of a single PHP file. This command can be really useful as part of a CI (Continuous Integration) system to check that we don’t have any syntax error in all PHP files of our project:

$ find . -type f -name "*.php" -exec php -l {} \;
No syntax errors detected in ./index.php
No syntax errors detected in ./vendor/autoload.php

There are another linters and validators that can be used the same way, but they require additional tools. For example, Composer has an option to validate the “composer.json” file, and Symfony provides linters for Twig and YAML files.

For example, if you have several “composer.json” files in your project and want to validate all of them, you can use the “composer validate” command (or “php composer.phar validate”) to check them all:

$ find . -type f -name "composer.json" -exec composer validate {} \;
./composer.json is valid for simple usage with composer but has
./vendor/symfony/config/Symfony/Component/Config/composer.json is valid
./vendor/symfony/console/Symfony/Component/Console/composer.json is valid
./vendor/symfony/dependency-injection/Symfony/Component/DependencyInjection/composer.json is valid

Get the size of each Composer dependency

How big are your dependencies? The du command estimates the file space usage for each dependency:

$ du -h -d 2 vendor
 56K	vendor/composer
496K	vendor/symfony/config
1.2M	vendor/symfony/console
...

Find suspicious PHP files

It’s very common in hacked WordPress projects to inject malicious code that makes use of functions such as eval(), exec() or base64_decode(). The following command finds PHP files that contain any of these functions:

$ find . -type f -name "*.php" -exec grep --with-filename "eval(\|exec(\|base64_decode(" {} \;
./virus.php: eval(base64_decode($_GET['a']));

Get the number of “use” statements in all PHP files, ordered

Does your classes contain too many “use” statements? Maybe it’s time to refactor! Check candidates with the following command:

$ find . -type f -name "*.php" -exec grep --with-filename -c "^use " {} \; | sort -t ":" -k 2 -n -r
./vendor/symfony/console/Symfony/Component/Console/Application.php:28
./vendor/symfony/dependency-injection/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php:21
./vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php:18
./vendor/symfony/finder/Symfony/Component/Finder/Finder.php:15
...

Find files with abstract classes

The following command will find all files that define abstract classes:

$ find . -type f -name "*.php" -exec grep --with-filename -c "^abstract class " {} \; | grep ":[^0]"
./vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php:1
./vendor/twig/twig/lib/Twig/TokenParser.php:1
...

Similarly, we can find PHP files containing 2 or more classes in a single file, ordered:

$ find . -type f -name "*.php" -exec grep --with-filename -c "^class " {} \; | grep ":[2-99]" | sort -t ":" -k 2 -n -r
./vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php:4
./vendor/twig/twig/test/Twig/Tests/IntegrationTest.php:4
...

List PHP settings for the xdebug extension

“php -i” outputs a huge list with all the settings that are in use. Thanks to the grep utility, we can display only the ones we are interested in. For example, to get all settings related to the xdebug extension:

$ php -i | grep xdebug

Find empty files and/or directories

Check if you left empty files in your project:

$ find . -type f -empty
./test.txt

List files currently open by a PHP process

In Linux, all processes are mapped into the /proc filesystem, so we can get lots of interesting information from a process, like open files.

For example, in the following script we open a file called “test.txt” located in the same directory of the file and then wait for 100 seconds so we have time to execute some commands.

$file = fopen(__DIR__ . 'test.txt', 'w');
sleep(100);
fclose($f);

Then, we execute it and get the PID (process id):

$ php test.php &
[2] 9525

$ ps aux|grep "test.php"
root      9525  0.1  2.0 161404 10736 pts/0    S    11:07   0:00 php test.php

So, the PID is 9525. Now, we can look for information about the process in the /proc filesystem. Each process has a directory with the same name as the PID. To get the list of open files:

$ ls -l /proc/9525/fd
total 0
lrwx------ 1 root root 64 Apr 23 11:07 0 -> /dev/pts/0
lrwx------ 1 root root 64 Apr 23 11:07 1 -> /dev/pts/0
lrwx------ 1 root root 64 Apr 23 11:07 2 -> /dev/pts/0
lr-x------ 1 root root 64 Apr 23 11:07 3 -> /proc/9525/auxv
l-wx------ 1 root root 64 Apr 23 11:07 4 -> /test.txt

The “fd” directory means file descriptors. And we can see that “test.txt” is there! The other file descriptors are for standard input, output and error.

We can also get the the full path of the command, so we know that has been executed using PHP 5.4:

$ ls -l /proc/9580/exe
lrwxrwxrwx 1 root root 0 Apr 23 11:11 /proc/9525/exe -> /usr/local/php54/bin/php

$ /usr/local/php54/bin/php -v
PHP 5.4.5 (cli) (built: Jul 26 2012 15:17:07)

Based on these commands, we can create new ones to fit our needs.

April 23 / 2015

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Symfony2 components overview: Filesystem

The 15th post of the Symfony2 components series is focused on the Filesystem component, which provides some basic utilities to work with the filesystem. It extends PHP built-in functions such as mkdir() or copy() to make them more portable and easier to use and test.

The Filesystem component provides basic utilities to deal with the filesystem

The Filesystem component provides basic utilities to deal with the filesystem

The problem

PHP built-in functions for filesystem operations mimic common utilities in Unix-like based systems to work with the filesystem. They borrow not only their names, but also the way they work. Because of this, their usage is not very intuitive, especially for Windows users. Take this code as an example:

mkdir(__DIR__ . '/dir/subdir');

First, you may not have permissions to create the directory “subdir” inside “dir”:

PHP Warning:  mkdir(): Permission denied in filesystem_test.php on line 5

Also, it may happen that you do have permissions, but the directory “dir” does not exist:

PHP Warning:  mkdir(): No such file or directory in filesystem_test.php on line 5

Both errors are pretty annoying, as they throw PHP warnings that cannot be intercepted. Also, there is no good reason (at least for me) to force us to create first “dir” and then “subdir”. By using the Filesystem component we can get rid off from all these annoying issues. They work like you would expect without reading the docs and take care of the small (or not so small) differences between Windows and Unix-like systems.

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;

$filesystem = new Filesystem();

try {
    $filesystem->mkdir(__DIR__ . '/dir/subdir');
} catch (IOExceptionInterface $e) {
    echo 'Error: ' . $e->getPath() . ' could not be created';
}

Yay! The mkdir() method of the component creates both directories recursively.

Another advantage is that we can get exceptions when something goes wrong, so if we try to create a directory in a place that we don’t have permissions, we will get an IOExceptionInterface:

$filesystem->mkdir('/root/dir/subdir');
Error: /root/dir/subdir could not be created

In general, methods accept strings, arrays or objects implementing the Traversable interface, so we can create several directories (or any other operation) in a single call. Remember that the Traversable interface is an abstract interface, IteratorAggregate or Iterator extend from it.

The following snippets are equivalent:

$filesystem->mkdir(__DIR__ . '/dir/subdir1');
$filesystem->mkdir(__DIR__ . '/dir/subdir2');
]);
$filesystem->mkdir([
    __DIR__ . '/dir/subdir1',
    __DIR__ . '/dir/subdir2'
]);
$dirCollection = new DirCollection([
    __DIR__ . '/dir/subdir1',
    __DIR__ . '/dir/subdir2'
]);
$filesystem->mkdir($dirCollection);

The DirCollection object is just a toy class implementing the Iterator interface:

class DirCollection implements Iterator
{

    protected $directories;

    protected $position;

    public function __construct(array $directories = [])
    {
        $this->directories = $directories;
        $this->position = 0;
    }

    public function current()
    {
        return $this->directories[$this->position];
    }

    public function next()
    {
        ++$this->position;
    }

    public function key()
    {
        return $this->position;
    }

    public function valid()
    {
        return isset($this->directories[$this->position]);
    }

    public function rewind()
    {
        $this->position = 0;
    }

}

We could also have used built-in iterators. For example, we could change the modification time of all files and directories belonging to a given directory recursively combining RecursiveDirectoryIterator and RecursiveIteratorIterator. With RecursiveDirectoryIterator we iterate recursively over filesystem directories and with RecursiveIteratorIterator we go over the whole hierarchy.

$filesystem = new Filesystem();

$directoryIterator = new \RecursiveDirectoryIterator(__DIR__ . '/origin', \FilesystemIterator::SKIP_DOTS);
$iterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);

$filesystem->touch($iterator);

Useful methods

We have seen so far only two methods: mkdir() and touch(), but the component provides a few more. As they are well documented in the official docs, we’ll just list them here and try only a few of them:

  • copy($originFile, $targetFile, $override = false)
  • mkdir($dirs, $mode = 0777)
  • exists($files)
  • touch($files, $time = null, $atime = null)
  • remove($files)
  • chmod($files, $mode, $umask = 0000, $recursive = false)
  • chown($files, $user, $recursive = false)
  • chgrp($files, $group, $recursive = false)
  • rename($origin, $target, $overwrite = false)
  • symlink($originDir, $targetDir, $copyOnWindows = false)
  • makePathRelative($endPath, $startPath)
  • mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array())
  • isAbsolutePath($file)
  • dumpFile($filename, $content, $mode = 0666)

The dumpFile() method is especially interesting, as dumps atomically content into a file. That means that we will never see a partially-written file, as it writes a temporary file first and then moves it to the new file location when it’s finished.

Finally, the makePathRelative() method is really useful when we want to have a relative path based on our current working directory.

$dir = '/home/raulfraile/filesystem/';

echo $dir; // /home/raulfraile/filesystem/
echo $filesystem->makePathRelative($dir, '/home/raulfraile'); // filesystem/

File locking

From 2.6, the component ships with another interesting feature: a simple file locking mechanism. You may want to implement a locking system for several reasons, but it’s common for avoiding overlapping in cron jobs.

For example, if we have a task that is executed every 5 minutes and those tasks take more than 5 minutes to finish, they will overlap. In a locking mechanism based on files, the first process creates the lock file, so other processes, when check that that file exists, will wait or finish. Once the first process finishes, it removes the lock file.

Its usage is very simple. The following code shows how to create a lock called “cron.lock”, so only one process is running at a time.

use Symfony\Component\Filesystem\LockHandler;

$lockHandler = new LockHandler('cron.lock');
if (!$lockHandler->lock()) {
    // another process is running, exit
    echo 'Another process is already running';

    return 1;
}

// do stuff...
sleep(100);

If we try to execute two processes, we get the error message in the second one:

$ php index.php &
[1] 10241

$ php index.php
Another process is already running

Who’s using it?

More info

Photo: Filesystem, by kleuske.

April 22 / 2015

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid

Melody: One-file PHP scripts with Composer dependencies

Melody is a really handy open-source tool to create one-file PHP scripts with Composer dependencies.

Melody is a tool to execute one-line PHP scripts with dependencies

Melody is a tool to execute one-line PHP scripts with dependencies

Installation

As Melody is just a PHAR file, we only need to download it and include it in one of the directories included in our PATH environment variable, for example at /usr/local/bin/:

$ sudo sh -c "curl http://get.sensiolabs.org/melody.phar -o /usr/local/bin/melody && chmod a+x /usr/local/bin/melody"

Then, if we execute “melody”, we should see something like this:

$ melody
Melody version 1.0 by SensioLabs

Usage:
 [options] command [arguments]
Options:
 --help (-h)           Display this help message.
 --quiet (-q)          Do not output any message.
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
 --version (-V)        Display this application version.
 --ansi                Force ANSI output.
 --no-ansi             Disable ANSI output.
 --no-interaction (-n) Do not ask any interactive question.

Available commands:
 help          Displays help for a command
 list          Lists commands
 run           execute a script
 self-update   Update melody.phar to the latest version.

Simple example

Let’s create a simple example to understand how Melody works. The following script makes use of the symfony/finder component to look for PNG files in the current directory (and subdirectories):

<?php
<<<CONFIG
packages:
    - "symfony/finder: ~2.6"
CONFIG;

$finder = Symfony\Component\Finder\Finder::create()
    ->in(__DIR__)
    ->name('*.png')
;

foreach ($finder as $file) {
    echo $file, "\n";
}

As you can see, it’s a regular PHP script with a heredoc string at the beginning, which defines the packages the script depends on, using the YAML format in a similar way as we do it in composer.json files.

Melody reads the heredoc string called CONFIG and downloads the packages behind the scenes using Composer. To run the script:

$ melody run example.php
/Users/raulfraile/logo.png
/Users/raulfraile/photo.png

Extra arguments

It is possible to send additional parameters to the PHP script appending them after the name of the script so they can be read using the $argc and $argv variables. For example, to configure the directory to look for PNG images:

$finder = Symfony\Component\Finder\Finder::create()
    ->in($argv[1])
    ->name('*.png')
;

And then:

$ melody run example.php /tmp
/tmp/image.png

PHP options

What if you need to pass additional options to the PHP binary? For example, you may want to start the built-in server or change some settings. This can be done using the “php-options” setting.

In the following example, the memory_limit setting has been set to 256M and the built-in server will be launched:

<<<CONFIG
packages:
    - ...
php-options:
    - "-d"
    - "memory_limit=256M"
    - "-S"
    - "localhost:8000"
CONFIG;

Running gists

Melody has another nice feature, it can run GitHub gists directly! This can be useful for scripts that we want to execute in different computers. For example, I created a simple script to check your ServerGrove’s mail settings sending a test email based on servergrove/email-test. To execute it, you need to include 3 parameters: $from, $to and $password:

$ melody run https://gist.github.com/raulfraile/8b87792f1ecbca520191 from@test.com to@test.com 123456
OK

This script sends a test email from from@test.com to to@test.com using the ServerGrove’s mail server. Obviously, it will only work if you are using our mail server, but setting up a different one is trivial.

Conclusion

Melody can be really handy. At ServerGrove, we are thinking about creating a repository of small tools to help our clients to check and manage their servers. What do you think?

Photo: “Music box 2″, by grfx_guru.

April 15 / 2015
Author Raul Fraile
Category PHP, Tutorials
Comments No Comments

Upcoming Conferences

WeCamp

PHP New Zealand

PHP Summer Camp Croatia

PHPNE

MadisonPHP

brnoPHP

SymfonyLiveLondon

ZgPHP

PHPSouthAfrica

PHPNWUK

SymfonyLiveNYC

PHPForumParis

PHPARG

PHPWorld

TechMeetupUY

SymfonyConMadrid