Обучение Zend Framework после Magento: модели

Я работал более года с Magento и узнал его достаточно хорошо. Теперь я хочу изучать Зенд, и я застрял с моделями.

Я привык иметь сущности и коллекцию сущностей в Magento, и, вероятно, я захочу использовать Zend_Db_Table, Zend_Db_Table_Row и/или Zend_Db_Table_Rowset. Что меня смущает, так это роль каждого класса.

Я знаю, что могу расширить каждый класс, и я понимаю, что в моем Product_Table класс (который расширяет Zend_Db_Table_Abstract) возможно иметь частное методы, которые скажут Zend, какие классы использовать для строк и наборов строк, однако я не чувствую себя комфортно с ним.

имея этот код в Magento:

Пример 1

// I understand that maybe I'll use the `new` keyword instead
// Mage::getModel() is only for exemplification
$product = Mage::getModel('catalog/product');
$product->setName('product name');
$product->setPrice(20);
$product->save();

if($id = $product->getId()){
    echo 'Product saved with id' . $id;
}
else{
    echo 'Error saving product';
}

Пример 2

$collection = Mage::getModel('catalog/product')->getCollection();
// this is the limit, I'm ok with other method's name
$collection->setPageSize(10);
$collection->load()

foreach($collection as $product){
    echo $product->getName() . ' costs ' . $product->getPrice() . PHP_EOL;
}

как я могу реализовать что-то подобное в Zend Framework? Альтернативно, если это действительно плохая идея, каковы лучшие практики для реализации модели в Zend Рамки?

спасибо

4 ответов


на Zend команда, как упоминалось в другом месте, думает иначе о слое модели, чем большинство других создателей PHP Framework. Их текущие мысли о "лучшем" способе использования их необработанных инструментов для предоставления модели сущности, поддерживаемой базой данных, можно найти в быстрый старт руководство.

тем не менее, решение большинства людей для моделей в Zend Framework-это bootstrapping доктрина.


вот как я, лично, реализации моделей. Я буду использовать реальный пример жизни: my User модель.

всякий раз, когда я создаю модель, я использую два файла и два класса: сама модель (например,Application_Model_User) и объекта сопоставления (например,Application_Model_UserMapper). Очевидно, что сама модель содержит данные, методы сохранения, удаления, изменения и т. д. Объект маппера содержит методы для извлечения модельных объектов, поиск объектов и т. д.

вот первые несколько строк пользователя модель:

class Application_Model_User {

    protected $_id;
    protected $_name;
    protected $_passHash;
    protected $_role;
    protected $_fullName;
    protected $_email;
    protected $_created;
    protected $_salt;

    // End protected properties

для каждого свойства у меня есть метод getter и setter. Пример id:

/* id */

public function getId() {
    return $this->_id;
}

public function setId($value) {
    $this->_id = (int) $value;
    return $this;
}

Я также использую некоторые стандартные "магические методы" для разоблачения публичных геттеров и сеттеров (внизу каждой модели):

public function __set($name, $value) {
    $method = 'set' . $name;
    if (('mapper' == $name) || !method_exists($this, $method)) {
        throw new Exception('Invalid user property');
    }
    $this->$method($value);
}

public function __get($name) {
    $method = 'get' . $name;
    if (('mapper' == $name) || !method_exists($this, $method)) {
        throw new Exception('Invalid user property');
    }
    return $this->$method();
}

public function setOptions(array $options) {
    $methods = get_class_methods($this);
    foreach ($options as $key => $value) {
        $method = 'set' . ucfirst($key);
        if (in_array($method, $methods)) {
            $this->$method($value);
        }
    }
    return $this;
}

пример save способ:

Я проверяю внутри save() метод, используя исключения, когда информация не проходит проверку.

public function save() {        
    // Validate username
    if (preg_match("/^[a-zA-Z](\w{6,15})$/", $this->_name) === 0) {
        throw new Application_Exception_UserInfoInvalid();
    }

    // etc.

    $db = Zend_Registry::get("db");

    // Below, I would check if $this->_id is null. If it is, then we need to "insert" the data into the database. If it isn't, we need to "update" the data. Use $db->insert() or $db->update(). If $this->_id is null, I might also initialize some fields like 'created' or 'salt'.
}

для объекта mapper у меня есть по крайней мере два методы: метод, который возвращает объект запроса для выбора объектов, и тот, который выполняет запрос, инициализирует и возвращает объекты. Я использую это, чтобы я мог манипулировать запросом в моем контроллере для сортировки и фильтрации.

редактировать

как я сказал в своих комментариях, этот пост:http://weierophinney.net/matthew/archives/202-Model-Infrastructure.html было вдохновением для моей текущей реализации модели.

Дополнительные параметры

вы также можете использовать Zend_Form чтобы сделать проверку, вместо того, чтобы катить свой собственный:http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html. Мне лично не нравится этот вариант, так как я думаю, что Zend_Form - это неудобно и трудно контролировать.

когда большинство людей впервые изучают Zend Framework, они учатся подклассу Zend_Db связанных классов. Вот статья, которая демонстрирует это: http://akrabat.com/zend-framework/on-models-in-a-zend-framework-application/

Я упомянул, что мне это не нравится. Вот несколько причин почему:

