feat(cloudron): add tirreno package artifacts

- Add CloudronStack/output/CloudronPackages-Artifacts/tirreno/ directory and its contents
- Includes package manifest, Dockerfile, source code, documentation, and build artifacts
- Add tirreno-1761840148.tar.gz as a build artifact
- Add tirreno-cloudron-package-1761841304.tar.gz as the Cloudron package
- Include all necessary files for the tirreno Cloudron package

This adds the complete tirreno Cloudron package artifacts to the repository.
This commit is contained in:
2025-10-30 11:43:06 -05:00
parent 0ce353ea9d
commit 91d52d2de5
1692 changed files with 202851 additions and 0 deletions
@@ -0,0 +1,47 @@
name: Unit Tests
on:
push:
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- '7.4'
- '8.0'
- '8.1'
# - '8.2'
steps:
- uses: actions/checkout@v3
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-${{ matrix.php-version }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.php-version }}-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run test suite
run: vendor/bin/phpunit
@@ -0,0 +1,3 @@
vendor
composer.lock
.phpunit.result.cache
@@ -0,0 +1,21 @@
risky: true
preset: symfony
enabled:
- align_double_arrow
- native_function_invocation
- ordered_use
- strict
disabled:
- native_function_invocation_symfony
- no_superfluous_phpdoc_tags_symfony
- pow_to_exponentiation
- pre_increment
- unalign_double_arrow
- yoda_style
finder:
name:
- "*.php"
@@ -0,0 +1,31 @@
# Contributions welcome!
### Here's a quick guide:
1. [Fork the repo on GitHub](https://github.com/bobthecow/Ruler).
2. Run the test suite. We only take pull requests with passing tests, and it's great to know that you have a clean slate. Make sure you have PHPUnit 3.5+, then run `phpunit` from the project directory.
3. Add tests for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, add a test!
4. Make the tests pass.
5. Push your fork to GitHub and submit a pull request.
### You can do some things to increase the chance that your pull request is accepted the first time:
* Submit one pull request per fix or feature.
* To help with that, do all your work in a feature branch (e.g. `feature/my-alsome-feature`).
* Follow the conventions you see used in the project.
* Use `phpcs --standard=PSR2` to check your changes against the coding standard.
* Write tests that fail without your code, and pass with it.
* Update any documentation: docblocks, README, examples, etc.
### Ruler follows the PSR-* coding standards:
* [PSR-0: Class and file naming conventions](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
* [PSR-1: Basic coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
* [PSR-2: Coding style guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2011 OpenSky Project Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,444 @@
Ruler
=====
Ruler is a simple stateless production rules engine for PHP 5.3+.
[![Package version](http://img.shields.io/packagist/v/ruler/ruler.svg?style=flat-square)](https://packagist.org/packages/ruler/ruler)
[![Build status](https://img.shields.io/github/workflow/status/bobthecow/Ruler/Unit%20Tests/main.svg?style=flat-square)](https://github.com/bobthecow/Ruler/actions?query=branch:main)
[![StyleCI](https://styleci.io/repos/1906921/shield)](https://styleci.io/repos/1906921)
Ruler has an easy, straightforward DSL
--------------------------------------
... provided by the RuleBuilder:
```php
$rb = new RuleBuilder;
$rule = $rb->create(
$rb->logicalAnd(
$rb['minNumPeople']->lessThanOrEqualTo($rb['actualNumPeople']),
$rb['maxNumPeople']->greaterThanOrEqualTo($rb['actualNumPeople'])
),
function() {
echo 'YAY!';
}
);
$context = new Context([
'minNumPeople' => 5,
'maxNumPeople' => 25,
'actualNumPeople' => fn() => 6,
]);
$rule->execute($context); // "Yay!"
```
### Of course, if you're not into the whole brevity thing
... you can use it without a RuleBuilder:
```php
$actualNumPeople = new Variable('actualNumPeople');
$rule = new Rule(
new Operator\LogicalAnd([
new Operator\LessThanOrEqualTo(new Variable('minNumPeople'), $actualNumPeople),
new Operator\GreaterThanOrEqualTo(new Variable('maxNumPeople'), $actualNumPeople)
]),
function() {
echo 'YAY!';
}
);
$context = new Context([
'minNumPeople' => 5,
'maxNumPeople' => 25,
'actualNumPeople' => fn() => 6,
]);
$rule->execute($context); // "Yay!"
```
But that doesn't sound too fun, does it?
Things you can do with your Ruler
---------------------------------
### Compare things
```php
// These are Variables. They'll be replaced by terminal values during Rule evaluation.
$a = $rb['a'];
$b = $rb['b'];
// Here are bunch of Propositions. They're not too useful by themselves, but they
// are the building blocks of Rules, so you'll need 'em in a bit.
$a->greaterThan($b); // true if $a > $b
$a->greaterThanOrEqualTo($b); // true if $a >= $b
$a->lessThan($b); // true if $a < $b
$a->lessThanOrEqualTo($b); // true if $a <= $b
$a->equalTo($b); // true if $a == $b
$a->notEqualTo($b); // true if $a != $b
$a->stringContains($b); // true if strpos($b, $a) !== false
$a->stringDoesNotContain($b); // true if strpos($b, $a) === false
$a->stringContainsInsensitive($b); // true if stripos($b, $a) !== false
$a->stringDoesNotContainInsensitive($b); // true if stripos($b, $a) === false
$a->startsWith($b); // true if strpos($b, $a) === 0
$a->startsWithInsensitive($b); // true if stripos($b, $a) === 0
$a->endsWith($b); // true if strpos($b, $a) === len($a) - len($b)
$a->endsWithInsensitive($b); // true if stripos($b, $a) === len($a) - len($b)
$a->sameAs($b); // true if $a === $b
$a->notSameAs($b); // true if $a !== $b
```
### Math even more things
```php
$c = $rb['c'];
$d = $rb['d'];
// Mathematical operators are a bit different. They're not Propositions, so
// they don't belong in rules all by themselves, but they can be combined
// with Propositions for great justice.
$rb['price']
->add($rb['shipping'])
->greaterThanOrEqualTo(50)
// Of course, there are more.
$c->add($d); // $c + $d
$c->subtract($d); // $c - $d
$c->multiply($d); // $c * $d
$c->divide($d); // $c / $d
$c->modulo($d); // $c % $d
$c->exponentiate($d); // $c ** $d
$c->negate(); // -$c
$c->ceil(); // ceil($c)
$c->floor(); // floor($c)
```
### Reason about sets
```php
$e = $rb['e']; // These should both be arrays
$f = $rb['f'];
// Manipulate sets with set operators
$e->union($f);
$e->intersect($f);
$e->complement($f);
$e->symmetricDifference($f);
$e->min();
$e->max();
// And use set Propositions to include them in Rules.
$e->containsSubset($f);
$e->doesNotContainSubset($f);
$e->setContains($a);
$e->setDoesNotContain($a);
```
### Combine Rules
```php
// Create a Rule with an $a == $b condition
$aEqualsB = $rb->create($a->equalTo($b));
// Create another Rule with an $a != $b condition
$aDoesNotEqualB = $rb->create($a->notEqualTo($b));
// Now combine them for a tautology!
// (Because Rules are also Propositions, they can be combined to make MEGARULES)
$eitherOne = $rb->create($rb->logicalOr($aEqualsB, $aDoesNotEqualB));
// Just to mix things up, we'll populate our evaluation context with completely
// random values...
$context = new Context([
'a' => rand(),
'b' => rand(),
]);
// Hint: this is always true!
$eitherOne->evaluate($context);
```
### Combine more Rules
```php
$rb->logicalNot($aEqualsB); // The same as $aDoesNotEqualB :)
$rb->logicalAnd($aEqualsB, $aDoesNotEqualB); // True if both conditions are true
$rb->logicalOr($aEqualsB, $aDoesNotEqualB); // True if either condition is true
$rb->logicalXor($aEqualsB, $aDoesNotEqualB); // True if only one condition is true
```
### `evaluate` and `execute` Rules
`evaluate()` a Rule with Context to figure out whether it is true.
```php
$context = new Context([
'userName' => fn() => $_SESSION['userName'] ?? null,
]);
$userIsLoggedIn = $rb->create($rb['userName']->notEqualTo(null));
if ($userIsLoggedIn->evaluate($context)) {
// Do something special for logged in users!
}
```
If a Rule has an action, you can `execute()` it directly and save yourself a
couple of lines of code.
```php
$hiJustin = $rb->create(
$rb['userName']->equalTo('bobthecow'),
function() {
echo "Hi, Justin!";
}
);
$hiJustin->execute($context); // "Hi, Justin!"
```
### Even `execute` a whole grip of Rules at once
```php
$hiJon = $rb->create(
$rb['userName']->equalTo('jwage'),
function() {
echo "Hey there Jon!";
}
);
$hiEveryoneElse = $rb->create(
$rb->logicalAnd(
$rb->logicalNot($rb->logicalOr($hiJustin, $hiJon)), // The user is neither Justin nor Jon
$userIsLoggedIn // ... but a user nonetheless
),
function() use ($context) {
echo sprintf("Hello, %s", $context['userName']);
}
);
$rules = new RuleSet([$hiJustin, $hiJon, $hiEveryoneElse]);
// Let's add one more Rule, so non-authenticated users have a chance to log in
$redirectForAuthentication = $rb->create($rb->logicalNot($userIsLoggedIn), function() {
header('Location: /login');
exit;
});
$rules->addRule($redirectForAuthentication);
// Now execute() all true Rules.
//
// Astute readers will note that the Rules we defined are mutually exclusive, so
// at most one of them will evaluate to true and execute an action...
$rules->executeRules($context);
```
Dynamically populate your evaluation Context
--------------------------------------------
Several of our examples above use static values for the context Variables. While
that's good for examples, it's not as useful in the Real World. You'll probably
want to evaluate Rules based on all sorts of things...
You can think of the Context as a ViewModel for Rule evaluation. You provide the
static values, or even code for lazily evaluating the Variables needed by your
Rules.
```php
$context = new Context;
// Some static values...
$context['reallyAnnoyingUsers'] = ['bobthecow', 'jwage'];
// You'll remember this one from before
$context['userName'] = fn() => $_SESSION['userName'] ?? null;
// Let's pretend you have an EntityManager named `$em`...
$context['user'] = function() use ($em, $context) {
if ($userName = $context['userName']) {
return $em->getRepository('Users')->findByUserName($userName);
}
};
$context['orderCount'] = function() use ($em, $context) {
if ($user = $context['user']) {
return $em->getRepository('Orders')->findByUser($user)->count();
}
return 0;
};
```
Now you have all the information you need to make Rules based on Order count or
the current User, or any number of other crazy things. I dunno, maybe this is
for a shipping price calculator?
> If the current User has placed 5 or more orders, but isn't "really annoying",
> give 'em free shipping.
```php
$rb->create(
$rb->logicalAnd(
$rb['orderCount']->greaterThanOrEqualTo(5),
$rb['reallyAnnoyingUsers']->doesNotContain($rb['userName'])
),
function() use ($shipManager, $context) {
$shipManager->giveFreeShippingTo($context['user']);
}
);
```
Access variable properties
--------------------------
As an added bonus, Ruler lets you access properties, methods and offsets on your
Context Variable values. This can come in really handy.
Say we wanted to log the current user's name if they are an administrator:
```php
// Reusing our $context from the last example...
// We'll define a few context variables for determining what roles a user has,
// and their full name:
$context['userRoles'] = function() use ($em, $context) {
if ($user = $context['user']) {
return $user->roles();
} else {
// return a default "anonymous" role if there is no current user
return ['anonymous'];
}
};
$context['userFullName'] = function() use ($em, $context) {
if ($user = $context['user']) {
return $user->fullName;
}
};
// Now we'll create a rule to write the log message
$rb->create(
$rb->logicalAnd(
$userIsLoggedIn,
$rb['userRoles']->contains('admin')
),
function() use ($context, $logger) {
$logger->info(sprintf("Admin user %s did a thing!", $context['userFullName']));
}
);
```
That was a bit of a mouthful. Instead of creating context Variables for
everything we might need to access in a rule, we can use VariableProperties, and
their convenient RuleBuilder interface:
```php
// We can skip over the Context Variable building above. We'll simply set our,
// default roles on the VariableProperty itself, then go right to writing rules:
$rb['user']['roles'] = ['anonymous'];
$rb->create(
$rb->logicalAnd(
$userIsLoggedIn,
$rb['user']['roles']->contains('admin')
),
function() use ($context, $logger) {
$logger->info(sprintf("Admin user %s did a thing!", $context['user']['fullName']);
}
);
```
If the parent Variable resolves to an object, and this VariableProperty name is
"bar", it will do a prioritized lookup for:
1. A method named `bar`
2. A public property named `bar`
3. ArrayAccess + offsetExists named `bar`
If the Variable resolves to an array it will return:
1. Array index `bar`
If none of the above are true, it will return the default value for this
VariableProperty.
Add your own Operators
----------------------
If none of the default Ruler Operators fit your needs, you can write your own! Just define
additional operators like this:
```php
namespace My\Ruler\Operators;
use Ruler\Context;
use Ruler\Operator\VariableOperator;
use Ruler\Proposition;
use Ruler\Value;
class ALotGreaterThan extends VariableOperator implements Proposition
{
public function evaluate(Context $context): bool
{
list($left, $right) = $this->getOperands();
$value = $right->prepareValue($context)->getValue() * 10;
return $left->prepareValue($context)->greaterThan(new Value($value));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
```
Then you can use them with RuleBuilder like this:
```php
$rb->registerOperatorNamespace('My\Ruler\Operators');
$rb->create(
$rb['a']->aLotGreaterThan(10);
);
```
But that's not all...
---------------------
Check out [the test suite](https://github.com/bobthecow/Ruler/blob/master/tests/Ruler/Test/Functional/RulerTest.php)
for more examples (and some hot CS 320 combinatorial logic action).
Ruler is plumbing. Bring your own porcelain.
============================================
Ruler doesn't bother itself with where Rules come from. Maybe you have a RuleManager
wrapped around an ORM or ODM. Perhaps you write a simple DSL and parse static files.
Whatever your flavor, Ruler will handle the logic.
@@ -0,0 +1,30 @@
{
"name": "ruler/ruler",
"description": "A simple stateless production rules engine for modern PHP.",
"keywords": ["rules", "engine"],
"homepage": "https://github.com/bobthecow/Ruler",
"license": "MIT",
"require": {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^8.5.12 | ^9"
},
"autoload": {
"psr-4": {
"Ruler\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Ruler\\Test\\": "tests/"
}
},
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com/"
}
]
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Ruler Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
@@ -0,0 +1,237 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* Copyright (c) 2009 Fabien Potencier
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Ruler;
/**
* Ruler Context.
*
* The Context contains facts with which to evaluate a Rule or other Proposition.
*
* Derived from Pimple, by Fabien Potencier:
*
* https://github.com/fabpot/Pimple
*
* @author Fabien Potencier
* @author Justin Hileman <justin@justinhileman.info>
*/
class Context implements \ArrayAccess
{
private array $keys = [];
private array $values = [];
private array $frozen = [];
private array $raw = [];
private $shared;
private $protected;
/**
* Context constructor.
*
* Optionally, bootstrap the context by passing an array of fact names and
* values.
*
* @param array $values (default: array())
*/
public function __construct(array $values = [])
{
$this->shared = new \SplObjectStorage();
$this->protected = new \SplObjectStorage();
foreach ($values as $key => $value) {
$this->offsetSet($key, $value);
}
}
/**
* Check if a fact is defined.
*
* @param string $name The unique name for the fact
*/
public function offsetExists($name): bool
{
return isset($this->keys[$name]);
}
/**
* Get the value of a fact.
*
* @param string $name The unique name for the fact
*
* @throws \InvalidArgumentException if the name is not defined
*
* @return mixed The resolved value of the fact
*/
#[\ReturnTypeWillChange]
public function offsetGet($name)
{
if (!$this->offsetExists($name)) {
throw new \InvalidArgumentException(\sprintf('Fact "%s" is not defined.', $name));
}
$value = $this->values[$name];
// If the value is already frozen, or if it's not callable, or if it's protected, return the raw value
if (isset($this->frozen[$name]) || !\is_object($value) || $this->protected->contains($value) || !$this->isCallable($value)) {
return $value;
}
// If this is a shared value, resolve, freeze, and return the result
if ($this->shared->contains($value)) {
$this->frozen[$name] = true;
$this->raw[$name] = $value;
return $this->values[$name] = $value($this);
}
// Otherwise, resolve and return the result
return $value($this);
}
/**
* Set a fact name and value.
*
* A fact will be lazily evaluated if it is a Closure or invokable object.
* To define a fact as a literal callable, use Context::protect.
*
* @param string $name The unique name for the fact
* @param mixed $value The value or a closure to lazily define the value
*
* @throws \RuntimeException if a frozen fact overridden
*/
public function offsetSet($name, $value): void
{
if (isset($this->frozen[$name])) {
throw new \RuntimeException(\sprintf('Cannot override frozen fact "%s".', $name));
}
$this->keys[$name] = true;
$this->values[$name] = $value;
}
/**
* Unset a fact.
*
* @param string $name The unique name for the fact
*/
public function offsetUnset($name): void
{
if ($this->offsetExists($name)) {
$value = $this->values[$name];
if (\is_object($value)) {
$this->shared->detach($value);
$this->protected->detach($value);
}
unset($this->keys[$name], $this->values[$name], $this->frozen[$name], $this->raw[$name]);
}
}
/**
* Define a fact as "shared". This lazily evaluates and stores the result
* of the callable for the scope of this Context instance.
*
* @param callable $callable A fact callable to share
*
* @throws \InvalidArgumentException if the callable is not a Closure or invokable object
*
* @return callable The passed callable
*/
public function share($callable)
{
if (!$this->isCallable($callable)) {
throw new \InvalidArgumentException('Value is not a Closure or invokable object.');
}
$this->shared->attach($callable);
return $callable;
}
/**
* Protect a callable from being interpreted as a lazy fact definition.
*
* This is useful when you want to store a callable as the literal value of
* a fact.
*
* @param callable $callable A callable to protect from being evaluated
*
* @throws \InvalidArgumentException if the callable is not a Closure or invokable object
*
* @return callable The passed callable
*/
public function protect($callable)
{
if (!$this->isCallable($callable)) {
throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
}
$this->protected->attach($callable);
return $callable;
}
/**
* Get a fact or the closure defining a fact.
*
* @param string $name The unique name for the fact
*
* @throws \InvalidArgumentException if the name is not defined
*
* @return mixed The value of the fact or the closure defining the fact
*/
public function raw($name)
{
if (!$this->offsetExists($name)) {
throw new \InvalidArgumentException(\sprintf('Fact "%s" is not defined.', $name));
}
if (isset($this->frozen[$name])) {
return $this->raw[$name];
}
return $this->values[$name];
}
/**
* Get all defined fact names.
*/
public function keys(): array
{
return \array_keys($this->keys);
}
/**
* Check whether a value is a Closure or invokable object.
*
* @param mixed $callable
*/
protected function isCallable($callable): bool
{
return \is_object($callable) && \is_callable($callable);
}
}
@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* @author Jordan Raub <jordan@raub.me>
*/
abstract class Operator
{
public const UNARY = 'UNARY';
public const BINARY = 'BINARY';
public const MULTIPLE = 'MULTIPLE';
protected $operands = [];
/**
* @param Proposition|VariableOperand ...$operands
*/
public function __construct(...$operands)
{
foreach ($operands as $operand) {
$this->addOperand($operand);
}
}
public function getOperands(): array
{
switch ($this->getOperandCardinality()) {
case self::UNARY:
if (1 !== \count($this->operands)) {
throw new \LogicException(static::class.' takes only 1 operand');
}
break;
case self::BINARY:
if (2 !== \count($this->operands)) {
throw new \LogicException(static::class.' takes 2 operands');
}
break;
case self::MULTIPLE:
if (0 === \count($this->operands)) {
throw new \LogicException(static::class.' takes at least 1 operand');
}
break;
}
return $this->operands;
}
/**
* @param Proposition|VariableOperand $operand
*/
abstract public function addOperand($operand): void;
abstract protected function getOperandCardinality();
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* An Addition Arithmetic Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Addition extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->add($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Ceil Math Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Ceil extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $operand */
[$operand] = $this->getOperands();
return new Value($operand->prepareValue($context)->ceil());
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Set;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Complement Set Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Complement extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
$complement = null;
/** @var VariableOperand $operand */
foreach ($this->getOperands() as $operand) {
if (!$complement instanceof Set) {
$complement = $operand->prepareValue($context)->getSet();
} else {
$set = $operand->prepareValue($context)->getSet();
$complement = $complement->complement($set);
}
}
return $complement;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A ContainsSubset comparison operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class ContainsSubset extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->getSet()
->containsSubset($right->prepareValue($context)->getSet());
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Division Arithmetic Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Division extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->divide($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A DoesNotContainSubset comparison operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class DoesNotContainSubset extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->getSet()
->containsSubset($right->prepareValue($context)->getSet()) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A EndsWith comparison operator.
*
* @author Cornel Les <thebogu@gmail.com>
*/
class EndsWith extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->endsWith($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A EndsWith insensitive comparison operator.
*
* @author Cornel Les <thebogu@gmail.com>
*/
class EndsWithInsensitive extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->endsWith($right->prepareValue($context), true);
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* An EqualTo comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class EqualTo extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->equalTo($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* An Exponentiate Math Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Exponentiate extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->exponentiate($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Floor Math Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Floor extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $operand */
[$operand] = $this->getOperands();
return new Value($operand->prepareValue($context)->floor());
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A GreaterThan comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class GreaterThan extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->greaterThan($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A GreaterThanOrEqualTo comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class GreaterThanOrEqualTo extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->lessThan($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Set;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Set Intersection Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Intersect extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
$intersect = null;
/** @var VariableOperand $operand */
foreach ($this->getOperands() as $operand) {
if (!$intersect instanceof Set) {
$intersect = $operand->prepareValue($context)->getSet();
} else {
$set = $operand->prepareValue($context)->getSet();
$intersect = $intersect->intersect($set);
}
}
return $intersect;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A LessThan comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LessThan extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->lessThan($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A LessThanOrEqualTo comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LessThanOrEqualTo extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->greaterThan($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
/**
* A logical AND operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LogicalAnd extends LogicalOperator
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var Proposition $operand */
foreach ($this->getOperands() as $operand) {
if ($operand->evaluate($context) === false) {
return false;
}
}
return true;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
/**
* A logical NOT operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LogicalNot extends LogicalOperator
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var Proposition $operand */
[$operand] = $this->getOperands();
return !$operand->evaluate($context);
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Proposition;
/**
* Logical operator base class.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
abstract class LogicalOperator extends PropositionOperator implements Proposition
{
/**
* array of propositions.
*
* @param Proposition[] $props
*/
public function __construct(array $props = [])
{
foreach ($props as $operand) {
$this->addOperand($operand);
}
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
/**
* A logical OR operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LogicalOr extends LogicalOperator
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var Proposition $operand */
foreach ($this->getOperands() as $operand) {
if ($operand->evaluate($context) === true) {
return true;
}
}
return false;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
/**
* A logical XOR operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class LogicalXor extends LogicalOperator
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
$true = 0;
/** @var Proposition $operand */
foreach ($this->getOperands() as $operand) {
if (true === $operand->evaluate($context)) {
if (++$true > 1) {
return false;
}
}
}
return $true === 1;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A set max operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Max extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $operand */
[$operand] = $this->getOperands();
return new Value($operand->prepareValue($context)->getSet()->max());
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A set min operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Min extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $operand */
[$operand] = $this->getOperands();
return new Value($operand->prepareValue($context)->getSet()->min());
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Modulo Arithmetic Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Modulo extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->modulo($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Multiplication Arithmetic Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Multiplication extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->multiply($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Negation Math Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Negation extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $operand */
[$operand] = $this->getOperands();
return new Value($operand->prepareValue($context)->negate());
}
protected function getOperandCardinality()
{
return static::UNARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A NotEqualTo comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class NotEqualTo extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->equalTo($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A NotSameAs comparison operator.
*
* @author Christophe Sicard <sicard.christophe@gmail.com>
*/
class NotSameAs extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->sameAs($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Operator as BaseOperator;
use Ruler\Proposition;
/**
* @author Jordan Raub <jordan@raub.me>
*/
abstract class PropositionOperator extends BaseOperator
{
/**
* @param Proposition $operand
*/
public function addOperand($operand): void
{
$this->addProposition($operand);
}
public function addProposition(Proposition $operand): void
{
if (static::UNARY === $this->getOperandCardinality()
&& 0 < \count($this->operands)
) {
throw new \LogicException(static::class.' can only have 1 operand');
}
$this->operands[] = $operand;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A SameAs comparison operator.
*
* @author Christophe Sicard <sicard.christophe@gmail.com>
*/
class SameAs extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->sameAs($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A Set Contains comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class SetContains extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->getSet()->setContains($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A Set Contains comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class SetDoesNotContain extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->getSet()->setContains($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A StartsWith comparison operator.
*
* @author Cornel Les <thebogu@gmail.com>
*/
class StartsWith extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->startsWith($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A StartsWith insensitive comparison operator.
*
* @author Cornel Les <thebogu@gmail.com>
*/
class StartsWithInsensitive extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->startsWith($right->prepareValue($context), true);
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A String Contains comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class StringContains extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->stringContains($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A String Contains case insensitive comparison operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class StringContainsInsensitive extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->stringContainsInsensitive($right->prepareValue($context));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A StringDoesNotContain comparison operator.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class StringDoesNotContain extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->stringContains($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Proposition;
use Ruler\VariableOperand;
/**
* A String does not Contain case insensitive comparison operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class StringDoesNotContainInsensitive extends VariableOperator implements Proposition
{
/**
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->stringContainsInsensitive($right->prepareValue($context)) === false;
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Subtraction Arithmetic Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Subtraction extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return new Value($left->prepareValue($context)->subtract($right->prepareValue($context)));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Set;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Symmetric Difference Set Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class SymmetricDifference extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
return $left->prepareValue($context)->getSet()
->symmetricDifference($right->prepareValue($context)->getSet());
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Context;
use Ruler\Set;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* A Set Union Operator.
*
* @author Jordan Raub <jordan@raub.me>
*/
class Union extends VariableOperator implements VariableOperand
{
public function prepareValue(Context $context): Value
{
$union = new Set([]);
/** @var VariableOperand $operand */
foreach ($this->getOperands() as $operand) {
$set = $operand->prepareValue($context)->getSet();
$union = $union->union($set);
}
return $union;
}
protected function getOperandCardinality()
{
return static::MULTIPLE;
}
}
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Operator;
use Ruler\Operator as BaseOperator;
use Ruler\VariableOperand;
/**
* @author Jordan Raub <jordan@raub.me>
*/
abstract class VariableOperator extends BaseOperator
{
/**
* @param VariableOperand $operand
*/
public function addOperand($operand): void
{
$this->addVariable($operand);
}
public function addVariable(VariableOperand $operand): void
{
if (static::UNARY === $this->getOperandCardinality()
&& 0 < \count($this->operands)
) {
throw new \LogicException(static::class.' can only have 1 operand');
}
$this->operands[] = $operand;
}
}
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* The Proposition interface represents a propositional statement.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
interface Proposition
{
/**
* Evaluate the Proposition with the given Context.
*
* @param Context $context Context with which to evaluate this Proposition
*/
public function evaluate(Context $context): bool;
}
@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* Rule class.
*
* A Rule is a conditional Proposition with an (optional) action which is
* executed upon successful evaluation.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Rule implements Proposition
{
protected $condition;
protected $action;
/**
* Rule constructor.
*
* @param Proposition $condition Propositional condition for this Rule
* @param callable $action Action (callable) to take upon successful Rule execution (default: null)
*/
public function __construct(Proposition $condition, $action = null)
{
$this->condition = $condition;
$this->action = $action;
}
/**
* Evaluate the Rule with the given Context.
*
* @param Context $context Context with which to evaluate this Rule
*/
public function evaluate(Context $context): bool
{
return $this->condition->evaluate($context);
}
/**
* Execute the Rule with the given Context.
*
* The Rule will be evaluated, and if successful, will execute its
* $action callback.
*
* @param Context $context Context with which to execute this Rule
*
* @throws \LogicException
*/
public function execute(Context $context): void
{
if ($this->evaluate($context) && isset($this->action)) {
if (!\is_callable($this->action)) {
throw new \LogicException('Rule actions must be callable.');
}
\call_user_func($this->action);
}
}
}
@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* RuleBuilder.
*
* The RuleBuilder provides a DSL and fluent interface for constructing
* Rules.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class RuleBuilder implements \ArrayAccess
{
private array $variables = [];
private array $operatorNamespaces = [];
/**
* Create a Rule with the given propositional condition.
*
* @param Proposition $condition Propositional condition for this Rule
* @param callable $action Action (callable) to take upon successful Rule execution (default: null)
*/
public function create(Proposition $condition, $action = null): Rule
{
return new Rule($condition, $action);
}
/**
* Register an operator namespace.
*
* Note that, depending on your filesystem, operator namespaces are most likely case sensitive.
*
* @throws \InvalidArgumentException
*/
public function registerOperatorNamespace(string $namespace): self
{
if (!\is_string($namespace)) {
throw new \InvalidArgumentException('Namespace argument must be a string');
}
$this->operatorNamespaces[$namespace] = true;
return $this;
}
/**
* Create a logical AND operator proposition.
*
* @param Proposition ...$props One or more Propositions
*/
public function logicalAnd(Proposition ...$props): Operator\LogicalAnd
{
return new Operator\LogicalAnd($props);
}
/**
* Create a logical OR operator proposition.
*
* @param Proposition ...$props One or more Propositions
*/
public function logicalOr(Proposition ...$props): Operator\LogicalOr
{
return new Operator\LogicalOr($props);
}
/**
* Create a logical NOT operator proposition.
*
* @param Proposition $prop Exactly one Proposition
*/
public function logicalNot(Proposition $prop): Operator\LogicalNot
{
return new Operator\LogicalNot([$prop]);
}
/**
* Create a logical XOR operator proposition.
*
* @param Proposition ...$props One or more Propositions
*/
public function logicalXor(Proposition ...$props): Operator\LogicalXor
{
return new Operator\LogicalXor($props);
}
/**
* Check whether a Variable is already set.
*
* @param string $name The Variable name
*/
public function offsetExists($name): bool
{
return isset($this->variables[$name]);
}
/**
* Retrieve a Variable by name.
*
* @param string $name The Variable name
*/
public function offsetGet($name): RuleBuilder\Variable
{
if (!isset($this->variables[$name])) {
$this->variables[$name] = new RuleBuilder\Variable($this, $name);
}
return $this->variables[$name];
}
/**
* Set the default value of a Variable.
*
* @param string $name The Variable name
* @param mixed $value The Variable default value
*/
public function offsetSet($name, $value): void
{
$this->offsetGet($name)->setValue($value);
}
/**
* Remove a defined Variable from the RuleBuilder.
*
* @param string $name The Variable name
*/
public function offsetUnset($name): void
{
unset($this->variables[$name]);
}
/**
* Find an operator in the registered operator namespaces.
*
* @throws \LogicException if a matching operator is not found
*/
public function findOperator(string $name): string
{
$operator = \ucfirst($name);
foreach (\array_keys($this->operatorNamespaces) as $namespace) {
$class = $namespace.'\\'.$operator;
if (\class_exists($class)) {
return $class;
}
}
throw new \LogicException(\sprintf('Unknown operator: "%s"', $name));
}
}
@@ -0,0 +1,427 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2013 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\RuleBuilder;
use Ruler\Operator;
use Ruler\Operator\VariableOperator;
use Ruler\RuleBuilder;
use Ruler\Variable as BaseVariable;
use Ruler\VariableOperand;
/**
* A propositional Variable.
*
* Variables are placeholders in Propositions and Comparison Operators. During
* evaluation, they are replaced with terminal Values, either from the Variable
* default or from the current Context.
*
* The RuleBuilder Variable extends the base Variable class with a fluent
* interface for creating VariableProperties, Operators and Rules without all
* kinds of awkward object instantiation.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Variable extends BaseVariable implements \ArrayAccess
{
private $ruleBuilder;
private array $properties = [];
/**
* RuleBuilder Variable constructor.
*
* @param RuleBuilder $ruleBuilder
* @param string $name Variable name (default: null)
* @param mixed $value Default Variable value (default: null)
*/
public function __construct(RuleBuilder $ruleBuilder, string $name = null, $value = null)
{
$this->ruleBuilder = $ruleBuilder;
parent::__construct($name, $value);
}
/**
* Get the RuleBuilder instance set on this Variable.
*/
public function getRuleBuilder(): RuleBuilder
{
return $this->ruleBuilder;
}
/**
* Get a VariableProperty for accessing methods, indexes and properties of
* the current variable.
*
* @param string $name Property name
* @param mixed $value The default VariableProperty value
*/
public function getProperty(string $name, $value = null): VariableProperty
{
if (!isset($this->properties[$name])) {
$this->properties[$name] = new VariableProperty($this, $name, $value);
}
return $this->properties[$name];
}
/**
* Fluent interface method for checking whether a VariableProperty has been defined.
*
* @param string $name Property name
*/
public function offsetExists($name): bool
{
return isset($this->properties[$name]);
}
/**
* Fluent interface method for creating or accessing VariableProperties.
*
* @see getProperty
*
* @param string $name Property name
*/
public function offsetGet($name): VariableProperty
{
return $this->getProperty($name);
}
/**
* Fluent interface method for setting default a VariableProperty value.
*
* @see setValue
*
* @param string $name Property name
* @param mixed $value The default Variable value
*/
public function offsetSet($name, $value): void
{
$this->getProperty($name)->setValue($value);
}
/**
* Fluent interface method for removing a VariableProperty reference.
*
* @param string $name Property name
*/
public function offsetUnset($name): void
{
unset($this->properties[$name]);
}
/**
* Fluent interface helper to create a contains comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function stringContains($variable): Operator\StringContains
{
return new Operator\StringContains($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a contains comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function stringDoesNotContain($variable): Operator\StringDoesNotContain
{
return new Operator\StringDoesNotContain($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a insensitive contains comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function stringContainsInsensitive($variable): Operator\StringContainsInsensitive
{
return new Operator\StringContainsInsensitive($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a GreaterThan comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function greaterThan($variable): Operator\GreaterThan
{
return new Operator\GreaterThan($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a GreaterThanOrEqualTo comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function greaterThanOrEqualTo($variable): Operator\GreaterThanOrEqualTo
{
return new Operator\GreaterThanOrEqualTo($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a LessThan comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function lessThan($variable): Operator\LessThan
{
return new Operator\LessThan($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a LessThanOrEqualTo comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function lessThanOrEqualTo($variable): Operator\LessThanOrEqualTo
{
return new Operator\LessThanOrEqualTo($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a EqualTo comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function equalTo($variable): Operator\EqualTo
{
return new Operator\EqualTo($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a NotEqualTo comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function notEqualTo($variable): Operator\NotEqualTo
{
return new Operator\NotEqualTo($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a SameAs comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function sameAs($variable): Operator\SameAs
{
return new Operator\SameAs($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a NotSameAs comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function notSameAs($variable): Operator\NotSameAs
{
return new Operator\NotSameAs($this, $this->asVariable($variable));
}
public function union(...$variables): self
{
return $this->applySetOperator('Union', $variables);
}
public function intersect(...$variables): self
{
return $this->applySetOperator('Intersect', $variables);
}
public function complement(...$variables): self
{
return $this->applySetOperator('Complement', $variables);
}
public function symmetricDifference(...$variables): self
{
return $this->applySetOperator('SymmetricDifference', $variables);
}
public function min(): self
{
return $this->wrap(new Operator\Min($this));
}
public function max(): self
{
return $this->wrap(new Operator\Max($this));
}
public function containsSubset($variable): Operator\ContainsSubset
{
return new Operator\ContainsSubset($this, $this->asVariable($variable));
}
public function doesNotContainSubset($variable): Operator\DoesNotContainSubset
{
return new Operator\DoesNotContainSubset($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a contains comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function setContains($variable): Operator\SetContains
{
return new Operator\SetContains($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a contains comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function setDoesNotContain($variable): Operator\SetDoesNotContain
{
return new Operator\SetDoesNotContain($this, $this->asVariable($variable));
}
public function add($variable): self
{
return $this->wrap(new Operator\Addition($this, $this->asVariable($variable)));
}
public function divide($variable): self
{
return $this->wrap(new Operator\Division($this, $this->asVariable($variable)));
}
public function modulo($variable): self
{
return $this->wrap(new Operator\Modulo($this, $this->asVariable($variable)));
}
public function multiply($variable): self
{
return $this->wrap(new Operator\Multiplication($this, $this->asVariable($variable)));
}
public function subtract($variable): self
{
return $this->wrap(new Operator\Subtraction($this, $this->asVariable($variable)));
}
public function negate(): self
{
return $this->wrap(new Operator\Negation($this));
}
public function ceil(): self
{
return $this->wrap(new Operator\Ceil($this));
}
public function floor(): self
{
return $this->wrap(new Operator\Floor($this));
}
public function exponentiate($variable): self
{
return $this->wrap(new Operator\Exponentiate($this, $this->asVariable($variable)));
}
/**
* Private helper to retrieve a Variable instance for the given $variable.
*
* @param mixed $variable BaseVariable instance or value
*/
private function asVariable($variable): BaseVariable
{
return ($variable instanceof BaseVariable) ? $variable : new BaseVariable(null, $variable);
}
/**
* Private helper to apply a set operator.
*/
private function applySetOperator(string $name, array $args): self
{
$reflection = new \ReflectionClass('\\Ruler\\Operator\\'.$name);
\array_unshift($args, $this);
return $this->wrap($reflection->newInstanceArgs($args));
}
/**
* Private helper to wrap a VariableOperator in a Variable instance.
*/
private function wrap(VariableOperator $op): self
{
return new self($this->ruleBuilder, null, $op);
}
/**
* Fluent interface helper to create a endsWith comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function endsWith($variable): Operator\EndsWith
{
return new Operator\EndsWith($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a endsWith insensitive comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function endsWithInsensitive($variable): Operator\EndsWithInsensitive
{
return new Operator\EndsWithInsensitive($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a startsWith comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function startsWith($variable): Operator\StartsWith
{
return new Operator\StartsWith($this, $this->asVariable($variable));
}
/**
* Fluent interface helper to create a startsWith insensitive comparison operator.
*
* @param mixed $variable Right side of comparison operator
*/
public function startsWithInsensitive($variable): Operator\StartsWithInsensitive
{
return new Operator\StartsWithInsensitive($this, $this->asVariable($variable));
}
/**
* Magic method to apply operators registered with RuleBuilder.
*
* @see RuleBuilder::registerOperatorNamespace
*
* @throws \LogicException if operator is not registered
*
* @return Operator|self
*/
public function __call(string $name, array $args)
{
$reflection = new \ReflectionClass($this->ruleBuilder->findOperator($name));
$args = \array_map([$this, 'asVariable'], $args);
\array_unshift($args, $this);
$op = $reflection->newInstanceArgs($args);
if ($op instanceof VariableOperand) {
return $this->wrap($op);
} else {
return $op;
}
}
}
@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2013 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\RuleBuilder;
use Ruler\Context;
use Ruler\Value;
/**
* A propositional VariableProperty.
*
* A VariableProperty is a special propositional Variable which maps to a
* property, method or offset of another Variable. During evaluation, they are
* replaced with terminal Values from properties of their parent Variable,
* either from their default Value, or from the current Context.
*
* The RuleBuilder VariableProperty extends the base VariableProperty class with
* a fluent interface for creating VariableProperties, Operators and Rules
* without all kinds of awkward object instantiation.
*
* (Note that this class doesn't *literally* extend the base VariableProperty
* class, due to PHP's complete inability to use multiple inheritance. Nor does
* it use a trait like it probably should, because this library targets
* PHP 5.3+. Instead it uses a highly refined "copy and paste" technique,
* perfected over years of diligent practice.)
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class VariableProperty extends Variable
{
private $parent;
/**
* VariableProperty class constructor.
*
* @param Variable $parent Parent Variable instance
* @param string $name Property name
* @param mixed $value Default Property value (default: null)
*/
public function __construct(Variable $parent, $name, $value = null)
{
$this->parent = $parent;
parent::__construct($parent->getRuleBuilder(), $name, $value);
}
/**
* Prepare a Value for this VariableProperty given the current Context.
*
* To retrieve a Value, the parent Variable is first resolved given the
* current context. Then, depending on its type, a method, property or
* offset of the parent Value is returned.
*
* If the parent Value is an object, and this VariableProperty name is
* "bar", it will do a prioritized lookup for:
*
* 1. A method named `bar`
* 2. A public property named `bar`
* 3. ArrayAccess + offsetExists named `bar`
*
* If it is an array:
*
* 1. Array index `bar`
*
* Otherwise, return the default value for this VariableProperty.
*
* @param Context $context The current Context
*/
public function prepareValue(Context $context): Value
{
$name = $this->getName();
$value = $this->parent->prepareValue($context)->getValue();
if (\is_object($value) && !$value instanceof \Closure) {
if (\method_exists($value, $name)) {
return $this->asValue(\call_user_func([$value, $name]));
} elseif (isset($value->$name)) {
return $this->asValue($value->$name);
} elseif ($value instanceof \ArrayAccess && $value->offsetExists($name)) {
return $this->asValue($value->offsetGet($name));
}
} elseif (\is_array($value) && \array_key_exists($name, $value)) {
return $this->asValue($value[$name]);
}
return $this->asValue($this->getValue());
}
/**
* Private helper to retrieve a Value instance for the given $value.
*
* @param mixed $value Value instance or value
*/
private function asValue($value): Value
{
return ($value instanceof Value) ? $value : new Value($value);
}
}
@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2013 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\RuleBuilder;
use Ruler\Context;
use Ruler\Value;
use Ruler\Variable;
/**
* All the guts of the VariableProperty, but none of the PHP 5.3ness.
*
* PHP 5.4+ users: Use this trait when creating custom Variable and
* VariableProperty classes for extending the RuleBuilder DSL.
*
* Everyone else: Ignore this, it's too cool for you.
*
* Apparently too cool for me, too, otherwise the VariableProperty classes in
* this library would be using this trait.
*
* A VariableProperty is a special propositional Variable which maps to a
* property, method or offset of another Variable. During evaluation, they are
* replaced with terminal Values from properties of their parent Variable,
* either from their default Value, or from the current Context.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
trait VariablePropertyTrait
{
private $parent;
/**
* Set the parent Variable reference.
*
* @param Variable $parent Parent Variable instance
*/
public function setParent(Variable $parent): void
{
$this->parent = $parent;
}
/**
* Prepare a Value for this VariableProperty given the current Context.
*
* To retrieve a Value, the parent Variable is first resolved given the
* current context. Then, depending on its type, a method, property or
* offset of the parent Value is returned.
*
* If the parent Value is an object, and this VariableProperty name is
* "bar", it will do a prioritized lookup for:
*
* 1. A method named `bar`
* 2. A public property named `bar`
* 3. ArrayAccess + offsetExists named `bar`
*
* If it is an array:
*
* 1. Array index `bar`
*
* Otherwise, return the default value for this VariableProperty.
*
* @param Context $context The current Context
*/
public function prepareValue(Context $context): Value
{
$name = $this->getName();
$value = $this->parent->prepareValue($context)->getValue();
if (\is_object($value) && !$value instanceof \Closure) {
if (\method_exists($value, $name)) {
return $this->asValue(\call_user_func([$value, $name]));
} elseif (isset($value->$name)) {
return $this->asValue($value->$name);
} elseif ($value instanceof \ArrayAccess && $value->offsetExists($name)) {
return $this->asValue($value->offsetGet($name));
}
} elseif (\is_array($value) && \array_key_exists($name, $value)) {
return $this->asValue($value[$name]);
}
return $this->asValue($this->getValue());
}
/**
* Private helper to retrieve a Value instance for the given $value.
*
* @param mixed $value Value instance or value
*/
private function asValue($value): Value
{
return ($value instanceof Value) ? $value : new Value($value);
}
}
@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* A Ruler RuleSet.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class RuleSet
{
protected $rules = [];
/**
* RuleSet constructor.
*
* @param array $rules Rules to add to RuleSet
*/
public function __construct(array $rules = [])
{
foreach ($rules as $rule) {
$this->addRule($rule);
}
}
/**
* Add a Rule to the RuleSet.
*
* Adding duplicate Rules to the RuleSet will have no effect.
*
* @param Rule $rule Rule to add to the set
*/
public function addRule(Rule $rule): void
{
$this->rules[\spl_object_hash($rule)] = $rule;
}
/**
* Execute all Rules in the RuleSet.
*
* @param Context $context Context with which to execute each Rule
*/
public function executeRules(Context $context): void
{
foreach ($this->rules as $rule) {
$rule->execute($context);
}
}
}
@@ -0,0 +1,234 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* A Ruler Set.
*
* A Set is essentially an array, a special case of Value which can be compared
* by SetOperators.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Set extends Value implements \Countable
{
/**
* Set constructor.
*
* A Set object is immutable, and is used by Variables for comparing their
* Default values or facts from the current Context.
*
* @param mixed $set Immutable value represented by this Value object
*/
public function __construct($set)
{
parent::__construct($set);
if (!\is_array($this->value)) {
if (null === $this->value) {
$this->value = [];
} else {
$this->value = [$this->value];
}
}
foreach ($this->value as &$value) {
if (\is_array($value)) {
$value = new self($value);
} elseif (\is_object($value)) {
if (!\method_exists($value, '__toString')) {
$value = new Value($value);
}
}
}
$this->value = \array_unique($this->value);
foreach ($this->value as &$value) {
if ($value instanceof Value && !$value instanceof self) {
$value = $value->getValue();
}
}
}
public function __toString(): string
{
$returnValue = '';
foreach ($this->value as $value) {
$returnValue .= (string) $value;
}
return $returnValue;
}
/**
* Set Contains comparison.
*
* @param Value $value Value object to compare against
*/
public function setContains(Value $value): bool
{
if (\is_array($value->getValue())) {
foreach ($this->value as $val) {
if ($val instanceof self && $val == $value->getSet()) {
return true;
}
}
return false;
}
return \in_array($value->getValue(), $this->value, true);
}
/**
* Set union operator.
*
* Returns a Set which is the union of this Set with all passed Sets.
*
* @param Value ...$sets One or more Sets
*/
public function union(Value ...$sets): self
{
$union = $this->value;
/** @var Value $arg */
foreach ($sets as $arg) {
/** @var array $convertedArg */
$convertedArg = $arg->getSet()->getValue();
$union = \array_merge($union, \array_diff($convertedArg, $union));
}
return new self($union);
}
/**
* Set intersection operator.
*
* Returns a Set which is the intersection of this Set with all passed sets.
*
* @param Value ...$sets One or more Sets
*/
public function intersect(Value ...$sets): self
{
$intersect = $this->value;
/** @var Value $arg */
foreach ($sets as $arg) {
/** @var array $convertedArg */
$convertedArg = $arg->getSet()->getValue();
// array_values is needed to make sure the indexes are ordered from 0
$intersect = \array_values(\array_intersect($intersect, $convertedArg));
}
return new self($intersect);
}
/**
* Set complement operator.
*
* Returns a Set which is the complement of this Set with all passed Sets.
*
* @param Value ...$sets One or more Sets
*/
public function complement(Value ...$sets): self
{
$complement = $this->value;
/** @var Value $arg */
foreach ($sets as $arg) {
/** @var array $convertedArg */
$convertedArg = $arg->getSet()->getValue();
// array_values is needed to make sure the indexes are ordered from 0
$complement = \array_values(\array_diff($complement, $convertedArg));
}
return new self($complement);
}
/**
* Set symmetric difference operator.
*
* Returns a Set which is the symmetric difference of this Set with the
* passed Set.
*/
public function symmetricDifference(Value $set): self
{
$returnValue = new self([]);
return $returnValue->union(
$this->complement($set),
$set->getSet()->complement($this)
);
}
/**
* Numeric minimum value in this Set.
*
* @throws \RuntimeException if this Set contains non-numeric members
*
* @return mixed
*/
public function min()
{
if (!$this->isValidNumericSet()) {
throw new \RuntimeException('min: all values must be numeric');
}
if (empty($this->value)) {
return null;
}
return \min($this->value);
}
/**
* Numeric maximum value in this Set.
*
* @throws \RuntimeException if this Set contains non-numeric members
*
* @return mixed
*/
public function max()
{
if (!$this->isValidNumericSet()) {
throw new \RuntimeException('max: all values must be numeric');
}
if (empty($this->value)) {
return null;
}
return \max($this->value);
}
/**
* Contains Subset comparison.
*/
public function containsSubset(self $set): bool
{
if ((\is_countable($set->getValue()) ? \count($set->getValue()) : 0) > (\is_countable($this->getValue()) ? \count($this->getValue()) : 0)) {
return false;
}
return \array_intersect($set->getValue(), $this->getValue()) === $set->getValue();
}
/**
* Helper function to validate that a set contains only numeric members.
*/
protected function isValidNumericSet(): bool
{
return (\is_countable($this->value) ? \count($this->value) : 0) === \array_sum(\array_map('is_numeric', $this->value));
}
public function count(): int
{
return \is_countable($this->value) ? \count($this->value) : 0;
}
}
@@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* A Ruler Value.
*
* A Value represents a comparable terminal value. Variables and Comparison Operators
* are resolved to Values by applying the current Context and the default Variable value.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Value
{
protected $value;
/**
* Value constructor.
*
* A Value object is immutable, and is used by Variables for comparing their default
* values or facts from the current Context.
*
* @param mixed $value Immutable value represented by this Value object
*/
public function __construct($value)
{
$this->value = $value;
}
public function __toString(): string
{
if (\is_object($this->value)) {
return \spl_object_hash($this->value);
} else {
return \serialize($this->value);
}
}
/**
* Return the value.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Get a Set containing this Value.
*/
public function getSet(): Set
{
return new Set($this->value);
}
/**
* Equal To comparison.
*
* @param Value $value Value object to compare against
*/
public function equalTo(self $value): bool
{
return $this->value === $value->getValue();
}
/**
* Identical To comparison.
*
* @param Value $value Value object to compare against
*/
public function sameAs(self $value): bool
{
return $this->value === $value->getValue();
}
/**
* String Contains comparison.
*
* @param Value $value Value object to compare against
*/
public function stringContains(self $value): bool
{
return \strpos($this->value, (string) $value->getValue()) !== false;
}
/**
* String Contains case-insensitive comparison.
*
* @param Value $value Value object to compare against
*/
public function stringContainsInsensitive(self $value): bool
{
return \stripos($this->value, (string) $value->getValue()) !== false;
}
/**
* Greater Than comparison.
*
* @param Value $value Value object to compare against
*/
public function greaterThan(self $value): bool
{
return $this->value > $value->getValue();
}
/**
* Less Than comparison.
*
* @param Value $value Value object to compare against
*/
public function lessThan(self $value): bool
{
return $this->value < $value->getValue();
}
public function add(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return $this->value + $value->getValue();
}
public function divide(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
if (0 === $value->getValue()) {
throw new \RuntimeException('Division by zero');
}
return $this->value / $value->getValue();
}
public function modulo(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
if (0 === $value->getValue()) {
throw new \RuntimeException('Division by zero');
}
return $this->value % $value->getValue();
}
public function multiply(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return $this->value * $value->getValue();
}
public function subtract(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return $this->value - $value->getValue();
}
public function negate()
{
if (!\is_numeric($this->value)) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return -$this->value;
}
public function ceil()
{
if (!\is_numeric($this->value)) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return (int) \ceil($this->value);
}
public function floor()
{
if (!\is_numeric($this->value)) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return (int) \floor($this->value);
}
public function exponentiate(self $value)
{
if (!\is_numeric($this->value) || !\is_numeric($value->getValue())) {
throw new \RuntimeException('Arithmetic: values must be numeric');
}
return $this->value ** $value->getValue();
}
/**
* String StartsWith comparison.
*
* @param Value $value Value object to compare against
* @param bool $insensitive Perform a case-insensitive comparison (default: false)
*/
public function startsWith(self $value, bool $insensitive = false): bool
{
$value = $value->getValue();
$valueLength = \strlen($value);
if (!empty($this->value) && !empty($value) && \strlen($this->value) >= $valueLength) {
return \substr_compare($this->value, $value, 0, $valueLength, $insensitive) === 0;
}
return false;
}
/**
* String EndsWith comparison.
*
* @param Value $value Value object to compare against
* @param bool $insensitive Perform a case-insensitive comparison (default: false)
*/
public function endsWith(self $value, bool $insensitive = false): bool
{
$value = $value->getValue();
$valueLength = \strlen($value);
if (!empty($this->value) && !empty($value) && \strlen($this->value) >= $valueLength) {
return \substr_compare($this->value, $value, -$valueLength, $valueLength, $insensitive) === 0;
}
return false;
}
}
@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* A propositional Variable.
*
* Variables are placeholders in Propositions and Comparison Operators. During
* evaluation, they are replaced with terminal Values, either from the Variable
* default or from the current Context.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class Variable implements VariableOperand
{
private $name;
private $value;
/**
* Variable class constructor.
*
* @param string $name Variable name (default: null)
* @param mixed $value Default Variable value (default: null)
*/
public function __construct(string $name = null, $value = null)
{
$this->name = $name;
$this->value = $value;
}
/**
* Return the Variable name.
*/
public function getName(): string
{
return $this->name;
}
/**
* Set the default Variable value.
*
* @param mixed $value The default Variable value
*/
public function setValue($value): void
{
$this->value = $value;
}
/**
* Get the default Variable value.
*
* @return mixed Variable value
*/
public function getValue()
{
return $this->value;
}
/**
* Prepare a Value for this Variable given the current Context.
*
* @param Context $context The current Context
*/
public function prepareValue(Context $context): Value
{
if (isset($this->name) && isset($context[$this->name])) {
$value = $context[$this->name];
} elseif ($this->value instanceof VariableOperand) {
$value = $this->value->prepareValue($context);
} else {
$value = $this->value;
}
return ($value instanceof Value) ? $value : new Value($value);
}
}
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* @author Jordan Raub <jordan@raub.me>
*/
interface VariableOperand
{
public function prepareValue(Context $context): Value;
}
@@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2013 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler;
/**
* A propositional VariableProperty.
*
* A VariableProperty is a special propositional Variable which maps to a
* property, method or offset of another Variable. During evaluation, they are
* replaced with terminal Values from properties of their parent Variable,
* either from their default Value, or from the current Context.
*
* @author Justin Hileman <justin@justinhileman.info>
*/
class VariableProperty extends Variable
{
private $parent;
/**
* VariableProperty class constructor.
*
* @param Variable $parent Parent Variable instance
* @param string $name Property name
* @param mixed $value Default Property value (default: null)
*/
public function __construct(Variable $parent, $name, $value = null)
{
$this->parent = $parent;
parent::__construct($name, $value);
}
/**
* Prepare a Value for this VariableProperty given the current Context.
*
* To retrieve a Value, the parent Variable is first resolved given the
* current context. Then, depending on its type, a method, property or
* offset of the parent Value is returned.
*
* If the parent Value is an object, and this VariableProperty name is
* "bar", it will do a prioritized lookup for:
*
* 1. A method named `bar`
* 2. A public property named `bar`
* 3. ArrayAccess + offsetExists named `bar`
*
* If it is an array:
*
* 1. Array index `bar`
*
* Otherwise, return the default value for this VariableProperty.
*
* @param Context $context The current Context
*/
public function prepareValue(Context $context): Value
{
$name = $this->getName();
$value = $this->parent->prepareValue($context)->getValue();
if (\is_object($value) && !$value instanceof \Closure) {
if (\method_exists($value, $name)) {
return $this->asValue(\call_user_func([$value, $name]));
} elseif (isset($value->$name)) {
return $this->asValue($value->$name);
} elseif ($value instanceof \ArrayAccess && $value->offsetExists($name)) {
return $this->asValue($value->offsetGet($name));
}
} elseif (\is_array($value) && \array_key_exists($name, $value)) {
return $this->asValue($value[$name]);
}
return $this->asValue($this->getValue());
}
/**
* Private helper to retrieve a Value instance for the given $value.
*
* @param mixed $value Value instance or value
*/
private function asValue($value): Value
{
return ($value instanceof Value) ? $value : new Value($value);
}
}
@@ -0,0 +1,299 @@
<?php
/*
* Copyright (c) 2009 Fabien Potencier
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Ruler\Test;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Test\Fixtures\Fact;
use Ruler\Test\Fixtures\Invokable;
/**
* Ruler Context test.
*
* Derived from Pimple, by Fabien Potencier:
*
* https://github.com/fabpot/Pimple
*
* @author Igor Wiedler <igor@wiedler.ch>
* @author Justin Hileman <justin@justinhileman.info>
*/
class ContextTest extends TestCase
{
public function testConstructor()
{
$facts = [
'name' => 'Mint Chip',
'type' => 'Ice Cream',
'delicious' => function () {
return true;
},
];
$context = new Context($facts);
$this->assertTrue(isset($context['name']));
$this->assertEquals('Mint Chip', $context['name']);
$this->assertTrue(isset($context['type']));
$this->assertEquals('Ice Cream', $context['type']);
$this->assertTrue(isset($context['delicious']));
$this->assertTrue($context['delicious']);
}
public function testWithString()
{
$context = new Context();
$context['param'] = 'value';
$this->assertEquals('value', $context['param']);
}
public function testWithClosure()
{
$context = new Context();
$context['fact'] = function () {
return new Fact();
};
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $context['fact']);
}
public function testFactsShouldBeDifferent()
{
$context = new Context();
$context['fact'] = function () {
return new Fact();
};
$factOne = $context['fact'];
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $factOne);
$factTwo = $context['fact'];
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $factTwo);
$this->assertNotSame($factOne, $factTwo);
}
public function testShouldPassContextAsParameter()
{
$context = new Context();
$context['fact'] = function () {
return new Fact();
};
$context['context'] = function ($context) {
return $context;
};
$this->assertNotSame($context, $context['fact']);
$this->assertSame($context, $context['context']);
}
public function testIsset()
{
$context = new Context();
$context['param'] = 'value';
$context['fact'] = function () {
return new Fact();
};
$context['null'] = null;
$this->assertTrue(isset($context['param']));
$this->assertTrue(isset($context['fact']));
$this->assertTrue(isset($context['null']));
$this->assertFalse(isset($context['non_existent']));
}
public function testConstructorInjection()
{
$params = ['param' => 'value'];
$context = new Context($params);
$this->assertSame($params['param'], $context['param']);
}
public function testOffsetGetValidatesKeyIsPresent()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Fact "foo" is not defined.');
$context = new Context();
echo $context['foo'];
}
public function testOffsetGetHonorsNullValues()
{
$context = new Context();
$context['foo'] = null;
$this->assertNull($context['foo']);
}
public function testUnset()
{
$context = new Context();
$context['param'] = 'value';
$context['fact'] = function () {
return new Fact();
};
unset($context['param'], $context['fact']);
$this->assertFalse(isset($context['param']));
$this->assertFalse(isset($context['fact']));
}
/**
* @dataProvider factDefinitionProvider
*/
public function testShare($fact)
{
$context = new Context();
$context['shared_fact'] = $context->share($fact);
$factOne = $context['shared_fact'];
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $factOne);
$factTwo = $context['shared_fact'];
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $factTwo);
$this->assertSame($factOne, $factTwo);
}
/**
* @dataProvider factDefinitionProvider
*/
public function testProtect($fact)
{
$context = new Context();
$context['protected'] = $context->protect($fact);
$this->assertSame($fact, $context['protected']);
}
public function testGlobalFunctionNameAsParameterValue()
{
$context = new Context();
$context['global_function'] = 'strlen';
$this->assertSame('strlen', $context['global_function']);
}
public function testRaw()
{
$context = new Context();
$context['fact'] = $definition = function () { return 'foo'; };
$this->assertSame($definition, $context->raw('fact'));
}
public function testRawHonorsNullValues()
{
$context = new Context();
$context['foo'] = null;
$this->assertNull($context->raw('foo'));
}
public function testRawValidatesKeyIsPresent()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Fact "foo" is not defined.');
$context = new Context();
$context->raw('foo');
}
public function testKeys()
{
$context = new Context();
$context['foo'] = 123;
$context['bar'] = 123;
$this->assertEquals(['foo', 'bar'], $context->keys());
}
/** @test */
public function settingAnInvokableObjectShouldTreatItAsFactory()
{
$context = new Context();
$context['invokable'] = new Invokable();
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $context['invokable']);
}
/** @test */
public function settingNonInvokableObjectShouldTreatItAsParameter()
{
$context = new Context();
$context['non_invokable'] = new Fact();
$this->assertInstanceOf(\Ruler\Test\Fixtures\Fact::class, $context['non_invokable']);
}
/**
* @dataProvider badFactDefinitionProvider
*/
public function testShareFailsForInvalidFactDefinitions($fact)
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Value is not a Closure or invokable object.');
$context = new Context();
$context->share($fact);
}
/**
* @dataProvider badFactDefinitionProvider
*/
public function testProtectFailsForInvalidFactDefinitions($fact)
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Callable is not a Closure or invokable object.');
$context = new Context();
$context->protect($fact);
}
/**
* Provider for invalid fact definitions.
*/
public function badFactDefinitionProvider()
{
return [
[123],
[new Fact()],
];
}
/**
* Provider for fact definitions.
*/
public function factDefinitionProvider()
{
return [
[function ($value) {
$fact = new Fact();
$fact->value = $value;
return $fact;
}],
[new Invokable()],
];
}
}
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Ruler package, an OpenSky project.
*
* (c) 2011 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Ruler\Test\Fixtures;
use Ruler\Context;
use Ruler\Operator\VariableOperator;
use Ruler\Proposition;
use Ruler\Value;
use Ruler\VariableOperand;
/**
* An EqualTo comparison operator.
*
* @author Justin Hileman <justin@shopopensky.com>
*/
class ALotGreaterThan extends VariableOperator implements Proposition
{
/**
* Evaluate whether the given variables are equal in the current Context.
*
* @param Context $context Context with which to evaluate this ComparisonOperator
*/
public function evaluate(Context $context): bool
{
/** @var VariableOperand $left */
/** @var VariableOperand $right */
[$left, $right] = $this->getOperands();
$value = $right->prepareValue($context)->getValue() * 10;
return $left->prepareValue($context)->greaterThan(new Value($value));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}
@@ -0,0 +1,28 @@
<?php
namespace Ruler\Test\Fixtures;
use Ruler\Context;
use Ruler\Proposition;
class CallbackProposition implements Proposition
{
private $callback;
/**
* @param callable $callback
*/
public function __construct($callback)
{
if (!\is_callable($callback)) {
throw new \InvalidArgumentException('CallbackProposition expects a callable argument');
}
$this->callback = $callback;
}
public function evaluate(Context $context): bool
{
return \call_user_func($this->callback, $context);
}
}
@@ -0,0 +1,18 @@
<?php
namespace Ruler\Test\Fixtures;
class Fact
{
public $value;
/**
* @param mixed $value
*/
public function __construct($value = null)
{
if ($value !== null) {
$this->value = $value;
}
}
}
@@ -0,0 +1,14 @@
<?php
namespace Ruler\Test\Fixtures;
use Ruler\Context;
use Ruler\Proposition;
class FalseProposition implements Proposition
{
public function evaluate(Context $context): bool
{
return false;
}
}
@@ -0,0 +1,14 @@
<?php
namespace Ruler\Test\Fixtures;
class Invokable
{
/**
* @param mixed $value
*/
public function __invoke($value = null)
{
return new Fact($value);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Ruler\Test\Fixtures;
use Ruler\Context;
use Ruler\Proposition;
class TrueProposition implements Proposition
{
public function evaluate(Context $context): bool
{
return true;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Ruler\Test\Fixtures;
class toStringable
{
private $thingy = null;
/**
* @param mixed $foo
*/
public function __construct($foo = null)
{
$this->thingy = $foo;
}
public function __toString(): string
{
return (string) $this->thingy;
}
}
@@ -0,0 +1,371 @@
<?php
namespace Ruler\Test\Functional;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\RuleBuilder;
class RulerTest extends TestCase
{
/**
* @dataProvider truthTableTwo
*/
public function testDeMorgan($p, $q)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q'));
$this->assertEquals(
$rb->create(
$rb->logicalNot(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalOr(
$rb->logicalNot(
$rb['p']->equalTo(true)
),
$rb->logicalNot(
$rb['q']->equalTo(true)
)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableTwo
*/
public function testDeMorganTwo($p, $q)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q'));
$this->assertEquals(
$rb->create(
$rb->logicalNot(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalAnd(
$rb->logicalNot(
$rb['p']->equalTo(true)
),
$rb->logicalNot(
$rb['q']->equalTo(true)
)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableTwo
*/
public function testCommutation($p, $q)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q'));
$this->assertEquals(
$rb->create(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
)
)->evaluate($context),
$rb->create(
$rb->logicalOr(
$rb['q']->equalTo(true),
$rb['p']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableTwo
*/
public function testCommutationTwo($p, $q)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q'));
$this->assertEquals(
$rb->create(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
)
)->evaluate($context),
$rb->create(
$rb->logicalAnd(
$rb['q']->equalTo(true),
$rb['p']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableThree
*/
public function testAssociation($p, $q, $r)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q', 'r'));
$this->assertEquals(
$rb->create(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb->logicalOr(
$rb['q']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalOr(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
),
$rb['r']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableThree
*/
public function testAssociationTwo($p, $q, $r)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q', 'r'));
$this->assertEquals(
$rb->create(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb->logicalAnd(
$rb['q']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalAnd(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
),
$rb['r']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableThree
*/
public function testDistribution($p, $q, $r)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q', 'r'));
$this->assertEquals(
$rb->create(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb->logicalOr(
$rb['q']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalOr(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
),
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableThree
*/
public function testDistributionTwo($p, $q, $r)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p', 'q', 'r'));
$this->assertEquals(
$rb->create(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb->logicalAnd(
$rb['q']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context),
$rb->create(
$rb->logicalAnd(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['q']->equalTo(true)
),
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['r']->equalTo(true)
)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableOne
*/
public function testDoubleNegation($p)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p'));
$this->assertEquals(
$rb->create(
$rb['p']->equalTo(true)
)->evaluate($context),
$rb->create(
$rb->logicalNot(
$rb->logicalNot(
$rb['p']->equalTo(true)
)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableOne
*/
public function testTautology($p)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p'));
$this->assertEquals(
$rb->create(
$rb['p']->equalTo(true)
)->evaluate($context),
$rb->create(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb['p']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableOne
*/
public function testTautologyTwo($p)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p'));
$this->assertEquals(
$rb->create(
$rb['p']->equalTo(true)
)->evaluate($context),
$rb->create(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb['p']->equalTo(true)
)
)->evaluate($context)
);
}
/**
* @dataProvider truthTableOne
*/
public function testExcludedMiddle($p)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p'));
$this->assertEquals(
$rb->create(
$rb->logicalOr(
$rb['p']->equalTo(true),
$rb->logicalNot(
$rb['p']->equalTo(true)
)
)
)->evaluate($context),
true
);
}
/**
* @dataProvider truthTableOne
*/
public function testNonContradiction($p)
{
$rb = new RuleBuilder();
$context = new Context(\compact('p'));
$this->assertEquals(
$rb->create(
$rb->logicalNot(
$rb->logicalAnd(
$rb['p']->equalTo(true),
$rb->logicalNot(
$rb['p']->equalTo(true)
)
)
)
)->evaluate($context),
true
);
}
public function truthTableOne()
{
return [
[true],
[false],
];
}
public function truthTableTwo()
{
return [
[true, true],
[true, false],
[false, true],
[false, false],
];
}
public function truthTableThree()
{
return [
[true, true, true],
[true, true, false],
[true, false, true],
[true, false, false],
[false, true, true],
[false, true, false],
[false, false, true],
[false, false, false],
];
}
}
@@ -0,0 +1,266 @@
<?php
namespace Ruler\Test\Functional;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\RuleBuilder;
class SetTest extends TestCase
{
public function testComplicated()
{
$rb = new RuleBuilder();
$context = new Context([
'expected' => 'a',
'foo' => ['a', 'z'],
'bar' => ['z', 'b'],
'baz' => ['a', 'z', 'b', 'q'],
'bob' => ['a', 'd'],
]);
$this->assertTrue(
$rb->create(
$rb['foo']->intersect(
$rb['bar']->symmetricDifference($rb['baz'])
)->setContains($rb['expected'])
)->evaluate($context)
);
$this->assertTrue(
$rb->create(
$rb['bar']->union(
$rb['bob']
)->containsSubset($rb['foo'])
)->evaluate($context)
);
}
public function setUnion()
{
return [
[
['a', 'b', 'c'],
[],
['a', 'b', 'c'],
],
[
[],
['a', 'b', 'c'],
['a', 'b', 'c'],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f'],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
['a', 'b', 'c'],
],
[
['a', 'b', 'c'],
['b', 'c'],
['a', 'b', 'c'],
],
[
['b', 'c'],
['b', 'd'],
['b', 'c', 'd'],
],
];
}
/**
* @dataProvider setUnion
*/
public function testUnion($a, $b, $expected)
{
$rb = new RuleBuilder();
$context = new Context(\compact('a', 'b', 'expected'));
$this->assertTrue(
$rb->create(
$rb['expected']->equalTo(
$rb['a']->union($rb['b'])
)
)->evaluate($context)
);
}
public function setIntersect()
{
return [
[
['a', 'b', 'c'],
[],
[],
],
[
[],
['a', 'b', 'c'],
[],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
[],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
['a', 'b', 'c'],
],
[
['a', 'b', 'c'],
['b', 'c'],
['b', 'c'],
],
[
['b', 'c'],
['b', 'd'],
['b'],
],
];
}
/**
* @dataProvider setIntersect
*/
public function testIntersect($a, $b, $expected)
{
$rb = new RuleBuilder();
$context = new Context(\compact('a', 'b', 'expected'));
$this->assertTrue(
$rb->create(
$rb['expected']->equalTo(
$rb['a']->intersect($rb['b'])
)
)->evaluate($context)
);
}
public function setComplement()
{
return [
[
['a', 'b', 'c'],
[],
['a', 'b', 'c'],
],
[
[],
['a', 'b', 'c'],
[],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
['a', 'b', 'c'],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
[],
],
[
['a', 'b', 'c'],
['b', 'c'],
['a'],
],
[
['b', 'c'],
['b', 'd'],
['c'],
],
];
}
/**
* @dataProvider setComplement
*/
public function testComplement($a, $b, $expected)
{
$rb = new RuleBuilder();
$context = new Context(\compact('a', 'b', 'expected'));
$this->assertTrue(
$rb->create(
$rb['expected']->equalTo(
$rb['a']->complement($rb['b'])
)
)->evaluate($context)
);
}
public function setSymmetricDifference()
{
return [
[
['a', 'b', 'c'],
[],
['a', 'b', 'c'],
],
[
[],
['a', 'b', 'c'],
['a', 'b', 'c'],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
['a', 'b', 'c', 'd', 'e', 'f'],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
[],
],
[
['a', 'b', 'c'],
['b', 'c'],
['a'],
],
[
['b', 'c'],
['b', 'd'],
['c', 'd'],
],
];
}
/**
* @dataProvider setSymmetricDifference
*/
public function testSymmetricDifference($a, $b, $expected)
{
$rb = new RuleBuilder();
$context = new Context(\compact('a', 'b', 'expected'));
$this->assertTrue(
$rb->create(
$rb['expected']->equalTo(
$rb['a']->symmetricDifference($rb['b'])
)
)->evaluate($context)
);
}
}
@@ -0,0 +1,53 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class AdditionTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Addition($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Addition($varA, $varB);
$op->prepareValue($context);
}
/**
* @dataProvider additionData
*/
public function testAddition($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Addition($varA, $varB);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function additionData()
{
return [
[1, 2, 3],
[2.5, 3.8, 6.3],
];
}
}
@@ -0,0 +1,53 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class CeilTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$op = new Operator\Ceil($varA);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$context = new Context();
$op = new Operator\Ceil($varA);
$op->prepareValue($context);
}
/**
* @dataProvider ceilingData
*/
public function testCeiling($a, $result)
{
$varA = new Variable('a', $a);
$context = new Context();
$op = new Operator\Ceil($varA);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function ceilingData()
{
return [
[1.2, 2],
[1.0, 1],
[1, 1],
[-0.5, 0],
[-1.5, -1],
];
}
}
@@ -0,0 +1,106 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class ComplementTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Complement($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Complement($varA, $varB);
$this->assertEquals(
['string'],
$op->prepareValue($context)->getValue()
);
}
/**
* @dataProvider complementData
*/
public function testComplement($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Complement($varA, $varB);
$this->assertEquals(
$result,
$op->prepareValue($context)->getValue()
);
}
public function complementData()
{
return [
[6, 2, [6]],
[
['a', 'b', 'c'],
'a',
['b', 'c'],
],
[
'a',
['a', 'b', 'c'],
[],
],
[
'a',
['b', 'c'],
['a'],
],
[
['a', 'b', 'c'],
[],
['a', 'b', 'c'],
],
[
[],
['a', 'b', 'c'],
[],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
['a', 'b', 'c'],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
[],
],
[
['a', 'b', 'c'],
['b', 'c'],
['a'],
],
[
['b', 'c'],
['b', 'd'],
['c'],
],
];
}
}
@@ -0,0 +1,65 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class ContainsSubsetTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\ContainsSubset($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
/**
* @dataProvider containsData
*/
public function testContains($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\ContainsSubset($varA, $varB);
$this->assertEquals($op->evaluate($context), $result);
}
/**
* @dataProvider containsData
*/
public function testDoesNotContain($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\DoesNotContainSubset($varA, $varB);
$this->assertNotEquals($op->evaluate($context), $result);
}
public function containsData()
{
return [
[[1], [1], true],
[[1], 1, true],
[[1, 2, 3], [1, 2], true],
[[1, 2, 3], [2, 4], false],
[['foo', 'bar', 'baz'], ['pow'], false],
[['foo', 'bar', 'baz'], ['bar'], true],
[['foo', 'bar', 'baz'], ['bar', 'baz'], true],
[null, 'bar', false],
[null, ['bar'], false],
[null, ['bar', 'baz'], false],
[null, null, true],
[[], [], true],
[[1, 2, 3], [2], true],
];
}
}
@@ -0,0 +1,65 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class DivisionTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Division($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Division($varA, $varB);
$op->prepareValue($context);
}
public function testDivideByZero()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Division by zero');
$varA = new Variable('a', \random_int(1, 100));
$varB = new Variable('b', 0);
$context = new Context();
$op = new Operator\Division($varA, $varB);
$op->prepareValue($context);
}
/**
* @dataProvider divisionData
*/
public function testDivision($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Division($varA, $varB);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function divisionData()
{
return [
[6, 2, 3],
[7.5, 2.5, 3.0],
];
}
}
@@ -0,0 +1,44 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class EndsWithInsensitiveTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 'foo bar baz');
$varB = new Variable('b', 'foo');
$op = new Operator\StartsWith($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
/**
* @dataProvider endsWithData
*/
public function testEndsWithInsensitive($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\EndsWithInsensitive($varA, $varB);
$this->assertEquals($op->evaluate($context), $result);
}
public function endsWithData()
{
return [
['supercalifragilistic', 'supercalifragilistic', true],
['supercalifragilistic', 'stic', true],
['supercalifragilistic', 'STIC', true],
['supercalifragilistic', 'super', false],
['supercalifragilistic', '', false],
];
}
}
@@ -0,0 +1,44 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class EndsWithTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 'foo bar baz');
$varB = new Variable('b', 'foo');
$op = new Operator\StartsWith($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
/**
* @dataProvider endsWithData
*/
public function testEndsWith($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\EndsWith($varA, $varB);
$this->assertEquals($op->evaluate($context), $result);
}
public function endsWithData()
{
return [
['supercalifragilistic', 'supercalifragilistic', true],
['supercalifragilistic', 'stic', true],
['supercalifragilistic', 'STIC', false],
['supercalifragilistic', 'super', false],
['supercalifragilistic', '', false],
];
}
}
@@ -0,0 +1,39 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class EqualToTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\EqualTo($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\EqualTo($varA, $varB);
$this->assertFalse($op->evaluate($context));
$context['a'] = 2;
$this->assertTrue($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 3;
};
$this->assertTrue($op->evaluate($context));
}
}
@@ -0,0 +1,53 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class ExponentiateTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Exponentiate($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Exponentiate($varA, $varB);
$op->prepareValue($context);
}
/**
* @dataProvider exponentiateData
*/
public function testExponentiate($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Exponentiate($varA, $varB);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function exponentiateData()
{
return [
[6, 2, 36],
[10, -1, 0.1],
];
}
}
@@ -0,0 +1,30 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class ExtraOperatorTest extends TestCase
{
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\GreaterThan($varA, $varB);
$this->assertFalse($op->evaluate($context));
$context['a'] = 2;
$this->assertFalse($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 0;
};
$this->assertTrue($op->evaluate($context));
}
}
@@ -0,0 +1,53 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class FloorTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$op = new Operator\Floor($varA);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$context = new Context();
$op = new Operator\Floor($varA);
$op->prepareValue($context);
}
/**
* @dataProvider ceilingData
*/
public function testCeiling($a, $result)
{
$varA = new Variable('a', $a);
$context = new Context();
$op = new Operator\Floor($varA);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function ceilingData()
{
return [
[1.2, 1],
[1.0, 1],
[1, 1],
[-0.5, -1],
[-1.5, -2],
];
}
}
@@ -0,0 +1,42 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class GreaterThanOrEqualToTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\GreaterThanOrEqualTo($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\GreaterThanOrEqualTo($varA, $varB);
$this->assertFalse($op->evaluate($context));
$context['a'] = 2;
$this->assertTrue($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 3;
};
$this->assertTrue($op->evaluate($context));
$context['4'] = 3;
$this->assertTrue($op->evaluate($context));
}
}
@@ -0,0 +1,39 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class GreaterThanTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\GreaterThan($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\GreaterThan($varA, $varB);
$this->assertFalse($op->evaluate($context));
$context['a'] = 2;
$this->assertFalse($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 0;
};
$this->assertTrue($op->evaluate($context));
}
}
@@ -0,0 +1,96 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class IntersectTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Intersect($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Intersect($varA, $varB);
$this->assertEquals(
[],
$op->prepareValue($context)->getValue()
);
}
/**
* @dataProvider intersectData
*/
public function testIntersect($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Intersect($varA, $varB);
$this->assertEquals(
$result,
$op->prepareValue($context)->getValue()
);
}
public function intersectData()
{
return [
[6, 2, []],
[
['a', 'c'],
'a',
['a'],
],
[
['a', 'b', 'c'],
[],
[],
],
[
[],
['a', 'b', 'c'],
[],
],
[
[],
[],
[],
],
[
['a', 'b', 'c'],
['d', 'e', 'f'],
[],
],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
['a', 'b', 'c'],
],
[
['a', 'b', 'c'],
['b', 'c'],
['b', 'c'],
],
[
['b', 'c'],
['b', 'd'],
['b'],
],
];
}
}
@@ -0,0 +1,42 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class LessThanOrEqualToTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\GreaterThanOrEqualTo($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\GreaterThanOrEqualTo($varA, $varB);
$this->assertFalse($op->evaluate($context));
$context['a'] = 2;
$this->assertTrue($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 3;
};
$this->assertTrue($op->evaluate($context));
$context['a'] = 2;
$this->assertFalse($op->evaluate($context));
}
}
@@ -0,0 +1,39 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class LessThanTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\LessThan($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\LessThan($varA, $varB);
$this->assertTrue($op->evaluate($context));
$context['a'] = 2;
$this->assertFalse($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 1;
};
$this->assertFalse($op->evaluate($context));
}
}
@@ -0,0 +1,55 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Test\Fixtures\FalseProposition;
use Ruler\Test\Fixtures\TrueProposition;
class LogicalAndTest extends TestCase
{
public function testInterface()
{
$true = new TrueProposition();
$op = new Operator\LogicalAnd([$true]);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructor()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalAnd([$true, $false]);
$this->assertFalse($op->evaluate($context));
}
public function testAddPropositionAndEvaluate()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalAnd();
$op->addProposition($true);
$this->assertTrue($op->evaluate($context));
$op->addOperand($true);
$this->assertTrue($op->evaluate($context));
$op->addProposition($false);
$this->assertFalse($op->evaluate($context));
}
public function testExecutingALogicalAndWithoutPropositionsThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalAnd();
$op->evaluate(new Context());
}
}
@@ -0,0 +1,55 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Test\Fixtures\FalseProposition;
use Ruler\Test\Fixtures\TrueProposition;
class LogicalNotTest extends TestCase
{
public function testInterface()
{
$true = new TrueProposition();
$op = new Operator\LogicalNot([$true]);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructor()
{
$op = new Operator\LogicalNot([new FalseProposition()]);
$this->assertTrue($op->evaluate(new Context()));
}
public function testAddPropositionAndEvaluate()
{
$op = new Operator\LogicalNot();
$op->addProposition(new TrueProposition());
$this->assertFalse($op->evaluate(new Context()));
}
public function testExecutingALogicalNotWithoutPropositionsThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalNot();
$op->evaluate(new Context());
}
public function testInstantiatingALogicalNotWithTooManyArgumentsThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalNot([new TrueProposition(), new FalseProposition()]);
}
public function testAddingASecondPropositionToLogicalNotThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalNot();
$op->addProposition(new TrueProposition());
$op->addProposition(new TrueProposition());
}
}
@@ -0,0 +1,55 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Test\Fixtures\FalseProposition;
use Ruler\Test\Fixtures\TrueProposition;
class LogicalOrTest extends TestCase
{
public function testInterface()
{
$true = new TrueProposition();
$op = new Operator\LogicalOr([$true]);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructor()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalOr([$true, $false]);
$this->assertTrue($op->evaluate($context));
}
public function testAddPropositionAndEvaluate()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalOr();
$op->addProposition($false);
$this->assertFalse($op->evaluate($context));
$op->addProposition($false);
$this->assertFalse($op->evaluate($context));
$op->addOperand($true);
$this->assertTrue($op->evaluate($context));
}
public function testExecutingALogicalOrWithoutPropositionsThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalOr();
$op->evaluate(new Context());
}
}
@@ -0,0 +1,58 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Test\Fixtures\FalseProposition;
use Ruler\Test\Fixtures\TrueProposition;
class LogicalXorTest extends TestCase
{
public function testInterface()
{
$true = new TrueProposition();
$op = new Operator\LogicalXor([$true]);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructor()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalXor([$true, $false]);
$this->assertTrue($op->evaluate($context));
}
public function testAddPropositionAndEvaluate()
{
$true = new TrueProposition();
$false = new FalseProposition();
$context = new Context();
$op = new Operator\LogicalXor();
$op->addProposition($false);
$this->assertFalse($op->evaluate($context));
$op->addOperand($false);
$this->assertFalse($op->evaluate($context));
$op->addProposition($true);
$this->assertTrue($op->evaluate($context));
$op->addOperand($true);
$this->assertFalse($op->evaluate($context));
}
public function testExecutingALogicalXorWithoutPropositionsThrowsAnException()
{
$this->expectException(\LogicException::class);
$op = new Operator\LogicalXor();
$op->evaluate(new Context());
}
}
@@ -0,0 +1,69 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class MaxTest extends TestCase
{
public function testInterface()
{
$var = new Variable('a', [5, 2, 9]);
$op = new Operator\Max($var);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
/**
* @dataProvider invalidData
*/
public function testInvalidData($datum)
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('max: all values must be numeric');
$var = new Variable('a', $datum);
$context = new Context();
$op = new Operator\Max($var);
$op->prepareValue($context);
}
public function invalidData()
{
return [
['string'],
[['string']],
[[1, 2, 3, 'string']],
[['string', 1, 2, 3]],
];
}
/**
* @dataProvider maxData
*/
public function testMax($a, $result)
{
$var = new Variable('a', $a);
$context = new Context();
$op = new Operator\Max($var);
$this->assertEquals(
$result,
$op->prepareValue($context)->getValue()
);
}
public function maxData()
{
return [
[5, 5],
[[], null],
[[5], 5],
[[-2, -5, -242], -2],
[[2, 5, 242], 242],
];
}
}
@@ -0,0 +1,69 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class MinTest extends TestCase
{
public function testInterface()
{
$var = new Variable('a', [5, 2, 9]);
$op = new Operator\Min($var);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
/**
* @dataProvider invalidData
*/
public function testInvalidData($datum)
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('min: all values must be numeric');
$var = new Variable('a', $datum);
$context = new Context();
$op = new Operator\Min($var);
$op->prepareValue($context);
}
public function invalidData()
{
return [
['string'],
[['string']],
[[1, 2, 3, 'string']],
[['string', 1, 2, 3]],
];
}
/**
* @dataProvider minData
*/
public function testMin($a, $result)
{
$var = new Variable('a', $a);
$context = new Context();
$op = new Operator\Min($var);
$this->assertEquals(
$result,
$op->prepareValue($context)->getValue()
);
}
public function minData()
{
return [
[5, 5],
[[], null],
[[5], 5],
[[-2, -5, -242], -242],
[[2, 5, 242], 2],
];
}
}
@@ -0,0 +1,65 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class ModuloTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Modulo($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Modulo($varA, $varB);
$op->prepareValue($context);
}
public function testDivideByZero()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Division by zero');
$varA = new Variable('a', \random_int(1, 100));
$varB = new Variable('b', 0);
$context = new Context();
$op = new Operator\Modulo($varA, $varB);
$op->prepareValue($context);
}
/**
* @dataProvider moduloData
*/
public function testModulo($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Modulo($varA, $varB);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function moduloData()
{
return [
[6, 2, 0],
[7, 3, 1],
];
}
}
@@ -0,0 +1,54 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class MultiplicationTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', [2]);
$op = new Operator\Multiplication($varA, $varB);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$varB = new Variable('b', 'blah');
$context = new Context();
$op = new Operator\Multiplication($varA, $varB);
$op->prepareValue($context);
}
/**
* @dataProvider multiplyData
*/
public function testMultiply($a, $b, $result)
{
$varA = new Variable('a', $a);
$varB = new Variable('b', $b);
$context = new Context();
$op = new Operator\Multiplication($varA, $varB);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function multiplyData()
{
return [
[6, 2, 12],
[7, 3, 21],
[2.5, 1.5, 3.75],
];
}
}
@@ -0,0 +1,52 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class NegationTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$op = new Operator\Negation($varA);
$this->assertInstanceOf(\Ruler\VariableOperand::class, $op);
}
public function testInvalidData()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Arithmetic: values must be numeric');
$varA = new Variable('a', 'string');
$context = new Context();
$op = new Operator\Negation($varA);
$op->prepareValue($context);
}
/**
* @dataProvider negateData
*/
public function testSubtract($a, $result)
{
$varA = new Variable('a', $a);
$context = new Context();
$op = new Operator\Negation($varA);
$this->assertEquals($op->prepareValue($context)->getValue(), $result);
}
public function negateData()
{
return [
[1, -1],
[0.0, 0.0],
['0', 0],
[-62834, 62834],
];
}
}
@@ -0,0 +1,42 @@
<?php
namespace Ruler\Test\Operator;
use PHPUnit\Framework\TestCase;
use Ruler\Context;
use Ruler\Operator;
use Ruler\Variable;
class NotEqualToTest extends TestCase
{
public function testInterface()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$op = new Operator\NotEqualTo($varA, $varB);
$this->assertInstanceOf(\Ruler\Proposition::class, $op);
}
public function testConstructorAndEvaluation()
{
$varA = new Variable('a', 1);
$varB = new Variable('b', 2);
$context = new Context();
$op = new Operator\NotEqualTo($varA, $varB);
$this->assertTrue($op->evaluate($context));
$context['a'] = 2;
$this->assertFalse($op->evaluate($context));
$context['a'] = 3;
$context['b'] = function () {
return 3;
};
$this->assertFalse($op->evaluate($context));
$context['a'] = 1;
$this->assertTrue($op->evaluate($context));
}
}

Some files were not shown because too many files have changed in this diff Show More