mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-15 00:54:41 +00:00
(mvc, add constraint pattern) for https://github.com/opnsense/core/issues/272
Constraints hook into the default validations, new constraints should derive from BaseConstraint.
This commit contains the code needed to add constraints to our model and a unittest for the UniqueConstraint option.
Add the following to a model field, to force uniqueness for a combination of keys:
<Constraints>
<check001> <!--validation name, unique within this field-->
<message>number should be unique</message> <!--the message to output on validation failure -->
<type>OPNsense\Base\Constraints\UniqueConstraint</type> <!--the class to construct, derived from OPNsense\Base\Constraints\BaseConstraint -->
<addFields> <!--optional additional fields for the equation, defined in UniqueConstraint -->
<field1>optfield</field1>
</addFields>
</check001>
</Constraints>
This commit is contained in:
parent
b3f3a68b0d
commit
5361a64bd1
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2016 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
namespace OPNsense\Base\Constraints;
|
||||
|
||||
use \Phalcon\Validation\Validator;
|
||||
use \Phalcon\Validation\ValidatorInterface;
|
||||
|
||||
use \Phalcon\Validation\Message;
|
||||
|
||||
abstract class BaseConstraint extends Validator implements ValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @param \Phalcon\Validation $validator
|
||||
* @param $attribute
|
||||
*/
|
||||
protected function appendMessage(\Phalcon\Validation $validator, $attribute)
|
||||
{
|
||||
$message = $this->getOption('message');
|
||||
$name = $this->getOption('name');
|
||||
if (empty($message)) {
|
||||
$message = 'validation failure ' . get_class($this);
|
||||
}
|
||||
if (empty($name)) {
|
||||
$name = get_class($this);
|
||||
}
|
||||
$validator->appendMessage(new Message($message, $attribute, $name));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2016 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
namespace OPNsense\Base\Constraints;
|
||||
|
||||
|
||||
/**
|
||||
* Class UniqueConstraint, add a unique constraint to this field and optional additional fields.
|
||||
* @package OPNsense\Base\Constraints
|
||||
*/
|
||||
class UniqueConstraint extends BaseConstraint
|
||||
{
|
||||
/**
|
||||
* Executes validation
|
||||
*
|
||||
* @param \Phalcon\Validation $validator
|
||||
* @param string $attribute
|
||||
* @return boolean
|
||||
*/
|
||||
public function validate(\Phalcon\Validation $validator, $attribute)
|
||||
{
|
||||
$node = $this->getOption('node');
|
||||
$addFields = $this->getOption('addFields');
|
||||
$fieldSeparator = chr(10) . chr(0);
|
||||
if ($node) {
|
||||
$containerNode = $node;
|
||||
$nodeName = $node->getInternalXMLTagName();
|
||||
$parentNode = $node->getParentNode();
|
||||
$level = 0;
|
||||
// dive into parent
|
||||
while ($containerNode != null &&
|
||||
get_class($containerNode) != 'OPNsense\Base\FieldTypes\ArrayField') {
|
||||
$containerNode = $containerNode->getParentNode();
|
||||
$level++;
|
||||
}
|
||||
if ($containerNode != null && $level == 2) {
|
||||
// collect (additional) key fields
|
||||
$keyFields = array($nodeName);
|
||||
if (!empty($addFields)) {
|
||||
foreach ($addFields as $field) {
|
||||
$keyFields[] = $field;
|
||||
}
|
||||
}
|
||||
// calculate the key for this node
|
||||
$nodeKey = '';
|
||||
foreach ($keyFields as $field) {
|
||||
$nodeKey .= $fieldSeparator . $parentNode->$field;
|
||||
}
|
||||
// when an ArrayField is found in range, traverse nodes and compare keys
|
||||
foreach ($containerNode->__items as $item) {
|
||||
if ($item !== $parentNode) {
|
||||
$itemKey = '';
|
||||
foreach ($keyFields as $field) {
|
||||
$itemKey .= $fieldSeparator . $item->$field;
|
||||
}
|
||||
if ($itemKey == $nodeKey) {
|
||||
$this->appendMessage($validator, $attribute);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -47,6 +47,11 @@ abstract class BaseField
|
||||
*/
|
||||
protected $internalChildnodes = array();
|
||||
|
||||
/**
|
||||
* @var array constraints for this field, additional to fieldtype
|
||||
*/
|
||||
protected $internalConstraints = array();
|
||||
|
||||
/**
|
||||
* @var null pointer to parent
|
||||
*/
|
||||
@ -211,6 +216,15 @@ abstract class BaseField
|
||||
$this->internalParentNode = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* return this nodes parent (or null if not found)
|
||||
* @return null|BaseField
|
||||
*/
|
||||
public function getParentNode()
|
||||
{
|
||||
return $this->internalParentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect default getter to internal child nodes.
|
||||
* Implements the special attribute __items to return all items and __reference to identify the field in this model.
|
||||
@ -347,13 +361,35 @@ abstract class BaseField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch all additional validators
|
||||
*/
|
||||
private function getConstraintValidators()
|
||||
{
|
||||
$result = array();
|
||||
foreach ($this->internalConstraints as $name => $constraint) {
|
||||
if (!empty($constraint['type'])) {
|
||||
try {
|
||||
$constr_class = new \ReflectionClass($constraint['type']);
|
||||
if ($constr_class->getParentClass()->name == 'OPNsense\Base\Constraints\BaseConstraint') {
|
||||
$constraint['name'] = $name;
|
||||
$constraint['node'] = $this;
|
||||
$result[] = $constr_class->newInstance($constraint);
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
null; // ignore configuration errors, if the constraint can't be found, skip.
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* return field validators for this field
|
||||
* @return array returns validators for this field type (empty if none)
|
||||
*/
|
||||
public function getValidators()
|
||||
{
|
||||
$validators = array();
|
||||
$validators = $this->getConstraintValidators();
|
||||
if ($this->isEmptyAndRequired()) {
|
||||
$validators[] = new PresenceOf(array('message' => $this->internalValidationMessage)) ;
|
||||
}
|
||||
@ -561,6 +597,15 @@ abstract class BaseField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set additional constraints
|
||||
* @param $constraints
|
||||
*/
|
||||
public function setConstraints($constraints)
|
||||
{
|
||||
$this->internalConstraints = $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* apply change case to this node, called by setValue
|
||||
*/
|
||||
|
||||
@ -20,7 +20,18 @@
|
||||
<MaximumValue>65535</MaximumValue>
|
||||
<ValidationMessage>not a valid number</ValidationMessage>
|
||||
<Required>Y</Required>
|
||||
<Constraints>
|
||||
<check001>
|
||||
<message>number should be unique</message>
|
||||
<type>OPNsense\Base\Constraints\UniqueConstraint</type>
|
||||
<addFields>
|
||||
<field1>optfield</field1>
|
||||
</addFields>
|
||||
</check001>
|
||||
</Constraints>
|
||||
</number>
|
||||
<optfield type="TextField">
|
||||
</optfield>
|
||||
</item>
|
||||
</arraytypes>
|
||||
</items>
|
||||
|
||||
@ -234,4 +234,34 @@ class BaseModelTest extends \PHPUnit_Framework_TestCase
|
||||
$data = BaseModelTest::$model->arraytypes->item->getNodes();
|
||||
$this->assertEquals(count($data), 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetNodes
|
||||
* @expectedException \Phalcon\Validation\Exception
|
||||
* @expectedExceptionMessage number should be unique
|
||||
*/
|
||||
public function testConstraintsNok()
|
||||
{
|
||||
$count = 2;
|
||||
foreach (BaseModelTest::$model->arraytypes->item->__items as $nodeid => $node) {
|
||||
$count-- ;
|
||||
if ($count >= 0) {
|
||||
$node->number = 999;
|
||||
}
|
||||
}
|
||||
BaseModelTest::$model->serializeToConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testConstraintsNok
|
||||
*/
|
||||
public function testConstraintsOk()
|
||||
{
|
||||
$count = 1;
|
||||
foreach (BaseModelTest::$model->arraytypes->item->__items as $nodeid => $node) {
|
||||
$count++ ;
|
||||
$node->number = $count;
|
||||
}
|
||||
BaseModelTest::$model->serializeToConfig();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user