@@ -548,6 +548,314 @@ public function search($params)
548
548
Для закрепления знаний, настройте фильтрацию и сортировку для дополнительного свойства ` countPlanets ` в модели
549
549
` StarSearch ` на странице <a href =" /yii2-app-advanced/backend/web/index.php?r=star " target =" _blank " >управления моделями Star</a >.
550
550
551
+ #### Формы для сохранения данных
552
+
553
+ Вы наверное уже обратили внимание на формы для сохранения:
554
+
555
+ - <a href =" /yii2-app-advanced/backend/web/index.php?r=star/create " target =" _blank " >звёзд</a >
556
+ - <a href =" /yii2-app-advanced/backend/web/index.php?r=planet/create " target =" _blank " >планет</a >
557
+ - <a href =" /yii2-app-advanced/backend/web/index.php?r=satellite/create " target =" _blank " >спутников</a >
558
+
559
+ Сейчас они выглядит, мягко говоря, не удобно для того, чтобы ими пользоваться.
560
+
561
+ Давайте начнём с первой формы - для сохранения звёзд. Смотрим на url - ` star/create ` , далее открываем контроллер ` StarController `
562
+ ищем в нём метод ` actionCreate() ` , видем что вызывается вид ` create ` - следовательно открываем ` yii2-app-advanced/backend/views/star/create.php `
563
+ И обнаруживаем, что в этом файле нет нам уже знакомого класса ` ActiveForm ` для работы с формами. А есть:
564
+
565
+ ``` php
566
+ $this->render('_form', ['model' => $model,])
567
+ ```
568
+
569
+ Как уже известно в видах $this - это <a href =" http://www.yiiframework.com/doc-2.0/yii-web-view.html " target =" _blank " >yii\web\View</a >.
570
+ Метод ` render ` , вам встречался в контроллере, но там его реализация отличается тем, что до вывода вида, вызывается компонент
571
+ для работы с видами, т.е. вызывается ` yii\web\View ` и только затем его метод для получения вида из файла. Тут же ` $this `
572
+ это уже компонент для работы с видами и его ` render() ` получает вид ` _form ` . Получается, что один вид ` _form ` находится внутри
573
+ другого вида ` create ` . Почему это так? Это удобно, так как вид ` _form ` содержит форму, которая может быть использована не
574
+ только для создания модели, но и также для изменения уже существующей модели. Если открыть вид ` update.php ` в этой же директории
575
+ то, можно обнаружить тот же код, что в и ` create.php ` :
576
+
577
+ ``` php
578
+ $this->render('_form', ['model' => $model,])
579
+ ```
580
+
581
+ Т.е. одна форма используется для видов ` create.php ` и ` update.php ` .
582
+
583
+ Открыв ` yii2-app-advanced/backend/views/star/_form.php ` вы не обнаружите ничего нового. Обычная форма - одно поле и кнопка.
584
+ В браузере эта форма занимает почти всю ширину экрана. Не совсем красиво. У
585
+ <a href =" http://www.yiiframework.com/doc-2.0/yii-bootstrap-activeform.html " target =" _blank " >ActiveForm</a >, что из пространства имён
586
+ ` yii\bootstrap\ ` есть свойство ` $layout ` , которое может иметь значение ` ['default', 'horizontal', 'inline'] ` . Попробуйте
587
+ установить inline. В ` views/star/_form ` :
588
+
589
+ ``` php
590
+ <?php $form = ActiveForm::begin(['layout'=>'inline']); ?>
591
+ ```
592
+
593
+ Не забудьте изменить ` yii\widgets\ActiveForm; ` на корректное пространство. Обновите страницу с формой и посмотрите
594
+ на результат. Красивее, чем было, хотя о вкусах не спорят. При ` inline ` можно заметить, что метки (label) не используются.
595
+ Но есть <a href =" http://htmlbook.ru/html/input/placeholder " target =" _blank " >placeholder</a >. Добавим его к ` textInput ` :
596
+
597
+ ``` php
598
+ <?= $form->field($model, 'name')->textInput(['maxlength' => 255, 'placeholder'=>'Введите название звезды']) ?>
599
+ ```
600
+
601
+ С Yii версии 2.0.3 ` maxlength ` - максимальная длина введённого текста, может быть автоматически высчитана из правила валидации:
602
+
603
+ ``` php
604
+ public function rules()
605
+ {
606
+ return [
607
+ [['name'], 'required'],
608
+ [['name'], 'string', 'max' => 255]
609
+ ];
610
+ }
611
+ ```
612
+
613
+ Для этого используйте:
614
+
615
+ ``` php
616
+ <?= $form->field($model, 'name')->textInput(['maxlength' => true, 'placeholder'=>'Введите название звезды']) ?>
617
+ ```
618
+
619
+ Теперь наша форма готова. Введите название для звезды и нажав кнопку создать, почувствовать себя властелином Вселенной.
620
+ <img src =" /scripts/assets/screen1.2-3.jpg " class =" img-responsive " >
621
+
622
+ Открыв ` \backend\controllers\StarController::actionCreate ` вы увидите уже знакомый принцип сохранения данных - проверка
623
+ и дальнейшая их сохранение. Со звездой всё просто. Перейдём к <a href =" /yii2-app-advanced/backend/web/index.php?r=planet/create " target =" _blank " >планетам</a >.
624
+
625
+ На форме с планетами появляется новое поле - ` star_id ` . Тот, кто будет пользоваться этой формой, будет вспоминать программиста
626
+ не добрым словом. Всех id не упомнишь, да и ошибиться всегда можно. Давайте сделаем выпадающий список с названиями звёзд.
627
+
628
+ Как мы делали когда-то для планет:
629
+
630
+ ``` php
631
+ <?= $form->field($model, 'planet')->dropDownList(
632
+ ['Меркурий', 'Венера', 'Земля', 'Марс', 'Юпитер', 'Сатурн', 'Уран', 'Нептун']
633
+ ) ?>
634
+ ```
635
+
636
+ Только вместо массива будем ипользовать запрос ` Star::find()->all() ` , который вернёт массив моделей.
637
+
638
+ ``` php
639
+ $stars = [];
640
+
641
+ foreach (Star::find()->all() as $star){
642
+ $stars[$star->id] = $star->name;
643
+ }
644
+
645
+ echo $form->field($model, 'planet')->dropDownList($stars);
646
+ ```
647
+
648
+ Но можно переписать этот код с использованием класса помощника <a href =" http://www.yiiframework.com/doc-2.0/yii-helpers-arrayhelper.html " target =" _blank " >
649
+ yii\helpers\ArrayHelper</a >, который позволяет обращаться с массивами более эффективно:
650
+
651
+ ``` php
652
+ $stars = ArrayHelper::map(Star::find()->all(), 'id', 'name');
653
+ echo $form->field($model, 'planet')->dropDownList($stars);
654
+ ```
655
+
656
+ Конечно, вы можете обойтись без переменной ` $stars ` , записав этот код одну строку. Ну и после всего, для этой формы
657
+ попробуйте использовать ` horizontal ` :
658
+
659
+ ``` php
660
+ <?php $form = ActiveForm::begin(['layout' => 'horizontal',]); ?>
661
+ ```
662
+
663
+ <img src =" /scripts/assets/screen1.2-4.jpg " class =" img-responsive " >
664
+
665
+ Осталось <a href =" /yii2-app-advanced/backend/web/index.php?r=satellite/create " target =" _blank " >форма создания спутников</a >.
666
+ Вы уже всё умеете, чтобы внести изменения самостоятельно.
667
+
668
+ <img src =" /scripts/assets/screen1.2-5.jpg " class =" img-responsive " >
669
+
670
+ Так как формы, для редактирования существующих моделей используются одни и те же, что и для сохранения. То, что-либо новое
671
+ создавать не нужно.
672
+
673
+ #### Сохранение реляционных данных.
674
+
675
+ У нас есть три формы для трёх разных моделей. Представьте себе ситуацию: нужно ввести информацию по новой планете, но звезды
676
+ у неё ещё нету. Не совсем удобно переключаться с формы на форму, сохраняя новые данные. Давайте объединим работу с тремя
677
+ формами в одной. Для этого нам понадобится новая модель формы, которая будет объединять работу с тремя моделями
678
+ относительно модели Планет.
679
+
680
+ Давайте начнём с теста. Это упростит отладку и разработку. Будем двигаться небольшими шагами, чтобы было понятнее и легче.
681
+
682
+ Создадим функциональный тест ` PlanetFormCept ` :
683
+
684
+ ```
685
+ cd yii2-app-advanced\tests\codeception\backend\
686
+ codecept build
687
+ ```
688
+
689
+ ```
690
+ codecept generate:cept functional PlanetFormCept
691
+ Test was created in PlanetFormCept.php
692
+ ```
693
+
694
+ Откройте файл теста ` PlanetFormCept.php ` и измените его содержимое на:
695
+
696
+ ``` php
697
+ <?php use tests\codeception\backend\FunctionalTester;
698
+ /* @var $scenario Codeception\Scenario */
699
+ $I = new FunctionalTester($scenario);
700
+ $I->wantTo('ensure than create form works');
701
+ ```
702
+
703
+ Можно запустить этот тест:
704
+
705
+ ```
706
+ codecept run functional functional/PlanetFormCept.php
707
+
708
+ Time: 1.51 seconds, Memory: 13.25Mb
709
+ OK (1 test, 0 assertions)
710
+ ```
711
+
712
+ Теперь к тесту добавим команду на открытие страницы с формой. Для этого нужно создать объект этой страницы.
713
+ В директории ` yii2-app-advanced/tests/codeception/backend/_pages/ ` создайте ` PlanetFormPage.php ` :
714
+
715
+ ``` php
716
+ <?php
717
+ namespace tests\codeception\backend\_pages;
718
+
719
+ use yii\codeception\BasePage;
720
+
721
+ class PlanetFormPage extends BasePage
722
+ {
723
+ public $route = 'planet/create';
724
+ }
725
+ ```
726
+
727
+ Теперь в нашем тесте можно воспользоваться этим объектом, для того, чтобы имитировать открытие страницы с формой:
728
+
729
+ ```
730
+ //...
731
+ $I->wantTo('ensure than create form works');
732
+ $formPage = \tests\codeception\backend\_pages\PlanetFormPage::openBy($I);
733
+ ```
734
+
735
+ Запускаем тест для того, чтобы убедиться, что всё выполнили правильно:
736
+
737
+ ```
738
+ codecept run functional functional/PlanetFormCept.php
739
+
740
+ Time: 518 ms, Memory: 18.00Mb
741
+ OK (1 test, 0 assertions)
742
+ ```
743
+
744
+ На данный момент форма содержит два поля: тестовое поле "Название планеты" и выпадающий список "Название звезды".
745
+ Проверим их через тест. Для этого нам понадобятся методы ` fillField ` и ` click ` из класса
746
+ ` yii2-app-advanced/tests/codeception/backend/functional/FunctionalTester.php ` , который создался с помощью ранее выполненной
747
+ команды ` codecept build ` .
748
+
749
+ <p class =" alert alert-info " >
750
+ Ознакомьтесь с информацией <a href =" http://codeception.com/docs/modules/Yii2 " target =" _blank " >по доступным методам</a >
751
+ модуля Yii2 для Codeception.
752
+ </p >
753
+
754
+ ``` php
755
+ <?php use tests\codeception\backend\FunctionalTester;
756
+ /* @var $scenario Codeception\Scenario */
757
+ $I = new FunctionalTester($scenario);
758
+ $I->wantTo('ensure than create form works');
759
+ $formPage = \tests\codeception\backend\PlanetFormPage::openBy($I);
760
+
761
+ $I->fillField('//*[@id="planet-name"]','Земля');
762
+ $I->selectOption('//*[@id="planet-star_id"]', 'Солнце');
763
+ $I->click('//*[@id="w0"]/div[3]/button');
764
+ $I->dontSeeInTitle('Новая планета');
765
+ ```
766
+
767
+ - fillField - заполняем текстовое поле "Название планеты"
768
+ - click - нажимаем на кнопку "Создать"
769
+ - dontSeeInTitle - проверяем, чтобы в заголовке странице не было текста "Новая планета"
770
+
771
+ ` //*[@id="planet-name"] ` и ` '//*[@id="w0"]/div[3]/button' ` - это <a href =" https://ru.wikipedia.org/wiki/XPath " target =" _blank " >XPath</a >.
772
+ Например, в браузере Chrome, нажав на странице с формой F12, можно получить XPath через контекстное меню к html коду элемента:
773
+
774
+ <img src =" /scripts/assets/screen1.2-6.jpg " class =" img-responsive " >
775
+
776
+ Запустив тест, увидим ошибку:
777
+
778
+ ```
779
+ codecept run functional functional/PlanetFormCept.php
780
+
781
+ InvalidArgumentException: Input "Planet[star_id]" cannot take "Солнце" as a value (possible values: ).
782
+ 3. I select option "//*[@id="planet-star_id"]","Солнце"
783
+ //...
784
+ ```
785
+
786
+ Всё потому, что используется тестовая база данных, которая никакой информации по звёздам не содержит. Данные есть только
787
+ в главной базе данных, но её использовать не будем, во избежание порчи данных. На помощь приходят фикстуры. Это состояние
788
+ базы данных, до которого она будет доведена при запуске теста. Для работы с фикстурами исполнитель функциональных тестов
789
+ ` FunctionalTester.php ` использует класс помощник ` FixtureHelper.php ` (в файле ` tests/codeception/backend/functional.suite.yml ` ):
790
+
791
+ ```
792
+ class_name: FunctionalTester
793
+ modules:
794
+ enabled:
795
+ - Filesystem
796
+ - Yii2
797
+ - tests\codeception\common\_support\FixtureHelper
798
+ config:
799
+ Yii2:
800
+ configFile: '../config/backend/functional.php'
801
+ ```
802
+
803
+ откройте этот файл и найдите его метод:
804
+
805
+ ``` php
806
+ public function fixtures()
807
+ {
808
+ return [
809
+ 'user' => [
810
+ 'class' => UserFixture::className(),
811
+ 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php',
812
+ ],
813
+ ];
814
+ }
815
+ ```
816
+
817
+ Запуская каждый раз любой функциональный тест из backend, стартует фикстура UserFixture:
818
+
819
+ ``` php
820
+ class UserFixture extends ActiveFixture
821
+ {
822
+ public $modelClass = 'common\models\User';
823
+ }
824
+ ```
825
+
826
+ , которая очищает таблицу для модели ` common\models\User ` . А затем заполняет её данными из ` dataFile ` .
827
+
828
+ Для кого-то, на первый раз это всё может показаться сложным и избыточным. Ведь мы ушли в сторону, а могли бы за это время
829
+ уже создать необходимую форму и двигаться дальше. Но нет, топчемся на месте. Тяжело в учении - легко в бою.
830
+ Сделайте паузу, выпейте кофе, расслабитесь. Когда отдохнули - продолжим.
831
+
832
+ Создадим новые фикстуры, которые будут сбрасывать состояние таблицы для звёзд, планет и их спутников. Создайте в
833
+ ` yii2-app-advanced/tests/codeception/common/fixtures ` файлы:
834
+
835
+ - PlanetFixture.php
836
+ - SatelliteFixture.php
837
+ - StarFixture.php
838
+
839
+ Вот пример одной из фикстуры ` SatelliteFixture.php ` :
840
+
841
+ ``` php
842
+ <?php
843
+ namespace tests\codeception\common\fixtures;
844
+
845
+ use yii\test\ActiveFixture;
846
+
847
+ class SatelliteFixture extends ActiveFixture
848
+ {
849
+ public $modelClass = 'common\models\Satellite';
850
+ }
851
+
852
+ ```
853
+
854
+ Остальные две: ` PlanetFixture ` и ` StarFixture ` создайте самостоятельно.
855
+
856
+
857
+
551
858
#### Дополнительная информация для самостоятельного ознакомления:
552
859
553
- - <a href =" # " target =" _blank " >Официальное руководство по работе с связями в AR</a >.
860
+ - <a href =" # " target =" _blank " >Официальное руководство по работе с связями в AR</a >.
861
+ - <a href =" https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/helper-array.md " target =" _blank " >Руководство по ArrayHelper</a >.
0 commit comments