如果你在写一个大型应用,你会发现你始终在重复着相同的代码,一遍又一遍。也许你会自写工具来避免这种情况。为了将这种工具复用在不同的程序场合,它们必须被配置到合乎代码结构之程度。全新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或是下面的评论让我们知道你在自己的项目中使用(或计划使用)它!