  • трудно создавать модели, которые включают производные/вычисляемые поля (т. е. данных из других таблиц)
  • я обнаружил, что невозможно включить контроль доступа (заполненный из моей базы данных)
  • мне нравится иметь полный контроль над моей модели

правка 2

для вашего второго примера: вы можете использовать фабричный метод zend_paginator для этого. Я упомянул, что в вашей оболочке вы создаете метод, который возвращает объект запроса базы данных для выбора объектов. Вот мой упрощенный, но рабочий пользователь mapper:

class Application_Model_UserMapper {

    public function generateSelect() {
        $db = Zend_Registry::get("db");

        $selectWhat = array(
            "users_id",
            "name",
            "role",
            "full_name",
            "email",
            "DATE_FORMAT(created, '%M %e, %Y at %l:%i:%s %p') as created",
            "salt",
            "passhash"
        );

        return $db->select()->from(array("u" => "users"), $selectWhat);
    }


    public function fetchFromSelect($select) {
        $rows = $select->query()->fetchAll();
        $results = array();

        foreach ($rows as $row) {
            $user = new Application_Model_User();

            $user->setOptions(array(
                "id" => $row["users_id"],
                "name" => $row["name"],
                "role" => $row["role"],
                "fullName" => $row["full_name"],
                "email" => $row["email"],
                "created" => $row["created"],
                "salt" => $row["salt"],
                "passHash" => $row["passhash"]
            ));

            $results[] = $user;
        }

        return $results;
    }

}

чтобы обработать paginator, я пишу пользовательский плагин Paginator и сохраняю его в library/Application/Paginator/Adapter/Users.php. Убедитесь, что у вас есть appnamespace и autoloaderNamespaces[] настройки правильно в приложение.ini. Вот плагин:

class Application_Paginator_Adapter_Users extends Zend_Paginator_Adapter_DbSelect {
    public function getItems($offset, $itemCountPerPage) {
        // Simply inject the limit clause and return the result set
        $this->_select->limit($itemCountPerPage, $offset);
        $userMapper = new Application_Model_UserMapper();
        return $userMapper->fetchFromSelect($this->_select);
    }
}

в моем контроллере:

// Get the base select statement
$userMapper = new Application_Model_UserMapper();
$select = $userMapper->generateSelect();

// Create our custom paginator instance
$paginator = new Zend_Paginator(new Application_Paginator_Adapter_Users($select));

// Set the current page of results and per page count
$paginator->setCurrentPageNumber($this->_request->getParam("page"));
$paginator->setItemCountPerPage(25);

$this->view->usersPaginator = $paginator;

затем визуализируйте пагинатор в скрипте просмотра.


Я делаю что-то похожее на способ SimpleCode. Мой стиль происходит от Pádraic Brady. У него есть несколько сообщений в блоге, но лучшим и самым быстрым ресурсом его является онлайн-книга, которую он написал:выжить в глубоком конце!. Эта ссылка должна привести вас прямо к его главе о моделях, картах данных и других интересных лакомствах, таких как ленивая загрузка. Идея заключается в следующем:

у вас есть сущности, такие как пользователь со свойствами, определенными в массиве. Все ваши сущности расширяют абстрактный класс с помощью magic getter / setters, которые получают или обновляют этот массив.

class User extends Entity
{
    protected $_data = array(
        'user_id' => 0,
        'first_name' => null,
        'last_name' => null
    );
}

class Car extends Entity
{
    protected $_data = array(
        'car_id' => 0,
        'make' => null,
        'model' => null
    );
}

class Entity
{
    public function __construct($data)
    {
        if(is_array($data))
        {
            $this->setOptions($data);
        }
    }

