分类目录归档:Symfony

Symfony组件 之 Console

在编写命令工具方面,在PHP的世界里,我暂时没有找到比Symfony的Console更加好的组件。Laravel中的命令行工具也是对Symfony的Console的再次封装,可见这个组件确实是一个绝佳的轮子。

首先是创建一个命令行工具类:

namespace Acme\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('demo:greet')
            ->setDescription('Greet someone')
            ->addArgument(
                'name',
                InputArgument::OPTIONAL,
                'Who do you want to greet?'
            )
            ->addOption(
               'yell',
               null,
               InputOption::VALUE_NONE,
               'If set, the task will yell in uppercase letters'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        if ($name) {
            $text = 'Hello '.$name;
        } else {
            $text = 'Hello';
        }

        if ($input->getOption('yell')) {
            $text = strtoupper($text);
        }

        $output->writeln($text);
    }
}

自己的命令行工具类需要继承Symfony\Component\Console\Command\Command,这个命令行需要实现configure和execute方法,configure方法自然就是设置命令工具的输入参数或输入选项,这些输入参数或选项,可以在execute方法中被取回以判断如何执行命令。

命令类写好之后,需要把命令放入一个Application中

#!/usr/bin/env php
<?php
// application.php

require __DIR__.'/vendor/autoload.php';

use Acme\Console\Command\GreetCommand;
use Symfony\Component\Console\Application;

$application = new Application();
$application->add(new GreetCommand);
$application->run();

然后运行命令:

$ php application.php demo:greet Fabien
$ php application.php demo:greet Fabien --yell

这个实现方案非常的直观干净,可以编写许多的命令,然后用add添加到application中即可,如果把这些命令通过一个配置文件注入,那么这个入口文件不需要修改,每次添加新的命令后修改一下配置,然后命令就可用了。

分清楚Argument和Option
Arguments are the strings – separated by spaces – that come after the command name itself. They are ordered, and can be optional or required. (Arguments使用空格分隔,跟在命令名称之后。它们是有顺序的,可以是可选或者必选)

$this
    // ...
    ->addArgument(
        'name',
        InputArgument::REQUIRED,
        'Who do you want to greet?'
    )
    ->addArgument(
        'last_name',
        InputArgument::OPTIONAL,
        'Your last name?'
    );

$this
    // ...
    ->addArgument(
        'names',
        InputArgument::IS_ARRAY,
        'Who do you want to greet (separate multiple names with a space)?'
    );

参数类型是IS_ARRAY类型,说明它可以接收多个值:

#在命令行之后跟随多个值
$ php application.php demo:greet Fabien Ryan Bernhard

注意参数是有顺序的,如果定义了IS_ARRAY类型的参数,应该是最后一个定义,因为参数本身是用空格分隔的。

You can combine IS_ARRAY with REQUIRED and OPTIONAL like this:

$this
    // ...
    ->addArgument(
        'names',
        InputArgument::IS_ARRAY | InputArgument::REQUIRED,
        'Who do you want to greet (separate multiple names with a space)?'
    );

Unlike arguments, options are not ordered (meaning you can specify them in any order) and are specified with two dashes (e.g. –yell – you can also declare a one-letter shortcut that you can call with a single dash like -y). Options are always optional, and can be setup to accept a value (e.g. –dir=src) or simply as a boolean flag without a value (e.g. –yell).
与参数不同,选项是没有顺序的(意味着你可以随意指定)并请以为两个横杆开始(可以配置以一个横杆开头)。选项总是可选的,可以设置它接受一个值或没有值让其简单作为一个布尔值。

$this
    // ...
    ->addOption(
        'colors', #--colors
        'c', #选项的简称 -c
        InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
        'Which colors do you like?',
        array('blue', 'red')
    );

选项可用类型:

Option Value
InputOption::VALUE_IS_ARRAY This option accepts multiple values (e.g. --dir=/foo --dir=/bar)
InputOption::VALUE_NONE Do not accept input for this option (e.g. --yell)
InputOption::VALUE_REQUIRED This value is required (e.g. --iterations=5), the option itself is still optional
InputOption::VALUE_OPTIONAL This option may or may not have a value (e.g. --yell or --yell=loud)

在命令中运行命令:

protected function execute(InputInterface $input, OutputInterface $output)
{
    $command = $this->getApplication()->find('demo:greet');

    $arguments = array(
        'command' => 'demo:greet',
        'name'    => 'Fabien',
        '--yell'  => true,
    );

    $input = new ArrayInput($arguments);
    $returnCode = $command->run($input, $output);

    // ...
}

关于命令行参数与选项如果工作,可以对照http://symfony.cn/docs/components/console/console_arguments.html。Symfony的命令行工具包符合http://docopt.org/描述的规范。

Symfony组件 之 Finder

The Finder component finds files and directories via an intuitive fluent interface.(流式接口搜索文件和目录)

use Symfony\Component\Finder\Finder;

$finder = new Finder();
$finder->files()->in(__DIR__);

foreach ($finder as $file) {
    // Print the absolute path
    print $file->getRealpath()."\n";

    // Print the relative path to the file, omitting the filename
    print $file->getRelativePath()."\n";

    // Print the relative path to the file
    print $file->getRelativePathname()."\n";
}

Finder方法in()是唯一必须调用的方法,用来指定搜索哪个目录。Finder的方法都是流式接口,意思就是调用它的方法后返回Finder对象本身。

方法列表:

// 指定搜索路径
$finder->in(__DIR__);
$finder->files()->in(__DIR__)->in('/elsewhere');
$finder->in('src/Symfony/*/*/Resources');
$finder->in(__DIR__)->exclude('ruby'); #排除目录
$finder->ignoreUnreadableDirs()->in(__DIR__); #排除不可读目录
$finder->in('ftp://example.com/pub/'); #指定远程目录
#注册一个自定义流
use Symfony\Component\Finder\Finder;
$s3 = new \Zend_Service_Amazon_S3($key, $secret);
$s3->registerStreamWrapper("s3");
$finder = new Finder();
$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
foreach ($finder->in('s3://bucket-name') as $file) {
    // ... do something
    print $file->getFilename()."\n";
}

// 文件与目录
$finder->files(); #仅搜索文件
$finder->directories(); // 仅搜索目录
$finder->files()->followLinks(); // 跟踪符号链接
$finder->ignoreVCS(false); // 默认符号VCS文件

// 排序(排序需要先读取所有文件和目录)
$finder->sortByModifiedTime()
$finder->sortByChangedTime()
$finder->sortByAccessedTime()
$finder->sortByType()
$finder->sortByName()

// 文件名过滤
$finder->files()->name('*.php');
$finder->files()->name('/\.php$/'); #正则
$finder->files()->notName('*.rb');

// 文件内容过滤
$finder->files()->contains('lorem ipsum');
$finder->files()->contains('/lorem\s+ipsum$/i'); #正则
$finder->files()->notContains('dolor sit amet');

// 限定路径
$finder->path('some/special/dir');
$finder->path('foo/bar');
$finder->path('/^foo\/bar/');
$finder->notPath('other/dir');

// 文件大小过滤
$finder->files()->size('< 1.5K');
$finder->files()->size('>= 1K')->size('<= 2K');

//文件时间过滤(strtotime能用的格式都能用)
$finder->date('since yesterday');

//目录深度
$finder->depth('== 0');
$finder->depth('< 3');

//读取文件内容
use Symfony\Component\Finder\Finder;

$finder = new Finder();
$finder->files()->in(__DIR__);

foreach ($finder as $file) {
    $contents = $file->getContents();

    // ...
}