使用Zend Framework 2将数据库列映射到域模型

时间:2014-02-10 21:50:49

标签: design-patterns zend-framework2 solid-principles poeaa

我一直在进行大量研究,无法找到这个看似热门问题的答案。

我有一个包含一些属性的域模型 - 让我们说firstNamelastName - 但在我的数据库中,我将它们存储为fnamelname

根据好PoEAA的规则,模型应该不知道它是如何存储的,如果有的话。所以我的问题是,这些字段的映射在哪里?

查看实现Zend\Stdlib\Hydrator\AbstractHyrdrator界面的现有类的来源,我没有看到任何提供映射功能的类。

有没有人看到过一个没有污染模型的干净解决方案?

更新:更简单的解决方案

自从我提出这个问题以来已经有一段时间了,但是这里有一个非常简单的解决方案,可以在没有太多开销的情况下将列映射到对象。使用ZF2的内置命名策略功能,可以实现所需的效果。

<?php
use Zend\Stdlib\Hydrator\NamingStrategy\UnderscoreNamingStrategy;
use Zend\Stdlib\Hydrator\ObjectProperty;

class Person {
    public $fname;
    public $lastName;
}
class MyNamingStrategy extends UnderscoreNamingStrategy {
    protected $map = [
        'fname' => 'first_name'
    ];

    public function hydrate($name) {
        $map = array_flip($this->map);
        if(isset($map[$name])) {
            return $map[$name];
        }
        return lcfirst(parent::hydrate($name));
    }

    public function extract($name) {
        $map = $this->map;
        if(isset($map[$name])) {
            return $map[$name];
        }
        return parent::extract($name);
    }


}
$p = new Person();
$h = new ObjectProperty();
$h->setNamingStrategy(new MyNamingStrategy());
$h->hydrate([
    'first_name' => 'john',
    'last_name' => 'Doe'
],$p);
var_dump($p);
var_dump($h->extract($p));

输出:

    object(Person)[4]
      public 'fname' => string 'john' (length=4)
      public 'lastName' => string 'Doe' (length=3)

    array (size=2)
      'first_name' => string 'john' (length=4)
      'last_name' => string 'Doe' (length=3)

2 个答案:

答案 0 :(得分:2)

您是否看过Data Mapper设计模式?我以前在PHP(几个ZF1应用程序)中实现了这一点,我认为它可以满足您的需求。您有责任了解数据库到映射器层,而模型(实体)对象不知道它的任何信息。它实现起来非常简单,您不需要像Doctrine那样使用更复杂的ORM。

编辑:这是Fowler's description of the pattern

这是一个blog post with a small example of how you could implement it in PHP

答案 1 :(得分:1)

使用ORM

我会使用任何ORM,它允许您配置模型类之外的映射。

就个人而言,我喜欢doctrine。我使用docbloc annotations通过XML来映射字段,因为它更容易,但我同意有关如何存储数据的信息不应该在模型中。幸运的是,您有YAMLDoctrineORMModule数据映射选项。

例如,如果你的模型中有这个实体:

<?php
class Message
{
    private $id;
    private $text;
    private $postedAt;
}

您可以在XML中配置此映射

<doctrine-mapping>
  <entity name="Message"  table="message">
    <field name="id" type="integer" />
    <field name="text" length="140" />
    <field name="postedAt" column="posted_at" type="datetime" />
  </entity>
</doctrine-mapping>

因此,映射在配置文件中,因此模型本身不知道它是如何持久化的。

你有{{3}}模块可以轻松地将Doctrine集成到ZF2中:

如果您不想使用ORM

您可以在配置文件中创建映射。如果您使用表网关模式,那么您可以将该配置注入Table类。我将举一个例子来调整ZF2骨架应用程序中Album模块的示例(您可以根据实际场景进行调整,我认为这比我必须研究您的系统更容易)

  1. 您可以在module.config.php文件中创建映射。将类名映射到表,并将类中的每个字段映射到列名

    'mappings' => array (
    
    'AlbumTable' => array ( 
    
            'table'=> 'TABLENAME',
    
            'fields' => array (
                'field1_en_clase' => 'field1_en_db',
                'field2_en_clase' => 'field2_en_db',
    
                ) 
        ),
    
        'OTHER_ENTITY' =>   array ( 
    
                    'table'=> 'TABLENAME',
    
                    'fields' => array (
                            'field1_en_clase' => 'field1_en_db',
                            'field2_en_clase' => 'field2_en_db',
    
    
                    )
    ), 
        //...   
    );
    
  2. 然后,当您在Module.php中配置服务时,将该信息发送到需要它们的类:

    class Module
    {
    
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Album\Model\AlbumTable' =>  function($sm) {
    
                    $config = $sm->get('AlbumTableGateway');
                        $tableGateway = $sm->get('AlbumTableGateway');
    
                                    //when you create the AlbumTable class, you send the field mappings
                                    $table = new AlbumTable($tableGateway, $config ['mappings']['AlbumTable']['fields']);
                            return $table;
                },
    
                'AlbumTableGateway' => function ($sm) {
    
                    $config = $sm->get('AlbumTableGateway');
                    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                    $resultSetPrototype = new ResultSet();
                    $resultSetPrototype->setArrayObjectPrototype(new Album());
    
                                     //when you create the TableGateway, you also take the table name from the config.
                    return new TableGateway($config ['mappings']['AlbumTable']['table'] , $dbAdapter, null, $resultSetPrototype);
                      },
                ),
        );
    }
    }
    
  3. 然后,您只需要调整您的AlbumTable类,以便它接收并使用该映射。

    class AlbumTable
    {
    protected $tableGateway, $mappings;
    
    //First, in the constructor, you receive and save the mappings
    
        public function __construct(TableGateway $tableGateway, $mappings)
    {
        $this->tableGateway = $tableGateway;
        $this->mappings = $mappings;
    }
    
    
    //then in every function where you before hardcoded the column names, now you take it from the mappings, for instance:
    
    public function saveAlbum(Album $album)
    {
    
            /*
             here you had
            $data = array(
                 'artist'  => $album->artist,
                 'title'   => $album->title,
                   );
    
             */
            // now you change it to:
             $data = array(
                $mappings['artist'] => $album->artist,
                $mappings['title']  => $album->title,
        );
    
       $id = (int) $album->id;
            if ($id == 0) {
                $this->tableGateway->insert($data);
            } else {
                if ($this->getAlbum($id)) {
                    $this->tableGateway->update($data, array('id' => $id));
                } else {
                    throw new \Exception('Album id does not exist');
                }
            }
     }
    
  4. 在列名称被编码的每个地方都是如此。

    我认为您可以轻松地将其调整到您的系统中。