Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Комплект велосипедов Yii разработчика

Anna | 29.05.2014 | нет комментариев
От автора

Когда автор пишет пост на прогре, он усердствует дать читателям максимально полную и пригодную информацию по теме. А вот если положительного результата либо решения нет? Тогда данный пост легко пища для ума, а ценность заключается в коллективном интеллекте.

Зашел я из далека, не спорю, но верю на ваше осознавание и поддержку. Озвучивая решения повседневных задач разработчика, в частности на фреймворке Yii, я предлагаю решение нашей команды. При этом, волнуют идеи сообщества. Ну, достаточно пудрить вам мозги.
Вперёд.

Модно, молодёжно, trait’ы

Бывают такие обстановки, когда модель исполняет способ N(), и при этом возвращает true / false и это восхитительно. Но традиционно пользователю не ясно отчего случилась оплошность и как жить с этим дальше, необходимы подробности. Отлично, если логика простая и вы не перфекционист — перенес чуть-чуть бизнес логики в контроллер и вывел ошибки с подробностями — но мы то не такие!
А если способ может вывести до 20 различных ошибок, отчего Вася не может приобрести пирожок либо запостить комментарий.
В Yii есть красивый способ validate() у модели, но он верно завязан на валидации данных самой модели и не подходит, если вы сотворили отвлеченный способ не связанный напрямую с моделью.

Как же быть?

А вот так

trait CustomError {
    private $_errorMessages = [];

    /**
     * Use in method :  return $this->setCustomErrorMessage(message);
     *
     * @param array $errorMessages
     * @return false
     */
    public function setCustomErrorMessage($errorMessages)
    {
        if(!is_array($errorMessages))
            $errorMessages = [$errorMessages];

        $this->_errorMessages = $errorMessages;

        return false;
    }

    /**
     * @param string $errorMessage
     */
    public function addCustomErrorMessage($errorMessage)
    {
        $this->_errorMessages[] = $errorMessage;
    }

    /**
     * @return array
     */
    public function getCustomErrorMessages()
    {
        return $this->_errorMessages;
    }

    /**
     * @return mixed
     */
    public function getCustomErrorMessageFirst()
    {
        return reset($this->_errorMessages);
    }

    /**
     * @return void
     */
    public function clearCustomErrorMessages()
    {
        $this->_errorMessages = [];
        return;
    }
}

Примитивный код как 5 копеек, но дюже упрощает жизнь. Пример:

class Blog extends CActiveRecord {

    use CustomError; // Подключаем наш трайт

    public function checkPrivacyCreate() { // Проверяем, может ли пользователь написать коммент
           ...
            $parent_post = $this->getPost($this->parent_post_id);
            if (empty($parent_post))
                return $this->setCustomErrorMessage(Yii::t('blog', 'post_not_found'));
           ...
        return true;
    }
}

// применение в контроллере
public function actionAddPost() {
      ....
      if (!$model->addPost())
                Tools::jsonError($model->getCustomErrorMessages()); // выводим в JSON формате ошибку
      ...
}

Что же мы получаем на выходе? Мы получаем адекватные способы, которые возвращают bool значение, а не винегрет допустимых результатов, от int до string. Никакого дублирование кода, чистый DRY. Правда нет, уверен, что мудрые люди придумают вариант почище, ну что же, было бы здорово!

Долой модные штуки, только консоль, только хардкор!

В Smartprogress мы используем continuous integration и всякий коммит проходит несколько стадий, от тестирование на локале, тестирование на дев сервере, тестирования на продакшене и тестирование на пользователях.

О чём это, а да, о том, что у нас даже 6 баз данных. По две на всякий этап, рабочая и тестовая. Сказать, что мы молимся на миграции — ничего не сказать. Но вот незадача, Yii migrate команда не предлагает никакого адекватного решения для такого зоопарка баз. Да, через ключи вы можете указать надобное соединение, но делать это всякий раз длинно, нудно, ЛЕНЬ (лень это то чувство, вызывающее симпатию у программистов даже огромнее, чем мужская единство. Что уж здесь говорить, данный пост рожден в объятиях этой жрицы программисткого искусства)

Ох и потянуло меня, давайте как все любят, бац бац и…

решение

<?php
Yii::import('system.cli.commands.MigrateCommand');
class MigratecomboCommand extends MigrateCommand {
    public $connections = array('db', 'db_test'); // Наименование компонентов коннектов из вашего конфига

    public function actionUp($args)
    {
        if(($migrations=$this->getNewMigrations())===array())
        {
            echo "No new migration found. Your system is up-to-date.n";
            return 0;
        }

        $total=count($migrations);
        $step=isset($args[0]) ? (int)$args[0] : 0;
        if($step>0)
            $migrations=array_slice($migrations,0,$step);

        $n=count($migrations);
        if($n===$total)
            echo "Total $n new ".($n===1 ? 'migration':'migrations')." to be applied:n";
        else
            echo "Total $n out of $total new ".($total===1 ? 'migration':'migrations')." to be applied:n";

        foreach($migrations as $migration)
            echo "    $migrationn";
        echo "n";

        if($this->confirm('Apply the above '.($n===1 ? 'migration':'migrations')."?"))
        {
            foreach($migrations as $migration)
            {
                foreach($this->connections as $connectionId) { // !!! Каждая магия тут, мы прогоняем миграцию по каждому конектам
                    $this->connectionID = $connectionId;
                    if($this->migrateUp($migration)===false)
                    {
                        echo "nMigration failed. All later migrations are canceled.n";
                        return 2;
                    }
                }
            }
            echo "nMigrated up successfully.n";
        }
    }