    public function __get($key)
    {
        if(array_key_exists($key, $this->_data)
        {
            return $this->_data[$key];
        }

        throw new Exception("Key {$key} not found.");
    }

    public function __set($key, $value)
    {
        if(array_key_exists($key, $this->_data))
        {
            $this->_data[$key] = $value;
        }

        throw new Exception("Key {$key} not found.");
    }

    public function setOptions($data)
    {
        if(is_array($data))
        {   
            foreach($data as $key => $value)
            {
                $this->__set($key, $value);
            }
        }
    }

    public function toArray()
    {
        return $this->_data;
    }
}

$user = new User();
$user->first_name = 'Joey';
$user->last_name = 'Rivera';

echo $user->first_name; // Joey

$car = new Car(array('make' => 'chevy', 'model' => 'corvette'));
echo $car->model; // corvette

Сопоставители данных для меня отделены от сущностей, их задача-сделать CRUD (создать, прочитать, обновить и удалить) в БД. Итак, если нам нужно загрузить объект из БД, я вызываю сопоставитель, специфичный для этого объекта, чтобы загрузить его. Например:

<?php

class UserMapper
{
    $_db_table_name = 'UserTable';
    $_model_name = 'User';

    public function find($id)
    {
        // validate id first

        $table = new $this->_db_table_name();
        $rows = $table->find($id);

        // make sure you get data

        $row = $rows[0]; // pretty sure it returns a collection even if you search for one id
        $user = new $this->_model_name($row); // this works if the naming convention matches the user and db table
        //else
        $user = new $this->_model_name();

        foreach($row as $key => $value)
        {
            $user->$key = $value;
        }

        return $user;
    }
}

$mapper = new UserMapper();
$user = $mapper->find(1); // assuming the user in the previous example was id 1
echo $user->first_name; // Joey

этот код должен дать представление о том, как создать код таким образом. Я не тестировал это, поэтому я, возможно, создал некоторые опечатки / синтаксические ошибки, как я написал. Как и другие упоминали, Zend позволяет вам делать то, что вы хотите с моделями, нет правильного и неправильного, это действительно зависит от вас. Обычно я создаю класс таблицы для каждой таблицы в БД, с которой хочу работать. Поэтому, если у меня есть таблица пользователей, у меня обычно есть объект User, User Mapper и класс User Table. UserTable расширит Zend_Db_Table_Abstract и в зависимости от того, что я делаю, не будет иметь никаких методов внутри или иногда я буду перезаписывать методы, такие как вставить или удалить в зависимости от моих потребностей. Я заканчиваю с большим количеством файлов, но я считаю, что разделение кода делает его намного проще быстро добраться туда, где мне нужно, чтобы добавить больше функциональности или исправить ошибку, так как я знаю, где все части кода будут.

надеюсь, что это помогает.


Структура Папок

application
--models
----DbTable
------User.php
--controllers
----IndexController.php
--forms
----User.php
--views
----scripts
------index
--------index.phtml

применение / модели/DbTable / потребитель.в PHP

class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
    protected $_name = 'users';
    protected $_primary = 'user_id';
}

приложения/формы/пользователей.в PHP

class Form_User extends Zend_Form
{
    public function init()
    {       
        $this->setAction('')
            ->setMethod('post');

        $user_name = new Zend_Form_Element_Text('user_name');
        $user_name->setLabel("Name")->setRequired(true);

        $user_password = new Zend_Form_Element_Text('user_password');
        $user_password->setLabel("Password")->setRequired(true);

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setLabel('Save');

        $this->addElements(array(
            $user_name,
            $user_password,
            $submit
        ));
    }
}

приложение/контроллеры/IndexController.в PHP

class IndexController extends Zend_Controller_Action
{
    public function init()
    {

    }

    public function indexAction()
    {
        $form = new Form_User();
        if($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost()))
        {
            $post = $this->getRequest()->getPost();
            unlink($post['submit']);

            $ut = new Application_Model_DbTable_User();
            if($id = $ut->insert($post))
            {
                $this->view->message = "User added with id {$id}";
            } else {
                $this->view->message = "Sorry! Failed to add user";
            }
        }
        $this->view->form = $form;
    }
}

application/views/scripts/index / index.phtml, который

echo $this->message;
echo $this->form;