Изучение Zend Framework после Magento: Модели - PullRequest
9 голосов
/ 19 августа 2011

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

Я привык иметь сущности и набор сущностей в 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 Framework?

Спасибо

Ответы [ 4 ]

5 голосов
/ 22 августа 2011

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

Тем не менее, большинством людей решение для моделей в Zend Framework является начальной загрузкой Doctrine .

4 голосов
/ 21 августа 2011

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

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

Вот несколько первых строк модели User:

class Application_Model_User {

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

    // End protected properties

Для каждого свойства у меня есть метод получения и установки.Пример для 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'.
}

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

РЕДАКТИРОВАТЬ

Как я уже говорил в моих комментариях, этот пост: 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 для этого.Я упоминал, что в вашей оболочке вы создаете метод, который возвращает объект запроса к базе данных для выбора объектов.Вот мой упрощенный, но работающий пользовательский маппер:

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 и сохраняю его в library/Application/Paginator/Adapter/Users.php.Убедитесь, что у вас правильно настроены appnamespace и autoloaderNamespaces[] в application.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;

Затем визуализируйте paginator в вашем скрипте вида.

2 голосов
/ 25 августа 2011

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

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

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

Для меня Data Mappers отделены от сущностей, их задача - делать 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, и в зависимости от того, что я делаю, внутри не будет никаких методов, или иногда я перезаписываю методы, такие как вставка или удаление, в зависимости от моих потребностей. В итоге у меня много файлов, но я полагаю, что разделение кода значительно облегчает быстрый переход к тому месту, где я должен быть, чтобы добавить больше функциональности или исправить ошибку, поскольку я знаю, где будут находиться все части кода.

Надеюсь, это поможет.

0 голосов
/ 26 августа 2011

Структура папки

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

приложение / модели / DbTable / User.php

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

приложение / формы / User.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
        ));
    }
}

приложение /controllers / 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;
...