Yii Developer Bicycle Set

    From the author

    When the author writes a post on a hub, he tries to give readers the most complete and useful information on the topic. But if there is no right answer or solution? Then this post is just food for the mind, and the value lies in the collective mind.

    I went from far away, I do not argue, but I hope for your understanding and support. Speaking of solutions to the everyday problems of the developer, in particular on the Yii framework, I propose a solution to our team. At the same time, they are interested in community ideas. Well, pretty powdering your brains.
    Forward.


    Fashionable, youth, traits


    There are situations when the model executes the N () method, and at the same time returns true / false, and this is fine. But usually the user does not understand why the error occurred and how to live with it further, details are needed. It’s good if the logic is simple and you’re not a perfectionist - you put a little business of logic into the controller and displayed errors with details - but we are not like that!
    And if the method can bring up to 20 different errors, why Vasya can’t buy a pie or post a comment.
    Yii has a wonderful validate () method for the model, but it is definitely tied to the validation of the data of the model itself and is not suitable if you created an abstract method that is not directly related to the model.

    How to be?
    And like this
    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;
        }
    }
    


    A simple code like 5 cents, but it greatly simplifies life. Example:
    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 формате ошибку
          ...
    }
    


    What do we get on the way out? We get adequate methods that return a bool value, rather than vinaigrette possible answers, from int to string. No code duplication, pure DRY. Although no, I'm sure that smart people will come up with a cleaner option, well, that would be great!

    Down with the fashion stuff, just the console, only hardcore!


    In Smartprogress, we use continuous integration and each commit goes through several stages, from testing on the local area, testing on the dev server, testing on production and testing on users .

    What is it about, and yes, that we already have 6 databases. Two for each stage, working and test. To say that we are praying for migration is to say nothing. But bad luck, the Yii migrate team does not offer any adequate solution for such a zoo base. Yes, through the keys you can specify the desired connection, but do it every time for a long, boring, Laziness (laziness is that feeling that causes sympathy among programmers even more than male solidarity. What can I say, this post was born in the arms of this priestess of the programmer art)

    Oh, and pulled me, let's all love, bang bam and ...
    decision
    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 "    $migration\n";
            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 "    $migration\n";
            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);
        }
    }
    


    I’ll explain a little, in the Up / Down method, we loop through all the connections and apply our migration to each database in turn.
    An elementary solution is impossible. In my opinion, even somewhere peeped, I repent. But now, one command is enough, which can be performed even on Friday evening, being in an "abstract" state.
    yiic migratecombo up(/down/create/...)
    

    And your migrations apply to all existing databases specified in the $ connections variable.

    But there are nuances. If you decide to somehow migrate in a tricky way, not using standard Yii methods, but directly through the database, then:
    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();
    


    Testing, unit, functional, on rabbits


    To say that I am a testing specialist is almost like declaring half a year ago that Crimea will become part of Russia.
    But I’ve been doing it for a long time and I can’t keep silent, so I apologize in advance.

    In functional testing, the first thing I came across was that almost all the functions of the site are available only to authorized users, and as you know, the environment for each test is pristine.
    We decided it
    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();
                    }
    	}
    }
    


    I will not give the login method code, everything is purely individual there. Now it’s enough to specify loginRequired = true in the test class and your test will be performed by an authorized user.

    I can’t advise young and inexperienced testers like me, a wonderful Faker tool for generating fictitious but most realistic data. An indispensable thing for DataProvider
    Small example
    class MyTest extends CDbTestCase
    {
         public function newUserProvider() { // генерим 3 случайных набора данных
            $faker = \Faker\Factory::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()
        }
    }
    



    Of course, this is not all the tricks and buns that we gave birth to over the long period of development of Smartprogress .
    There are many more solutions and improvements, but I would like to ask you, dear readers, to share my thoughts and best practices on the topic. Surely every developer has a real zoo of helpers and ready-made solutions for a variety of tasks.
    I hope you share them with me and the entire habrahabr community.

    Also popular now: