📜 ⬆️ ⬇️

A set of bike Yii developer

From the author

When the author writes a post on Habré, 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 came from far away, I do not argue, but I hope for your understanding and support. Voicing solutions to everyday developer problems, in particular on the Yii framework, I propose a solution to our team. At the same time, they are interested in community ideas. Well, quite puff 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 that’s fine. But usually the user does not understand why an error occurred and how to live with it further, we need details. Well, if the logic is simple and you are not a perfectionist, you brought a little business of logic to the controller and brought errors with details - but we are not like that!
And if the method can display up to 20 different errors, why Vasya cannot buy a pie or post a comment.
In Yii, there is an excellent validate () method for the model, but it is precisely tied to validating 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?
That's how
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; } } 


Simple code is like 5 kopecks, but it makes life very simple. 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 at the output? We get adequate methods that return a bool value, rather than a vinaigrette of possible answers, from int to string. No code duplication, pure DRY. Although no, I am sure that smart people will come up with a cleaner option, well, that would be great!

Down with the fancy stuff, just the console, just hardcore!


In Smartprogress, we use continuous integration and each commit goes through several stages, from testing on locale, testing on a server server, testing on production and testing on users .

What is it, and yes, that we have as many as 6 databases. Two at each stage, working and test. To say that we pray on 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 right connection, but do it every time for a long time, tedious, LAZY (laziness is that feeling, which causes sympathy among programmers even more than male solidarity. art)

Oh, and pulled me, let's all love, bam bang and ...
decision
 <?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 " $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 bit; in the Up / Down method, we cycle through all connections and in turn apply our migration to each database.
An elementary decision is impossible. In my even somewhere peeking, I confess. But now, one command is enough, which can be executed even on Friday evening, being in the "abstract" state.
 yiic migratecombo up(/down/create/...) 

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

But there are nuances. If you decide how to do the tricky migration, not using the standard Yii methods, but directly through the base, 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, in rabbits


To say that I am a testing specialist is almost like half a year ago saying that the Crimea will be part of Russia.
But I have been doing it for a long time and I cannot keep silent, so I apologize in advance.

In functional testing, the first thing I encountered was that almost all functions of the site are available only to authorized users, and as you know, the environment for each test is virgin clean.
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 code of the login method, everything is entirely individual. Now it is enough to specify loginRequired = true in the test class and your test will be performed by a user authorized by all rules.

It’s impossible not to advise young and inexperienced testers like me, a wonderful Faker tool for generating fictitious, but as realistic as possible data. Irreplaceable 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 still many solutions and improvements, but I would like to ask you, dear readers, to share your thoughts and ideas on the subject. Surely every developer has a real zoo of helpers and ready-made solutions for various tasks.
I hope you share them with me and the whole habrahabr community.

Source: https://habr.com/ru/post/217239/


All Articles