如果你在写一个大型应用,你会发现你始终在重复着相同的代码,一遍又一遍。也许你会自写工具来避免这种情况。为了将这种工具复用在不同的程序场合,它们必须被配置到合乎代码结构之程度。全新PropertyAccess component祝你实现上述过程。
注意本组件的代码并非全新。它们早已在Form组件中存在许久。我们决定提取代码令其成为全新组件,是因为很多人觉得它有用,我希望你也这样认为。(译注:本文作者Bernhard Schussek ,网名@webmozart,是Symfony重型自动组件“表单”的项目带头人)
我们先看个简单的例子:数据格子(data grid)。假设你已经建立了一个DataGrid类,为的是将可循环的数组转换成表格输出。代码是很简单的:
| 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 | class DataGrid
{
    private $rows = array();
 
    private $columns = array();
 
    public function __construct($items, array $columns)
    {
        if (!is_array($items) && !$items instanceof \Traversable) {
            throw new \InvalidArgumentException(
                'The grid items should be an array or a \Traversable.'
            );
        }
 
        // Let keys and values contain the column name 让数组的键值包含列名
        $columns = array_combine($columns, $columns);
 
        // Turn values into human readable names 将值转换成易读的名称
        $this->columns = array_map(function ($column) {
            return ucfirst(trim(preg_replace(
                // (1) Replace special chars by spaces 去除空格
                // (2) Insert spaces between lower-case and upper-case 小写和大写之间插入空格
                array('/[_\W]+/', '/([a-z])([A-Z])/'),
                array(' ', '$1 $2'),
                $column
            )));
        }, $columns);
 
        // Store row data
        foreach ($items as $item) {
            $this->rows[] = array_intersect_key($item, $columns);
        }
    }
 
    public function getColumns()
    {
        return $this->columns;
    }
 
    public function getRows()
    {
        return $this->rows;
    }
} | 
在控制器中,你可直接将简单的嵌套数组传给grid:
| 1 2 3 4 5 6 7 8 | $data = array(
    array('id' => 1, 'firstName' => 'Paul', 'lastName' => 'Stanley'),
    array('id' => 2, 'firstName' => 'Gene', 'lastName' => 'Simmons'),
    array('id' => 3, 'firstName' => 'Ace', 'lastName' => 'Frehley'),
    array('id' => 4, 'firstName' => 'Peter', 'lastName' => 'Criss'),
);
 
$grid = new DataGrid($data, array('firstName', 'lastName')); | 
把格子显示在模板中也不难:
类似这样的操作,又好又容易,但它有一个主要局限:格子只能与数组配合工作,不能与对象一起。而你经常使用的domain model却是对象,不是吗?
让我们看看怎样利用PropertyAccess组件来强化格子。下面代码是略微修改过的DataGrid类:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | use Symfony\Component\PropertyAccess\PropertyAccess;
 
class DataGrid
{
    // ...
 
    public function __construct($items, array $columns)
    {
        // ...
 
        $accessor = PropertyAccess::getPropertyAccessor();
 
        // Store row data 存储行数据
        foreach ($items as $item) {
            $this->rows[] = array_map(function ($path) use ($item, $accessor) {
                return $accessor->getValue($item, $path);
            }, $columns);
        }
    }
 
    // ...
} | 
还需要改造一下controller才能让代码像前面的例子那样工作:
| 1 | $grid = new DataGrid($data, array('[firstName]', '[lastName]')); | 
代码修改之后,老的数据仍然正常运行,发生了什么?
你是否注意到传递到DataGrid对象的被方括号括起来的参数?它们被称为PropertyPath。Property Paths可以有不同的代号写法:
| Path | Equivalent to(相当于) | 
|---|---|
| [index] | $data['index'] | 
| [index][sub] | $data['index']['sub'] | 
| prop | $data->getProp(),$data->isProp(),$data->hasProp(),$data->__get('prop')or$data->prop, whichever is found first | 
| prop.sub | $data->getProp()->getSub(),$data->getProp()->isSub()etc. | 
换句话说,property paths配置的是“如何去访问数据结构”,不管是数组,还是对象。前例的格子将作如下“翻译”:
| 1 2 3 4 | $accessor = PropertyAccess::getPropertyAccessor();
 
$row[] = $accessor->getValue($item, '[firstName]');
$row[] = $accessor->getValue($item, '[lastName]'); | 
等同于:
| 1 2 | $row[] = $item['firstName'];
$row[] = $item['lastName']; | 
听起来较为抽象,但它可以在以下例程之后立即变明确。我们先对音乐家赋与不同的乐器:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 
不需要变动DataGrid,我们就能添加一列,用于显示嵌套数组中的值。很酷不是吗?
另外一例,我们引入Musician音乐家类:
| 1 2 3 4 5 6 7 8 9 10 11 12 | class Musician
{
    public function __construct($id, $firstName, $lastName, array $instrument)
    {
        // ...
    }
 
    public function getId() { /* ... */ }
    public function getFirstName() { /* ... */ }
    public function getLastName() { /* ... */ }
    public function getInstrument() { /* ... */ }
} | 
版面所限,我们留空具体代码,但是我认为这个例子可以非常好的自我解释。我们现在改变格子的构造器,读取Musician实例:
又一次,我们没有改变DataGrid类本身。通过使用没有方括号的标识,格子系统就能够访问getters来填充自己的单元格:
| 1 2 3 | $row[] = $item->getFirstName();
$row[] = $item->getLastName();
$row[] = $item->getInstrument()['name']; | 
我们现在可以把乐器instrument放到一个类中,再把instrument[name]改为instrument.name。具体内容留给各位当作练习。
最后,通过使用PropertyAccessor,你不光可以从结构化数据中读取,还可以写入数据进去。例如:
对于性能追求者要提醒的一点:在未来的某个Symfony版本中,PropertyAccess组件将提供一个“代码生成”的layer,用于提升PropertyAccessor类的速度(唯快不破?)。如果你想让你的代码向上兼容的话,确保使用一个全局accessor并将其注入到你需要的任何地方:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
 
class DataGrid
{
    // ...
 
    public function __construct($items, array $columns,
            PropertyAccessorInterface $accessor)
    {
        // ...
    }
 
    // ...
} | 
然后是控制器:
| 1 2 3 4 5 6 | use Symfony\Component\PropertyAccess\PropertyAccess;
 
// Globally unique PropertyAccessor instance 全局accessor实例
$accessor = PropertyAccess::getPropertyAccessor();
 
$grid = new DataGrid($data, array(/* ... */), $accessor); | 
就是这些了!希望你对这个小小的组件有兴趣!我是认真的,希望通过twitter或是下面的评论让我们知道你在自己的项目中使用(或计划使用)它!
 4.2翻译中
                     4.2翻译中

 
                     
                    