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:
Vendored
+47
@@ -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"
|
||||
+31
@@ -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+.
|
||||
|
||||
[](https://packagist.org/packages/ruler/ruler)
|
||||
[](https://github.com/bobthecow/Ruler/actions?query=branch:main)
|
||||
[](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.
|
||||
+30
@@ -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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
+15
@@ -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>
|
||||
+237
@@ -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);
|
||||
}
|
||||
}
|
||||
+64
@@ -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();
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -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));
|
||||
}
|
||||
}
|
||||
+427
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
+101
@@ -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);
|
||||
}
|
||||
}
|
||||
+58
@@ -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;
|
||||
}
|
||||
}
|
||||
+248
@@ -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;
|
||||
}
|
||||
}
|
||||
+85
@@ -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);
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
+92
@@ -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);
|
||||
}
|
||||
}
|
||||
+299
@@ -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()],
|
||||
];
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Ruler\Test\Fixtures;
|
||||
|
||||
class Invokable
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke($value = null)
|
||||
{
|
||||
return new Fact($value);
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
}
|
||||
+371
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+266
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
+53
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+53
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+106
@@ -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'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+65
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+65
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+44
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+44
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+39
@@ -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));
|
||||
}
|
||||
}
|
||||
+53
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+30
@@ -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));
|
||||
}
|
||||
}
|
||||
+53
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+42
@@ -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));
|
||||
}
|
||||
}
|
||||
+39
@@ -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));
|
||||
}
|
||||
}
|
||||
+96
@@ -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'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+42
@@ -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));
|
||||
}
|
||||
}
|
||||
+39
@@ -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));
|
||||
}
|
||||
}
|
||||
+55
@@ -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());
|
||||
}
|
||||
}
|
||||
+55
@@ -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());
|
||||
}
|
||||
}
|
||||
+55
@@ -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());
|
||||
}
|
||||
}
|
||||
+58
@@ -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());
|
||||
}
|
||||
}
|
||||
+69
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+69
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+65
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+54
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+52
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
+42
@@ -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
Reference in New Issue
Block a user