├── .gitignore
├── CHANGELOG.md
├── LICENCE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
└── CompositeForm.php
└── tests
├── LoadActiveFormTest.php
├── LoadApiTest.php
├── TestCase.php
├── ValidateTest.php
├── _forms
├── MetaForm.php
├── OnlyNestedProductForm.php
├── ProductForm.php
└── ValueForm.php
└── bootstrap.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 | /phpunit.xml
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.1.0
4 |
5 | - Allowed loading of partial data: the `load($data)` method returns `true` if at least one form's data is presented in `$data` array (vjik)
6 | - Renamed `private $forms` variable to `protected $_forms` (vjik, makcumka2000)
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | # The BSD License (BSD)
2 |
3 | Copyright © 2016 by Dmitry Eliseev (ElisDN) All rights reserved.
4 |
5 | > Redistribution and use in source and binary forms, with or without modification,
6 | > are permitted provided that the following conditions are met:
7 | >
8 | > Redistributions of source code must retain the above copyright notice, this
9 | > list of conditions and the following disclaimer.
10 | >
11 | > Redistributions in binary form must reproduce the above copyright notice, this
12 | > list of conditions and the following disclaimer in the documentation and/or
13 | > other materials provided with the distribution.
14 | >
15 | > Neither the name of Dmitry Eliseev (ElisDN) nor the names of its
16 | > contributors may be used to endorse or promote products derived from
17 | > this software without specific prior written permission.
18 | >
19 | >THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 | >ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | >WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | >DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23 | >ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | >(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | >LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 | >ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | >(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | >SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Composite Form for Yii2 Framework
2 |
3 | The extension allows to create nested form models.
4 |
5 | ## Installation
6 |
7 | Install with composer:
8 |
9 | ```bash
10 | composer require elisdn/yii2-composite-form
11 | ```
12 |
13 | ## Usage samples
14 |
15 | Create any simple form model for SEO information:
16 |
17 | ```php
18 | class MetaForm extends Model
19 | {
20 | public $title;
21 | public $description;
22 | public $keywords;
23 |
24 | public function rules()
25 | {
26 | return [
27 | [['title'], 'string', 'max' => 255],
28 | [['description', 'keywords'], 'string'],
29 | ];
30 | }
31 | }
32 | ```
33 |
34 | and for characteristics:
35 |
36 | ```php
37 | class ValueForm extends Model
38 | {
39 | public $value;
40 |
41 | private $_characteristic;
42 |
43 | public function __construct(Characteristic $characteristic, $config = [])
44 | {
45 | $this->_characteristic = $characteristic;
46 | parent::__construct($config);
47 | }
48 |
49 | public function rules()
50 | {
51 | return [
52 | ['value', 'safe'],
53 | ];
54 | }
55 |
56 | public function attributeLabels()
57 | {
58 | return [
59 | 'value' => $this->_characteristic->name,
60 | ];
61 | }
62 |
63 | public function getCharacteristicId()
64 | {
65 | return $this->_characteristic->id;
66 | }
67 | }
68 | ```
69 |
70 | And create a composite form model which uses both as an internal forms:
71 |
72 | ```php
73 | use elisdn\compositeForm\CompositeForm;
74 |
75 | /**
76 | * @property MetaForm $meta
77 | * @property ValueForm[] $values
78 | */
79 | class ProductCreateForm extends CompositeForm
80 | {
81 | public $code;
82 | public $name;
83 |
84 | public function __construct($config = [])
85 | {
86 | $this->meta = new MetaForm();
87 | $this->values = array_map(function (Characteristic $characteristic) {
88 | return new ValueForm($characteristic);
89 | }, Characteristic::find()->orderBy('sort')->all());
90 | parent::__construct($config);
91 | }
92 |
93 | public function rules()
94 | {
95 | return [
96 | [['code', 'name'], 'required'],
97 | [['code', 'name'], 'string', 'max' => 255],
98 | [['code'], 'unique', 'targetClass' => Product::className()],
99 | ];
100 | }
101 |
102 | protected function internalForms()
103 | {
104 | return ['meta', 'values'];
105 | }
106 | }
107 | ```
108 |
109 | That is all. After all just use external `$form` and internal `$form->meta` and `$form->values` models for `ActiveForm`:
110 |
111 | ```php
112 |
113 |
114 |
Common
115 |
116 | = $form->field($model, 'code')->textInput() ?>
117 | = $form->field($model, 'name')->textInput() ?>
118 |
119 | Characteristics
120 |
121 | values as $i => $valueForm): ?>
122 | = $form->field($valueForm, '[' . $i . ']value')->textInput() ?>
123 |
124 |
125 | SEO
126 |
127 | = $form->field($model->meta, 'title')->textInput() ?>
128 | = $form->field($model->meta, 'description')->textarea(['rows' => 2]) ?>
129 |
130 |
131 | = Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
132 |
133 |
134 |
135 | ```
136 |
137 | and for your application's services:
138 |
139 | ```php
140 | class ProductManageService
141 | {
142 | private $products;
143 |
144 | public function __construct(ProductRepository $products)
145 | {
146 | $this->products = $products;
147 | }
148 |
149 | public function create(ProductCreateForm $form)
150 | {
151 | $product = Product::create(
152 | $form->code,
153 | $form->name,
154 | new Meta(
155 | $form->meta->title,
156 | $form->meta->description,
157 | $form->meta->keywords
158 | )
159 | );
160 |
161 | foreach ($form->values as $valueForm) {
162 | $product->changeValue($valueForm->getCharacteristicId(), $valueForm->value);
163 | }
164 |
165 | $this->products->save($product);
166 |
167 | return $product->id;
168 | }
169 |
170 | ...
171 | }
172 | ```
173 |
174 | with simple controller for web:
175 |
176 | ```php
177 | class ProductController extends \yii\web\Controller
178 | {
179 | ...
180 |
181 | public function actionCreate()
182 | {
183 | $form = new ProductCreateForm();
184 |
185 | if ($form->load(Yii::$app->request->post()) && $form->validate()) {
186 | $id = $this->service->create($form);
187 | return $this->redirect(['view', 'id' => $id]);
188 | }
189 |
190 | return $this->render('create', [
191 | 'model' => $form,
192 | ]);
193 | }
194 | }
195 | ```
196 |
197 | or for API:
198 |
199 | ```php
200 | class ProductController extends \yii\rest\Controller
201 | {
202 | ...
203 |
204 | public function actionCreate()
205 | {
206 | $form = new ProductCreateForm();
207 | $form->load(Yii::$app->request->getBodyParams());
208 |
209 | if ($form->validate()) {
210 | $id = $this->service->create($form);
211 | $response = Yii::$app->getResponse();
212 | $response->setStatusCode(201);
213 | $response->getHeaders()->set('Location', Url::to(['view', 'id' => $id], true));
214 | return [];
215 | }
216 |
217 | return $form;
218 | }
219 | }
220 | ```
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elisdn/yii2-composite-form",
3 | "description": "Nested forms base class for Yii2 Framework.",
4 | "type": "yii2-extension",
5 | "keywords": ["yii2", "yii 2"],
6 | "license": "BSD-3-Clause",
7 | "authors": [
8 | {
9 | "name": "Dmitry Eliseev",
10 | "email": "mail@elisdn.ru",
11 | "homepage": "http://www.elisdn.ru"
12 | }
13 | ],
14 | "support": {
15 | "issues": "https://github.com/ElisDN/yii2-composite-form/issues?state=open",
16 | "source": "https://github.com/ElisDN/yii2-composite-form"
17 | },
18 | "require": {
19 | "yiisoft/yii2": "~2.0"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "4.*"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "elisdn\\compositeForm\\": "src/"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "elisdn\\compositeForm\\tests\\": "tests/"
32 | }
33 | },
34 | "repositories": [
35 | {
36 | "type": "composer",
37 | "url": "https://asset-packagist.org"
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | ./tests
11 |
12 |
13 |
14 |
15 | ./src/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/CompositeForm.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm;
11 |
12 | use yii\base\Model;
13 | use yii\helpers\ArrayHelper;
14 |
15 | abstract class CompositeForm extends Model
16 | {
17 | /**
18 | * @var Model[]|array[]
19 | */
20 | protected $_forms = [];
21 |
22 | /**
23 | * @return array of internal forms like ['meta', 'values']
24 | */
25 | abstract protected function internalForms();
26 |
27 | public function load($data, $formName = null)
28 | {
29 | $success = parent::load($data, $formName);
30 | foreach ($this->_forms as $name => $form) {
31 | if (is_array($form)) {
32 | $success = Model::loadMultiple($form, $data, $formName === null ? null : $name) || $success;
33 | } else {
34 | $success = $form->load($data, $formName !== '' ? null : $name) || $success;
35 | }
36 | }
37 | return $success;
38 | }
39 |
40 | public function validate($attributeNames = null, $clearErrors = true)
41 | {
42 | if ($attributeNames !== null) {
43 | $parentNames = array_filter($attributeNames, 'is_string');
44 | $success = $parentNames ? parent::validate($parentNames, $clearErrors) : true;
45 | } else {
46 | $success = parent::validate(null, $clearErrors);
47 | }
48 | foreach ($this->_forms as $name => $form) {
49 | if ($attributeNames === null || array_key_exists($name, $attributeNames) || in_array($name, $attributeNames, true)) {
50 | $innerNames = ArrayHelper::getValue($attributeNames, $name);
51 | if (is_array($form)) {
52 | $success = Model::validateMultiple($form, $innerNames) && $success;
53 | } else {
54 | $success = $form->validate($innerNames, $clearErrors) && $success;
55 | }
56 | }
57 | }
58 | return $success;
59 | }
60 |
61 | public function hasErrors($attribute = null)
62 | {
63 | if ($attribute !== null && mb_strpos($attribute, '.') === false) {
64 | return parent::hasErrors($attribute);
65 | }
66 | if (parent::hasErrors($attribute)) {
67 | return true;
68 | }
69 | foreach ($this->_forms as $name => $form) {
70 | if (is_array($form)) {
71 | foreach ($form as $i => $item) {
72 | if ($attribute === null) {
73 | if ($item->hasErrors()) {
74 | return true;
75 | }
76 | } elseif (mb_strpos($attribute, $name . '.' . $i . '.') === 0) {
77 | if ($item->hasErrors(mb_substr($attribute, mb_strlen($name . '.' . $i . '.')))) {
78 | return true;
79 | }
80 | }
81 | }
82 | } else {
83 | if ($attribute === null) {
84 | if ($form->hasErrors()) {
85 | return true;
86 | }
87 | } elseif (mb_strpos($attribute, $name . '.') === 0) {
88 | if ($form->hasErrors(mb_substr($attribute, mb_strlen($name . '.')))) {
89 | return true;
90 | }
91 | }
92 | }
93 | }
94 | return false;
95 | }
96 |
97 | public function getErrors($attribute = null)
98 | {
99 | $result = parent::getErrors($attribute);
100 | foreach ($this->_forms as $name => $form) {
101 | if (is_array($form)) {
102 | /** @var Model[] $form */
103 | foreach ($form as $i => $item) {
104 | foreach ($item->getErrors() as $attr => $errors) {
105 | /** @var array $errors */
106 | $errorAttr = $name . '.' . $i . '.' . $attr;
107 | if ($attribute === null) {
108 | foreach ($errors as $error) {
109 | $result[$errorAttr][] = $error;
110 | }
111 | } elseif ($errorAttr === $attribute) {
112 | foreach ($errors as $error) {
113 | $result[] = $error;
114 | }
115 | }
116 | }
117 | }
118 | } else {
119 | foreach ($form->getErrors() as $attr => $errors) {
120 | /** @var array $errors */
121 | $errorAttr = $name . '.' . $attr;
122 | if ($attribute === null) {
123 | foreach ($errors as $error) {
124 | $result[$errorAttr][] = $error;
125 | }
126 | } elseif ($errorAttr === $attribute) {
127 | foreach ($errors as $error) {
128 | $result[] = $error;
129 | }
130 | }
131 | }
132 | }
133 | }
134 | return $result;
135 | }
136 |
137 | public function getFirstErrors()
138 | {
139 | $result = parent::getFirstErrors();
140 | foreach ($this->_forms as $name => $form) {
141 | if (is_array($form)) {
142 | foreach ($form as $i => $item) {
143 | foreach ($item->getFirstErrors() as $attr => $error) {
144 | $result[$name . '.' . $i . '.' . $attr] = $error;
145 | }
146 | }
147 | } else {
148 | foreach ($form->getFirstErrors() as $attr => $error) {
149 | $result[$name . '.' . $attr] = $error;
150 | }
151 | }
152 | }
153 | return $result;
154 | }
155 |
156 | public function __get($name)
157 | {
158 | if (isset($this->_forms[$name])) {
159 | return $this->_forms[$name];
160 | }
161 | return parent::__get($name);
162 | }
163 |
164 | public function __set($name, $value)
165 | {
166 | if (in_array($name, $this->internalForms(), true)) {
167 | $this->_forms[$name] = $value;
168 | } else {
169 | parent::__set($name, $value);
170 | }
171 | }
172 |
173 | public function __isset($name)
174 | {
175 | return isset($this->_forms[$name]) || parent::__isset($name);
176 | }
177 | }
--------------------------------------------------------------------------------
/tests/LoadActiveFormTest.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests;
11 |
12 | use elisdn\compositeForm\tests\_forms\OnlyNestedProductForm;
13 | use elisdn\compositeForm\tests\_forms\ProductForm;
14 |
15 | class LoadActiveFormTest extends TestCase
16 | {
17 | public function testWholeForm()
18 | {
19 | $data = [
20 | 'ProductForm' => [
21 | 'code' => 'P100',
22 | 'name' => 'Product Name',
23 | ],
24 | 'MetaForm' => [
25 | 'title' => 'Meta Title',
26 | 'description' => 'Meta Description',
27 | ],
28 | 'ValueForm' => [
29 | ['value' => '101'],
30 | ['value' => '102'],
31 | ['value' => '103'],
32 | ],
33 | ];
34 |
35 | $form = new ProductForm(3);
36 |
37 | $this->assertTrue($form->load($data));
38 |
39 | $this->assertEquals($data['ProductForm']['code'], $form->code);
40 | $this->assertEquals($data['ProductForm']['name'], $form->name);
41 |
42 | $this->assertEquals($data['MetaForm']['title'], $form->meta->title);
43 | $this->assertEquals($data['MetaForm']['description'], $form->meta->description);
44 |
45 | $this->assertCount(3, $values = $form->values);
46 |
47 | $this->assertEquals($data['ValueForm'][0]['value'], $values[0]->value);
48 | $this->assertEquals($data['ValueForm'][1]['value'], $values[1]->value);
49 | $this->assertEquals($data['ValueForm'][2]['value'], $values[2]->value);
50 | }
51 |
52 | public function testPartialForm()
53 | {
54 | $data = [
55 | 'ProductForm' => [
56 | 'code' => 'P100',
57 | 'name' => 'Product Name',
58 | ],
59 | 'MetaForm' => [
60 | 'title' => 'Meta Title',
61 | 'description' => 'Meta Description',
62 | ],
63 | ];
64 |
65 | $form = new ProductForm(3);
66 |
67 | $this->assertTrue($form->load($data));
68 |
69 | $this->assertEquals($data['ProductForm']['code'], $form->code);
70 | $this->assertEquals($data['ProductForm']['name'], $form->name);
71 |
72 | $this->assertEquals($data['MetaForm']['title'], $form->meta->title);
73 | $this->assertEquals($data['MetaForm']['description'], $form->meta->description);
74 |
75 | $this->assertCount(3, $values = $form->values);
76 |
77 | $this->assertNull($values[0]->value);
78 | $this->assertNull($values[1]->value);
79 | $this->assertNull($values[2]->value);
80 | }
81 |
82 | public function testOnlyInternalForms()
83 | {
84 | $data = [
85 | 'MetaForm' => [
86 | 'title' => 'Meta Title',
87 | 'description' => 'Meta Description',
88 | ],
89 | ];
90 |
91 | $form = new OnlyNestedProductForm();
92 |
93 | $this->assertTrue($form->load($data));
94 |
95 | $this->assertEquals($data['MetaForm']['title'], $form->meta->title);
96 | $this->assertEquals($data['MetaForm']['description'], $form->meta->description);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/LoadApiTest.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests;
11 |
12 | use elisdn\compositeForm\tests\_forms\OnlyNestedProductForm;
13 | use elisdn\compositeForm\tests\_forms\ProductForm;
14 |
15 | class LoadApiTest extends TestCase
16 | {
17 | public function testWholeForm()
18 | {
19 | $data = [
20 | 'code' => 'P100',
21 | 'name' => 'Product Name',
22 | 'meta' => [
23 | 'title' => 'Meta Title',
24 | 'description' => 'Meta Description',
25 | ],
26 | 'values' => [
27 | ['value' => '101'],
28 | ['value' => '102'],
29 | ['value' => '103'],
30 | ],
31 | ];
32 |
33 | $form = new ProductForm(3);
34 |
35 | $this->assertTrue($form->load($data, ''));
36 |
37 | $this->assertEquals($data['code'], $form->code);
38 | $this->assertEquals($data['name'], $form->name);
39 |
40 | $this->assertEquals($data['meta']['title'], $form->meta->title);
41 | $this->assertEquals($data['meta']['description'], $form->meta->description);
42 |
43 | $this->assertCount(3, $values = $form->values);
44 |
45 | $this->assertEquals($data['values'][0]['value'], $values[0]->value);
46 | $this->assertEquals($data['values'][1]['value'], $values[1]->value);
47 | $this->assertEquals($data['values'][2]['value'], $values[2]->value);
48 | }
49 |
50 | public function testOnlyInternalForms()
51 | {
52 | $data = [
53 | 'meta' => [
54 | 'title' => 'Meta Title',
55 | 'description' => 'Meta Description',
56 | ],
57 | ];
58 |
59 | $form = new OnlyNestedProductForm();
60 |
61 | $this->assertTrue($form->load($data, ''));
62 |
63 | $this->assertEquals($data['meta']['title'], $form->meta->title);
64 | $this->assertEquals($data['meta']['description'], $form->meta->description);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests;
11 |
12 | use yii\console\Application;
13 |
14 | abstract class TestCase extends \PHPUnit_Framework_TestCase
15 | {
16 | protected function setUp()
17 | {
18 | parent::setUp();
19 | $this->mockApplication();
20 | }
21 |
22 | protected function tearDown()
23 | {
24 | $this->destroyApplication();
25 | parent::tearDown();
26 | }
27 |
28 | protected function mockApplication()
29 | {
30 | new Application([
31 | 'id' => 'testapp',
32 | 'basePath' => __DIR__,
33 | 'vendorPath' => dirname(__DIR__) . '/vendor',
34 | 'runtimePath' => __DIR__ . '/runtime',
35 | ]);
36 | }
37 |
38 | protected function destroyApplication()
39 | {
40 | \Yii::$app = null;
41 | }
42 | }
--------------------------------------------------------------------------------
/tests/ValidateTest.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests;
11 |
12 | use elisdn\compositeForm\tests\_forms\OnlyNestedProductForm;
13 | use elisdn\compositeForm\tests\_forms\ProductForm;
14 |
15 | class ValidateTest extends TestCase
16 | {
17 | public function testValidWholeForm()
18 | {
19 | $data = [
20 | 'code' => 'P100',
21 | 'name' => 'Product Name',
22 | 'meta' => [
23 | 'title' => 'Meta Title',
24 | 'description' => 'Meta Description',
25 | ],
26 | 'values' => [
27 | ['value' => '101'],
28 | ['value' => '102'],
29 | ['value' => '103'],
30 | ],
31 | ];
32 |
33 | $form = new ProductForm(3);
34 |
35 | $form->load($data, '');
36 |
37 | $this->assertTrue($form->validate());
38 | $this->assertFalse($form->hasErrors());
39 | $this->assertEmpty($form->getErrors());
40 | }
41 |
42 | public function testValidWithoutValues()
43 | {
44 | $data = [
45 | 'code' => 'P100',
46 | 'name' => 'Product Name',
47 | 'meta' => [
48 | 'title' => 'Meta Title',
49 | 'description' => 'Meta Description',
50 | ],
51 | 'values' => [],
52 | ];
53 |
54 | $form = new ProductForm(0);
55 |
56 | $form->load($data, '');
57 |
58 | $this->assertTrue($form->validate());
59 | $this->assertFalse($form->hasErrors());
60 | $this->assertEmpty($form->getErrors());
61 | }
62 |
63 | public function testNotValidWholeForm()
64 | {
65 | $data = [
66 | 'code' => null,
67 | 'name' => 'Product Name',
68 | 'meta' => [
69 | 'title' => null,
70 | 'description' => 'Meta Description',
71 | ],
72 | 'values' => [
73 | ['value' => '101'],
74 | ['value' => ''],
75 | ['value' => '103'],
76 | ],
77 | ];
78 |
79 | $form = new ProductForm(3);
80 |
81 | $form->load($data, '');
82 |
83 | $this->assertFalse($form->validate());
84 | $this->assertTrue($form->hasErrors());
85 |
86 | $this->assertEquals([
87 | 'code' => ['Code cannot be blank.'],
88 | 'meta.title' => ['Title cannot be blank.'],
89 | 'values.1.value' => ['Value cannot be blank.'],
90 | ], $form->getErrors());
91 |
92 | $this->assertEquals(['Code cannot be blank.'], $form->getErrors('code'));
93 | $this->assertEquals(['Title cannot be blank.'], $form->getErrors('meta.title'));
94 | $this->assertEquals(['Value cannot be blank.'], $form->getErrors('values.1.value'));
95 |
96 | $this->assertEquals([], $form->getErrors('name'));
97 | $this->assertEquals([], $form->getErrors('meta.description'));
98 | $this->assertEquals([], $form->getErrors('values.2.value'));
99 |
100 | $this->assertTrue($form->hasErrors('code'));
101 | $this->assertFalse($form->hasErrors('name'));
102 | $this->assertTrue($form->hasErrors('meta.title'));
103 | $this->assertFalse($form->hasErrors('meta.description'));
104 | $this->assertTrue($form->hasErrors('values.1.value'));
105 | $this->assertFalse($form->hasErrors('values.2.value'));
106 |
107 | $this->assertEquals([
108 | 'code' => 'Code cannot be blank.',
109 | 'meta.title' => 'Title cannot be blank.',
110 | 'values.1.value' => 'Value cannot be blank.',
111 | ], $form->getFirstErrors());
112 | }
113 |
114 | public function testNotValidInternalForms()
115 | {
116 | $data = [
117 | 'code' => 'P100',
118 | 'name' => 'Product Name',
119 | 'meta' => [
120 | 'title' => null,
121 | 'description' => 'Meta Description',
122 | ],
123 | 'values' => [
124 | ['value' => '101'],
125 | ['value' => ''],
126 | ['value' => '103'],
127 | ],
128 | ];
129 |
130 | $form = new ProductForm(3);
131 |
132 | $form->load($data, '');
133 |
134 | $this->assertFalse($form->validate());
135 | $this->assertTrue($form->hasErrors());
136 |
137 | $this->assertEquals([
138 | 'meta.title' => ['Title cannot be blank.'],
139 | 'values.1.value' => ['Value cannot be blank.'],
140 | ], $form->getErrors());
141 |
142 | $this->assertFalse($form->hasErrors('code'));
143 | $this->assertTrue($form->hasErrors('meta.title'));
144 | $this->assertTrue($form->hasErrors('values.1.value'));
145 |
146 | $this->assertEquals([
147 | 'meta.title' => 'Title cannot be blank.',
148 | 'values.1.value' => 'Value cannot be blank.',
149 | ], $form->getFirstErrors());
150 | }
151 |
152 | public function testValidAttributeNames()
153 | {
154 | $data = [
155 | 'code' => 'P100',
156 | 'name' => 'Product Name',
157 | 'meta' => [
158 | 'title' => 'Meta Title',
159 | 'description' => 'Meta Description',
160 | ],
161 | 'values' => [
162 | ['value' => '101'],
163 | ['value' => '103'],
164 | ],
165 | ];
166 |
167 | $form = new ProductForm(0);
168 |
169 | $form->load($data, '');
170 |
171 | $this->assertTrue($form->validate(['code']));
172 | $this->assertTrue($form->validate(['name']));
173 | $this->assertTrue($form->validate(['meta']));
174 | $this->assertTrue($form->validate(['meta' => ['title']]));
175 | $this->assertTrue($form->validate(['meta' => ['description']]));
176 | $this->assertTrue($form->validate(['meta' => ['title', 'description']]));
177 | $this->assertTrue($form->validate(['values']));
178 | $this->assertTrue($form->validate(['values' => ['value']]));
179 | }
180 |
181 | public function testNotValidAttributeNames()
182 | {
183 | $data = [
184 | 'code' => null,
185 | 'name' => 'Product Name',
186 | 'meta' => [
187 | 'title' => null,
188 | 'description' => 'Meta Description',
189 | ],
190 | 'values' => [
191 | ['value' => '101'],
192 | ['value' => ''],
193 | ],
194 | ];
195 |
196 | $form = new ProductForm(2);
197 |
198 | $form->load($data, '');
199 |
200 | $this->assertFalse($form->validate(['code']));
201 | $this->assertTrue($form->validate(['name']));
202 | $this->assertFalse($form->validate(['meta']));
203 | $this->assertFalse($form->validate(['meta' => ['title']]));
204 | $this->assertTrue($form->validate(['meta' => ['description']]));
205 | $this->assertFalse($form->validate(['meta' => ['title', 'description']]));
206 | $this->assertFalse($form->validate(['values']));
207 | $this->assertFalse($form->validate(['values' => ['value']]));
208 | }
209 |
210 | public function testValidOnlyNestedForms()
211 | {
212 | $data = [
213 | 'meta' => [
214 | 'title' => 'Meta Title',
215 | 'description' => 'Meta Description',
216 | ],
217 | ];
218 |
219 | $form = new OnlyNestedProductForm();
220 |
221 | $form->load($data, '');
222 |
223 | $this->assertTrue($form->validate());
224 | $this->assertFalse($form->hasErrors());
225 | $this->assertEmpty($form->getErrors());
226 | }
227 |
228 | public function testNotValidOnlyNestedForms()
229 | {
230 | $data = [
231 | 'meta' => [
232 | 'title' => null,
233 | 'description' => 'Meta Description',
234 | ],
235 | ];
236 |
237 | $form = new OnlyNestedProductForm();
238 |
239 | $form->load($data, '');
240 |
241 | $this->assertFalse($form->validate());
242 | $this->assertTrue($form->hasErrors());
243 |
244 | $this->assertEquals([
245 | 'meta.title' => ['Title cannot be blank.'],
246 | ], $form->getErrors());
247 |
248 | $this->assertEquals([
249 | 'meta.title' => 'Title cannot be blank.',
250 | ], $form->getFirstErrors());
251 | }
252 | }
--------------------------------------------------------------------------------
/tests/_forms/MetaForm.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests\_forms;
11 |
12 | use yii\base\Model;
13 |
14 | class MetaForm extends Model
15 | {
16 | public $title;
17 | public $description;
18 |
19 | public function rules()
20 | {
21 | return [
22 | [['title'], 'required'],
23 | [['title', 'description'], 'string'],
24 | ];
25 | }
26 | }
--------------------------------------------------------------------------------
/tests/_forms/OnlyNestedProductForm.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests\_forms;
11 |
12 | use elisdn\compositeForm\CompositeForm;
13 |
14 | /**
15 | * @property MetaForm $meta
16 | */
17 | class OnlyNestedProductForm extends CompositeForm
18 | {
19 | public function __construct($config = [])
20 | {
21 | $this->meta = new MetaForm();
22 | parent::__construct($config);
23 | }
24 |
25 | protected function internalForms()
26 | {
27 | return ['meta'];
28 | }
29 | }
--------------------------------------------------------------------------------
/tests/_forms/ProductForm.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests\_forms;
11 |
12 | use elisdn\compositeForm\CompositeForm;
13 |
14 | /**
15 | * @property MetaForm $meta
16 | * @property ValueForm[] $values
17 | */
18 | class ProductForm extends CompositeForm
19 | {
20 | public $code;
21 | public $name;
22 |
23 | /**
24 | * @param integer $valuesCount
25 | * @param array $config
26 | */
27 | public function __construct($valuesCount, $config = [])
28 | {
29 | $this->meta = new MetaForm();
30 | $this->values = $valuesCount ? array_map(function () {
31 | return new ValueForm();
32 | }, range(1, $valuesCount)) : [];
33 | parent::__construct($config);
34 | }
35 |
36 | public function rules()
37 | {
38 | return [
39 | [['code'], 'required'],
40 | [['code', 'name'], 'string'],
41 | ];
42 | }
43 |
44 | protected function internalForms()
45 | {
46 | return ['meta', 'values'];
47 | }
48 | }
--------------------------------------------------------------------------------
/tests/_forms/ValueForm.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | namespace elisdn\compositeForm\tests\_forms;
11 |
12 | use yii\base\Model;
13 |
14 | class ValueForm extends Model
15 | {
16 | public $value;
17 |
18 | public function rules()
19 | {
20 | return [
21 | ['value', 'required'],
22 | ];
23 | }
24 | }
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
6 | * @license https://github.com/ElisDN/yii2-composite-form/blob/master/LICENSE.md
7 | * @link https://github.com/ElisDN/yii2-composite-form
8 | */
9 |
10 | defined('YII_DEBUG') or define('YII_DEBUG', true);
11 | defined('YII_ENV') or define('YII_ENV', 'test');
12 |
13 | require(__DIR__ . '/../vendor/autoload.php');
14 | require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
--------------------------------------------------------------------------------