Egoblog.cz - Petr Kobelka

Nette, DataMapper aneb nechceme už ORM!

Domain name object a DataMapper jsou moderním trendem ve vývoji software. Doby, kdy jste entity dělali přesně podle databáze jsou již dávno pryč.


PHP | Komentáře (0) | Shlédnuto 1302 × | Vloženo: 17. prosince 2015

Co je to ORM?

ORM (Object relation mapping, nebo česky objěktově relační mapování) je technika vývoje software, která zajišťuje automatickou konverzi informací mezi relační databází a objektovým modelem. Jak už název sám napovídá, je třeba v technice ORM objektový model přizpůsobovat databázovému návrhu.

Co je to Data Mapper a Domain Name Object

Návrhový vzor DataMapper v kombinaci s DomainNameObject popsal Martin Fowler [http://martinfowler.com/eaaCatalog/dataMapper.html] na svém webu a ve své knize Patterns of Enterprise Application Architecture - mimochodem doporučuji jako základní bibli návrhových vzorů. Jedná se o implementaci, kdy enetity nerjsou závislé na databázové reprezentaci, jako je tomu u ORM. Někdy se takové technice říká NotORM.

A layer of Mappers (473) that moves data between objects and a database while keeping them independent of each other and the mapper itself.

Diagram převzat z http://martinfowler.com/eaaCatalog/dataMapper.html. Ukazuje jak funguje vztah mezi DataMapper a objektem (domain name object).

Pokud k následně k entitám DomainNameObject ještě přidáte vazby na mappery závislých entit, získáte přístup k prakticky skoro "dokonalému" systému tvorby software, který vám umožní dělat kódem spoustu zajímavých věcí.

Obrovskou výhodou mapperů je to, že nejsou vázáni na relační databázi, nebo na konkrétní úložiště. Stačí jen vyměni obsah metody a najednou se vše směruje např do filesystému, nebo na ftp. Možností je mnoho a jsou jednoduše dosažitelné.

Abych jen nepsal teorii, ukážu vám jednoduchý příklad implementace.

Praktický příklad použití data mapperu v Nette a vykreslení v Latte

Nejdříve ukázka velmi jednoduchého zdrojového kódu. Nehledejte v něm žádné záludnosti, žádné tam nejsou.

/**
 *
 * abstraktni trida mapperu, ktera ma zakladni vlastnosti a metody spolecne pro vsechny mappery
 *
 */
abstract class mapper
{
  /** @var array schema modelu */
  protected $schema = [];
  /** @var \Nette\Database\Context */
  protected $db;
  /**
   * @param \Nette\Database\Context $db
   */
  public function __construct(\Nette\Database\Context $db)
  {
    $this->db = $db;
  }
}

/**
 * trida blogoveho clanku
 */
class post
{
  /** @var int */
  private $id;
  /** @var string */
  private $title;
  /** @var int */
  private $author_id;
  /** @var \author */
  peivate $author = null;
  /** @var \authorMapper */
  peivate $authorMapper = null;
  /**
   * @param int $id
   */
  public function setId($id)
  {
    $this->id = $id;
  }
  /**
   * @return int
   */
  public function getId()
  {
    return $id;
  }
    /** @var string  */
  private $title = NULL;
  /** @var string  */
  private $author = NULL;
  /**
   * setter title
   *
   * @param string $title
   * @access public
   */
  public function setTitle($title)
  {
    if (!empty($title))
    {
      $this->title = $title;
    }
  }
  /**
   * getter title
   *
   * @access public
   * @return string
   */
  public function getTitle()
  {
    return $this->title;
  }
  /**
   * getter author
   *
   * @access public
   * @return string
   */
  public function getAuthor()
  {
    if ($this->author === null && $this->authorMapper !== null)
    {
      $this->author = $this->authorMapper->find($this->author_id);
    }
    return $this->author;
  }
  /**
   *
   */
  public function setAuthorMapper(\authorMapper $mapper)
  {
    $this->authorMapper = $mapper;
  }

}

/**
 * trida autora clanku
 */
class author
{
  /** @var int  */
  private $id = null;
  /** @var string  */
  private $name = null;
  /**
   * setter id
   *
   * @param int $id
   * @access public
   */
  public function setId($id)
  {
    if (is_numeric($id))
    {
      $this->id = $id;
    }
  }
  /**
   * getter id
   *
   * @access public
   * @return int
   */
  public function getId()
  {
    return $this->id;
  }
  /**
   * setter name
   *
   * @param string $name
   * @access public
   */
  public function setName($name)
  {
    if (!empty($name))
    {
      $this->name = $name;
    }
  }
  /**
   * getter name
   *
   * @access public
   * @return string
   */
  public function getName()
  {
    return $this->name;
  }
}

/**
 * mapper blogoveho clanku
 */

class postMapper extends mapper
{
  /** @const TB name */
  const TB = 'post';
  /**
   * @param \Nette\Database\Context $db
   */
  public function __construct(\Nette\Database\Context $db)
  {
    parent::__construct($db);
    $this->scheme = ['id','title'];
  }
  /**
   *
   * @return \post
   */
  public function find($id)
  {
    return $this->create($id);
  }
  /**
   * tovarnicka pro tvorbu a naplneni modelu
   * return \post
   */
  private function create ($id)
  {
    $post = new post();
    //cerna magie, ktere namapuje podle SCHEME data do objektu post
    //.....

    //zde pridame ke clanku mapper autoru
    //diky nemu muzeme data o autorovi clanku vytahnout az kdyz je
    //potrebujeme a presto je mame vzdy k dispozici
    //tohle je cerna super magie ^_^
    $post->setAuthorMapper(new \authorMapper($this->db));

  }
  /**
   * @param \post
   */
  public function save (\post $post)
  {
    //predame mapperu objekt, ktery jej ulozi
  }
}

/**
 * mapper autora
 */
class authorMapper extends mapper
{
  /** @const TB name */
  const TB = 'author';
  /**
   * @param \Nette\Database\Context $db
   */
  public function __construct(\Nette\Database\Context $db)
  {
    parent::__construct($db);
    $this->scheme = ['id','name'];
  }
  /**
   *
   * @return author
   */
  public function find($id)
  {
    return $this->create($id);
  }
  /**
   * tovarnicka pro tvorbu a naplneni modelu
   * return \author
   */
  private function create ($id)
  {
    $author = new author();
    //cerna magie, ktere namapuje podle SCHEME data do objektu post
    //.....
    
    return $author;
  }
  /**
   * @param \author
   */
  public function save (\author $author)
  {
    //predame mapperu objekt, ktery jej ulozi
  }
}

Co jsme to vlastně napsali za kód?

Jde o velmi jednoduchou a naivní implementaci blogového článku a odkazu na jeho autora. V prvním případě je třeba se podívat na třídu mapper. Ta slouží jako předek všem mapperům.

Dále máme třídy post, která reprezentuje článek blogu a autor, která reprezentuje jeho autora. Mappery postMapper a autorMapper, které se starají o synchronizaci dat mezi modelem a databází. Super kouzlem mapperu je to, že se nemusí jednat jen o databázi. Může to být filesystem, ftp, ...

Když bude chtít v šabloně (uvažujme v rovině latte a Nette) vypsat titulek postu, provedu to následovně:

$post->getTitle()

a co když budu chtít vypsat pod titulek autora? Udělám následující

$post->getAuthor()->getName()

Jednoduché, přehledné, efektivní. Co více si přát.

A proč to funguje? Protože v mapperu jsme si do objektu přidali závislost autorMapper a v metodě getAuthor() si zavoláme metodu, která nám vrátí autora postu. Data máme vždy k dispozici ale jejich získání funguje až v době prvního volání metody = můžeme učetřit spoustu času a paměti = větší rychlost zpracování a vykreslení stránky.

Petr Kobelka | Egoblog.cz | Tvorba www stránek - www.petrkobelka.cz

Petr Kobelka
Autor je zkušeným web developerem a programátorem s více než 10 letými zkušenostmi. Pracuje jako programátor pro známou Olomouckou společnost zabývající se tvorbou internetových a intranetových řešení. Spolu se zaměstnáním pracuje na volné noze a zabývá se tvorbou internetových stránek. Ve volném čase rád fotí, jezdí na kole, plave a cestuje.

Komentáře

E-mail je potřeba pouze pro vygenerování Gravataru!

Oups, žádné komentáře? Buďtě první !!!

Blog píše Petr Kobelka

Petr Kobelka - egoblog.cz

Žádám všechny, kteří mají zájem vkládat komentáře, aby se řídili pravidly NETikety. Komentáře, porušující tato pravidla můžou být bez varování smazány.