diff --git a/core/Form/Form.class.php b/core/Form/Form.class.php index f94abc892e..c914b0ef1d 100644 --- a/core/Form/Form.class.php +++ b/core/Form/Form.class.php @@ -38,6 +38,28 @@ public static function create() return new self; } + /** + * @return Form + */ + public function checkRules() + { + parent::checkRules(); + + foreach ($this->getPrimitiveList() as $name => $primitive) { + if ($primitive instanceof PrimitiveForm) { + $error = $this->getError($name); + $validated = $primitive->validate(); + if (!$error && !$validated) { + $this->markWrong($name); + } elseif ($error == Form::WRONG && $validated) { + $this->markGood($name); + } + } + } + + return $this; + } + public function getErrors() { return array_merge($this->errors, $this->violated); diff --git a/core/Form/Primitives/PrimitiveForm.class.php b/core/Form/Primitives/PrimitiveForm.class.php index 988f540271..278f9e686d 100644 --- a/core/Form/Primitives/PrimitiveForm.class.php +++ b/core/Form/Primitives/PrimitiveForm.class.php @@ -16,8 +16,20 @@ class PrimitiveForm extends BasePrimitive { protected $proto = null; + protected $needValidate = false; + private $composite = false; + /** + * @param bool $needValidate + * @return PrimitiveForm + */ + public function setNeedValidate($needValidate) + { + $this->needValidate = ($needValidate == true); + return $this; + } + /** * @throws WrongArgumentException * @return PrimitiveForm @@ -80,6 +92,15 @@ public function getProto() return $this->proto; } + public function validate() + { + $result = true; + if ($this->needValidate && $this->value) { + $result = $this->valiadateForm($this->value); + } + return $result; + } + /** * @throws WrongArgumentException * @return PrimitiveForm @@ -91,6 +112,18 @@ public function setValue($value) return parent::setValue($value); } + public function clean() + { + if (!$this->composite) + return parent::clean(); + + $this->raw = null; + $this->imported = false; + if ($this->value) { + $this->value->clean()->dropAllErrors(); + } + } + /** * @throws WrongArgumentException * @return PrimitiveForm @@ -116,7 +149,8 @@ public function exportValue() if (!$this->value) return null; - return $this->value->export(); + $default = $this->composite && $this->imported ? array() : null; + return $this->value->export() ?: $default; } public function getInnerErrors() @@ -139,10 +173,7 @@ public function unfilteredImport($scope) private function actualImport($scope, $importFiltering) { - if (!$this->proto) - throw new WrongStateException( - "no proto defined for PrimitiveForm '{$this->name}'" - ); + $this->assertSettuped(); if (!isset($scope[$this->name])) return null; @@ -168,5 +199,29 @@ private function actualImport($scope, $importFiltering) return true; } + + protected final function valiadateForm(Form $form) + { + if ($this->proto instanceof EntityProto) { + return $this->proto->validate(null, $form); + } else { + return !$form->checkRules()->getErrors(); + } + } + + protected function assertSettuped() + { + if ($this->composite) { + if (!$this->value) + throw new WrongStateException( + "setValue(Form) first if you choosed composite PrimitiveForm '{$this->name}'" + ); + } else { + if (!$this->proto) + throw new WrongStateException( + "no proto defined for PrimitiveForm '{$this->name}'" + ); + } + } } ?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveFormsList.class.php b/core/Form/Primitives/PrimitiveFormsList.class.php index a8c5f90787..96fbc0d507 100644 --- a/core/Form/Primitives/PrimitiveFormsList.class.php +++ b/core/Form/Primitives/PrimitiveFormsList.class.php @@ -35,6 +35,18 @@ public function setComposite($composite = true) ); } + public function validate() + { + $result = true; + if ($this->needValidate && $this->value) { + foreach ($this->value as $form) { + //every form must be validated, do not do 'continue' here if false result + $result = $result && $this->valiadateForm($form); + } + } + return $result; + } + public function getInnerErrors() { $result = array(); diff --git a/main/EntityProto/EntityProto.class.php b/main/EntityProto/EntityProto.class.php index 776767926b..cf61f4bb7e 100644 --- a/main/EntityProto/EntityProto.class.php +++ b/main/EntityProto/EntityProto.class.php @@ -40,6 +40,13 @@ public function checkConstraints( return $this; } + public function checkPostConstraints( + $object, Form $form, $previousObject = null + ) + { + return $this; + } + public function isAbstract() { return false; @@ -66,14 +73,16 @@ final public function validate( $object, $form, $previousObject = null ) { - if (is_array($object)) { + if (($object !== null && is_array($object)) || ($form !== null && is_array($form))) { return $this->validateList($object, $form, $previousObject); } - Assert::isInstance($object, $this->className()); + if ($object && $this->className()) { + Assert::isInstance($object, $this->className()); + } Assert::isInstance($form, 'Form'); - if ($previousObject) + if ($previousObject && $this->className()) Assert::isInstance($previousObject, $this->className()); if ($this->baseProto()) @@ -88,31 +97,29 @@ final public function validateSelf( ) { $this->checkConstraints($object, $form, $previousObject); - - $getter = new ObjectGetter($this, $object); + $getter = $object + ? $this->getValidateObjectGetter($object) + : null; $previousGetter = $previousObject - ? new ObjectGetter($this, $previousObject) + ? $this->getValidateObjectGetter($previousObject) : null; - foreach ($this->getFormMapping() as $id => $primitive) { + foreach ($this->getFormMapping() as $primitiveName => $primitive) { if ($primitive instanceof PrimitiveForm) { $proto = $primitive->getProto(); $childForm = $form->getValue($primitive->getName()); - $child = $getter->get($id); + $child = $getter ? $getter->get($primitiveName) : null; $previousChild = $previousGetter - ? $previousGetter->get($id) + ? $previousGetter->get($primitiveName) : null; - $childResult = true; - if ( - $child - && !$proto->validate( + !$proto->validate( $child, $childForm, $previousChild ) ) { @@ -121,6 +128,8 @@ final public function validateSelf( } } + $this->checkPostConstraints($object, $form, $previousObject); + $errors = $form->getErrors(); return empty($errors); @@ -130,7 +139,8 @@ final public function validateList( $objectsList, $formsList, $previousObjectsList = null ) { - Assert::isEqual(count($objectsList), count($formsList)); + if ($objectsList !== null) + Assert::isEqual(count($objectsList), count($formsList)); reset($formsList); @@ -145,11 +155,14 @@ final public function validateList( $result = true; $previousObject = null; + $object = null; - foreach ($objectsList as $object) { + foreach ($formsList as $form) { - $form = current($formsList); - next($formsList); + if ($objectsList) { + $object = current($objectsList); + next($formsList); + } if ($previousObjectsList) { $previousObject = current($previousObjectsList); @@ -224,5 +237,13 @@ final public function getPrimitive($name) return $result; } + + /** + * @param any $object + * @return ObjectGetter + */ + protected function getValidateObjectGetter($object) { + return new ObjectGetter($this, $object); + } } ?> \ No newline at end of file diff --git a/test/core/PrimitiveFormTest.class.php b/test/core/PrimitiveFormTest.class.php new file mode 100644 index 0000000000..27e036a78c --- /dev/null +++ b/test/core/PrimitiveFormTest.class.php @@ -0,0 +1,205 @@ +required(), + Primitive::integer('age')->setMin(0)->required(), + Primitive::boolean('capital'), + ); + } + + public function checkPostConstraints($object, Form $form, $previousObject = null) + { + $name = $form->getValue('name'); + $capital = $form->getValue('capital'); + if ($name == 'Moscow' && !$capital) { + $form->markWrong('capital'); + } elseif ($name != 'Moscow' && $capital) { + $form->markWrong('name'); + } + return $this; + } + } + + /** + * @group pf + */ + final class PrimitiveFormTest extends TestCase + { + /** + * @group pf1 + */ + public function testWithEntityProto() + { + $prm = Primitive::form('city')-> + ofProto($this->getEntityProto())-> + setNeedValidate(true); + + $this->primitiveFormCheck($prm); + } + + /** + * @group pf2 + */ + public function testWithCompositeFormWithoutEntityProto() + { + $entityProto = $this->getEntityProto(); + $ruleCallback = CallbackLogicalObject::create( + function (Form $form) use ($entityProto) { + $entityProto->checkPostConstraints(null, $form); + return true; + } + ); + $form = $this->getEntityProto()->makeForm()-> + addRule('rule', $ruleCallback); + + //primitive with composite custom form + $prm = Primitive::form('city')-> + setNeedValidate(true)-> + setComposite(true)-> + setValue($form); + + $this->primitiveFormCheck($prm); + } + + /** + * @group pf3 + */ + public function testWithFormAndErrors() + { + $prm = Primitive::form('capital')-> + ofProto($this->getEntityProto())-> + setNeedValidate(true)-> + required(); + $prmList = Primitive::formsList('cities')-> + setNeedValidate(true)-> + ofProto($this->getEntityProto()); + + $scope = $this->getMultiCityScope(); + + $form = Form::create()-> + add($prm)-> + add($prmList); + + //success import + $form->import($scope); + $this->assertEquals(array(), $form->getErrors()); + $this->assertEquals(array(), $form->getInnerErrors()); + $form->checkRules(); + $this->assertEquals(array(), $form->getErrors()); + $this->assertEquals(array(), $form->getInnerErrors()); + $this->assertEquals($scope, $form->export()); + + //error in capital + $form->clean()->dropAllErrors(); + $scope['capital']['name'] = 'NewYork'; + $scope['cities'][0]['name'] = 'Moscow'; + $form->import($scope); + $this->assertEquals(array(), $form->getErrors()); + $this->assertEquals(array(), $form->getInnerErrors()); + $form->checkRules(); + $this->assertEquals(array('capital' => Form::WRONG, 'cities' => Form::WRONG), $form->getErrors()); + $this->assertEquals( + array( + 'capital' => array('name' => Form::WRONG), + 'cities' => array(0 => array('capital' => Form::WRONG)), + ), + $form->getInnerErrors() + ); + + //missing capital and + $form->clean()->dropAllErrors(); + $scope = $this->getMultiCityScope(); + unset($scope['capital'], $scope['cities'][1]['age']); + $form->import($scope); + $this->assertEquals(array('capital' => Form::MISSING, 'cities' => Form::WRONG), $form->getErrors()); + $this->assertEquals( + array( + 'capital' => Form::MISSING, + 'cities' => array(1 => array('age' => Form::MISSING)) + ), + $form->getInnerErrors() + ); + } + + private function primitiveFormCheck(PrimitiveForm $prm) + { + $scope = $this->getOneCityScope(); + + //success import + $this->assertTrue($prm->import($scope)); + $this->assertTrue($prm->validate()); + $prm->setNeedValidate(true); + $this->assertTrue($prm->validate()); + $this->assertEmpty($prm->getInnerErrors()); + $this->assertEquals($scope['city'], $prm->exportValue()); + + //wrong city name + $scope['city']['name'] = 'Novgorod'; + $this->assertTrue($prm->import($scope)); + $this->assertFalse($prm->validate()); + $this->assertEquals(array('name' => Form::WRONG), $prm->getInnerErrors()); + + //cleaning + $prm->clean(); + $this->assertEquals(array(), $prm->getInnerErrors()); + $this->assertEquals(null, $prm->exportValue()); + + unset($scope['city']['capital'], $scope['city']['age']); + $this->assertFalse($prm->import($scope)); + $this->assertFalse($prm->validate()); + $this->assertEquals(array('age' => Form::MISSING), $prm->getInnerErrors()); + } + + private function getMultiCityScope() + { + return array( + 'capital' => $this->getCityScope(), + 'cities' => array( + array( + 'name' => 'Novgorod', + 'age' => (Date::create('now')->getYear() - 859), + ), + array( + 'name' => 'Murmansk', + 'age' => (Date::create('now')->getYear() - 1916), + ), + ), + ); + } + + private function getOneCityScope() + { + return array( + 'city' => $this->getCityScope(), + ); + } + + private function getCityScope() + { + return array( + 'name' => 'Moscow', + 'age' => (Date::create('now')->getYear() - 1147), + 'capital' => true, + ); + } + + /** + * @return EntityProto + */ + private function getEntityProto() + { + return PrimitiveFormTestEntityProto::me(); + } + }