Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Expression assertion #23

Closed

Conversation

nikolaposa
Copy link
Contributor

I think it would be useful to have possibility for defining assertions in this way, as those can be very easily persisted or serialized in contrast to CallbackAssertion for example. Also, having such dynamic and generic type of assertion is much more convenient when it comes to creating some permissions CRUD functionality.

Example usage

ExpressionAssertion::fromArray([
    'left' => [ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'role.username'],
    'operator' => '=',
    'right' => 'test',
]);

ExpressionAssertion::fromArray([
    'left' => [ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'role.age'],
    'operator' => '>',
    'right' => 20,
]);

ExpressionAssertion::fromArray([
    'left' => [ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'role.adult'], //resolves to isAdult() or getAdult() method on Role object
    'operator' => '=',
    'right' => true,
]);

ExpressionAssertion::fromArray([
    'left' => [ExpressionAssertion::OPERAND_CONTEXT_PROPERTY => 'resource.short_description'], //resolves to either getShortDescription() method or short_description property
    'operator' => 'regex',
    'right' => '/ipsum/',
]);

CS fixes.

Testing ExpressionAssertion.

'regex' operator instead of 'contains'.

Exception in case that context object property cannot be resolved.

Exception in case that context object property cannot be resolved.

Exception in case that context object property cannot be resolved.
@weierophinney weierophinney added this to the 2.7.0 milestone May 1, 2018
@weierophinney weierophinney changed the base branch from master to develop May 1, 2018 17:30
weierophinney added a commit to weierophinney/zend-permissions-acl that referenced this pull request May 1, 2018
const OPERATOR_GT = '>';
const OPERATOR_GTE = '>=';
const OPERATOR_IN = 'in';
const OPERATOR_NIN = 'nin';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one would make more sense as !in, making it more akin to !=. I'll make that change on merge.

/**
* @var array
*/
private $assertContext = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed. assert() should pass the context to methods that need it.

The reason is concurrency. In async systems, if this value gets reset in another process, it can affect the operands in the current process. By passing it around instead, the context of the current process can never be changed.

self::OPERATOR_REGEX,
];

private function __construct($left, $operator, $right)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There really needs to be documentation of what constitutes valid operands.

* @param string $operator
* @param mixed $right
* @return self
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should indicate exceptions that can be thrown by validateOperand() and validateOperator().

/**
* @param array $expression
* @throws InvalidAssertionException
* @return self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here, and also indicate the more specific exception this method raises.

continue;
}

return $object->$accessor();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above could be more simply written as:

if (method_exists($object, $accessor)) {
    return $object->accessor();
}

}

if (!property_exists($object, $field)) {
throw new \RuntimeException('Object property cannot be resolved');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using a global exception case here, versus the package-specific one elsewhere?

case self::OPERATOR_EQ :
return $left == $right;
case self::OPERATOR_NEQ :
return $left != $right;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiousity, why are these using the non-strict forms? Do you think we should have additional operators for strict in/equality?

throw new RuntimeException(sprintf(
'Unsupported expression assertion operator: %s',
$operator
));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, there is no possible way to get to the default case, as you've validated the operator previously.

return $object->$accessor();
}

if (!property_exists($object, $field)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't do what you think it does. Or, rather, it has an unintentional side-effect you may not be aware of.

Starting in PHP 5.3, property_exists started returning true regardless of visibility. This means that the above will return true even if the property is not public. Which makes line 227 a potential error. The only way to solve this is via reflection.

@weierophinney
Copy link
Member

I've incorporated all feedback in #36; I would have pushed to your branch, but you've locked it. 😄

weierophinney added a commit that referenced this pull request May 1, 2018
@michalbundyra
Copy link
Member

@weierophinney It can be closed now, as #36 is merged and 2.7.0 is released.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants