Вопрос

I have been doing a fair amount of research and cannot to find an answer to this seemingly popular question.

I have a domain model that contains some properties-let's say firstName and lastName-but in my database I store them as fname and lname.

According to the rules of good PoEAA, the model should have no knowledge of how it is stored, if at all. So my question is, where does the mapping of these fields go?

Looking through the source of the existing classes that implement the Zend\Stdlib\Hydrator\AbstractHyrdrator interface I don't see any of them that offer mapping functionality.

Has anybody seen a clean solution for this that doesn't pollute the model?

UPDATE: Better Simpler Solution

It's been awhile since I asked this question but here would be a very simple solution to map columns to the object without much overhead. Using the built-in naming strategy capabilities of ZF2 the desired affect can be achieved.

<?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));

Outputs:

    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

Using an ORM

I would use any ORM, that allows you to configure the mappings outside of the model classes.

Personally, i like doctrine. I use to map the fields with the database by means of the docbloc annotations since it is easier, but I agree that the information about how the data is stored should not be inside the model. Luckily you have the XML and YAML options for data mapping.

For instance, if you have this Entity en your model:

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

You can configure this mapping in 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>

So, the mappings are in configuration files, so the model itself is not aware of how it is persisted.

You have the DoctrineORMModule module to easily integrate Doctrine into ZF2:

If you don't want to use an ORM

You can create the mappings in the config files. Having that you use the table gateway pattern, after that, you can inject that config to the Table class. I will give you an example adapting the example of the Album module from the ZF2 skeleton application (you can adapt it to your real scenario, i think this is easy than if i have to study your system)

  1. You create the mappings in the module.config.php file. You map the classname to a table, and every field on the class, to a column name

    '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. Then, when you configure the service in the Module.php, you send that information to the classes that need them:

    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. Then, you just need to adapt your AlbumTable class, so it receives and uses that mappings.

    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');
                }
            }
     }
    

And the same everywhere a column name was harcoded.

I think you can easily adapt this to your system.

Другие советы

Have you looked at the Data Mapper design pattern? I've implemented this before in PHP (several ZF1 applications) and I think it does what you're looking for. You give the responsibility of knowing about the database to the mapper layer without the model (entity) objects knowing anything about it. It's pretty simple to implement and you don't need to use a more complex ORM like Doctrine.

Edit: here's Fowler's description of the pattern

And here's a blog post with a small example of how you could implement it in PHP

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top