    public function actionDown($args)
    {
        $step=isset($args[0]) ? (int)$args[0] : 1;
        if($step<1)
        {
            echo "Error: The step parameter must be greater than 0.n";
            return 1;
        }

        if(($migrations=$this->getMigrationHistory($step))===array())
        {
            echo "No migration has been done before.n";
            return 0;
        }
        $migrations=array_keys($migrations);

        $n=count($migrations);
        echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:n";
        foreach($migrations as $migration)
            echo "    $migrationn";
        echo "n";

        if($this->confirm('Revert the above '.($n===1 ? 'migration':'migrations')."?"))
        {
            foreach($migrations as $migration)
            {
                foreach($this->connections as $connectionId) {
                    $this->connectionID = $connectionId;
                    if($this->migrateDown($migration)===false)
                    {
                        echo "nMigration failed. All later migrations are canceled.n";
                        return 2;
                    }
                }
            }
            echo "nMigrated down successfully.n";
        }
    }

    private $_db;
    protected function getDbConnection()
    {
        if(($this->_db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection)
            return $this->_db;

        echo "Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.n";
        exit(1);
    }
}

Немножко поясню, в способе Up/Down мы проходимся в цикле по каждому коннектам и по очереди применяем нашу миграцию к всякой базе.
Решение элементарное до невозможно. По моему даже где то подсмотренное, раскаиваюсь. Но сейчас, довольно одной команды, которую дозволено исполнить даже в пятницу вечером будучи в «абстрактном» состоянии.

yiic migratecombo up(/down/create/...)

И ваши миграции использоваться ко каждому присутствующим базам, указанным в переменной $connections.

Но есть нюансы. Если вы решите, как то по хитроумному исполнить миграцию, не применяя типовые способы Yii, а напрямую через базу, то:

class m140317_060002_fill_search_column extends CDbMigration
{
	public function up()
	{
        $goals = $this->getDbConnection() // Обратите внимание, взамен Yii::app()->db->createCommand... мы используем $this->getDbConnection()
            ->createCommand("SELECT id, `name` FROM goals WHERE `moderated` != 'deleted'")
            ->queryAll();

Тестирование, юнит, функциональное, на кроликах

Сказать, что я эксперт по тестированию, это примерно как заявить пол года назад, что Крым войдет в состав России.
Но занимаюсь им теснее давным-давно и промолчать не могу, так что извиняюсь предварительно.

В функциональном тестирование первое, с чем я столкнулся, это то, что примерно все функции сайта доступны только авторизированным пользователям, а как вестимо окружение для всякого теста девственно чисто.

Мы и это решили

class WebTestCase extends CWebTestCase
{
        public $loginRequired = false;

        protected function setUp()
	{
		parent::setUp();
                $this->setBrowser('*firefox');
		$this->setBrowserUrl(TEST_BASE_URL);
                $this->prepareTestSession();

                if($this->loginRequired) {
                    $this->login();
                }
	}
}

Код способа логина приводить не буду, там всё сугубо индивидуально. Сейчас довольно в классе теста указать loginRequired = true и ваш тест будет исполнять авторизированный по каждому правилам пользователь.

Немогу не порекомендовать молодым и неопытным тестировщикам как я, восхитительный инструмент Faker для генерации фальшивых, но максимально реалистичных данных. Необходимая вещь для DataProvider

Небольшой пример

class MyTest extends CDbTestCase
{
     public function newUserProvider() { // генерим 3 случайных комплекта данных
        $faker = FakerFactory::create('ru_RU');
        $array = array();

        for($i=0; $i<3; $i  ) {
            $array[$i]['user']['name'] = $faker->name;
            $array[$i]['user']['address'] = $faker->address;
            $array[$i]['user']['country'] = $faker->country;
        }
        return $array;
    }

   /**
     * @param $user
     * @dataProvider newUserProvider
     */
    public function testCreate($user) // Данный тест выполнится 3 раза и всякий раз с различными данными
    {
         $model = new User('signup');
         $model->name = $user['name'];
         ...
         $model->save()
    }

}

Безусловно, это не все хитрости и плюшки, которые мы родили за длинный период разработки Smartprogress.
Есть еще много решение и совершенствований, но я бы хотел попросить вас, дорогие читатели, поделиться своими мыслями и наработками по теме. Наверно у всякого разработчика есть подлинный зоопарк хелперов и готовых решение для самых различных задач.
Верю вы поделитесь ими со мною и каждому сообществом habrahabr.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB