Loading models with NotORM and Dependency Injection

Since Nette 2.0.5 we can load models via Dependency Injection container (in config files) and in presenter we can use autowire method inject. Pros of DI container approach is transparency.

This tutorial uses Nette 2.0.5 a NotORM. Are you looking for dibi or Nette Database (in Czech)?

Configuring environment

config.neon

First we set up required parameters and register database service. Then we can add particular models as services.

parameters:
    # parameters to connect to database
    database:
        driver: mysql
        hostn: local
        username: root
        password:
        dbname: database

nette:
    # required to activate debug panel for NotORM
    database:
        default:
            dsn: "%database.driver%:host=%database.host%;dbname=%database.dbname%"
            user: %database.username%
            password: %database.password%

services:
    dbcache: NotORM_Cache_Include("%tempDir%/notorm.cache")
    database: NotORM(@\PDO, null, @dbcache)

    # our models
    userModel: Models\User
    articleModel: Models\Article

Models

To preserve KISS approach and not to repeat same code in all models, we have to create abstract parent class. We define basic methods and database connection in it.

BaseModel.php

namespace Models;

use Nette;
use NotORM;

abstract class Base
{
    /** @var NotORM */
    protected $db;

    /** @var string */
    private $tableName;


    /**
     * @param NotORM
     */
    public function __construct(NotORM $notorm)
    {
        $this->db = $notorm;
        $this->tableName = $this->tableNameByClass(get_class($this));
    }


    /**
     * Determine table by class name
     * @param string
     * @return string
     * @result:Pages => pages, ArticleTag => article_tag
     */
    private function tableNameByClass($className)
    {
        $tableName = explode("\\", $className);
        $tableName = lcfirst(array_pop($tableName));

        $replace = []; // A => _a
        foreach(range("A", "Z") as $letter) {
            $replace[$letter] = "_".strtolower($letter);
        }

        return strtr($tableName, $replace);
    }

    // own methods: insert, update, delete, count,
    // fetchSingle, fetchPairs,  etc.

}

Particular model

Every model extending Models\Base contains all it's methods. We may use them and also add some specific for this model only.

namespace Models;

class User extends Base
{
    /**
     * Return user by name
     * @param string
     */
    public function getByName($name)
    {
        return $this->db->user("name", $name)->fetch();
    }
}

Let's inject

We have already registered models as services. To avoid $this->context we have to inject to presenters.

To avaid “inject hell” we will load all frequently used models in BasePresenter to properties with protected access.

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    /** @var Models\User */
    protected $userModel;


    /**
     * Inject all models that are expected to be used in most of BasePresenter's ancestors
     * @param Models\User
     */
    public function injectBaseModels(Models\User $userModel)
    {
        $this->userModel = $userModel;
    }

}

Use in practice

class ArticlePresenter extends BasePresenter
{
    /** @var Models\Article */
    private $articleModel;


    /**
     * @param Models\Article
     */
    public function injectModels(Models\Article $articleModel)
    {
        $this->articleModel = $articleModel;
    }


    /**
     * @param int
     */
    public function actionDetail($id)
    {
        $this->template->article = $article = $this->articleModel->fetch($id)
        $this->template->articleAuthor = $this->userModel->fetch($article["user_id"]);
    }

}

Easy, right?