├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
└── CleverAge
│ └── Ruler
│ ├── Exception
│ ├── Exception.php
│ └── NotSatisfiedException.php
│ ├── Rule
│ └── ObjectIsEqual.php
│ ├── RuleAbstract.php
│ └── RuleInterface.php
└── tests
├── CleverAge
└── Ruler
│ └── Test
│ ├── Fixtures
│ ├── BarObject.php
│ ├── Exception
│ │ ├── Custom2Exception.php
│ │ └── CustomException.php
│ ├── FooChildObject.php
│ ├── FooObject.php
│ └── Rule
│ │ ├── FalsyRule.php
│ │ └── TruelyRule.php
│ ├── Functional
│ └── RulerTest.php
│ └── Rule
│ └── ObjectIsEqualTest.php
└── bootstrap.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
3 | composer.phar
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.3
5 | - 5.4
6 | - 5.5
7 |
8 | before_script:
9 | - curl -s http://getcomposer.org/installer | php
10 | - php composer.phar install --dev
11 |
12 | script: phpunit
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v1.0.0 (master)
2 | =====
3 | * add more tests
4 | * `RuleInterface::doIsSatisfied` and `RuleInterface::doIsNotSatisfied` become protected (previously public) and move to `RuleAbstract` class.
5 |
6 | v0.1.0 : Initial version
7 | =====
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/cleverage/Ruler)
2 |
3 | Ruler
4 | =====
5 |
6 | cleverage/Ruler is a PHP 5.3 Library. Use it to implement your own business rules, and link them in order to check if you are satisfying the set.
7 |
8 | Exemple :
9 |
10 | You want to check if the current user can activate a feature. For this, you have to check that :
11 | - he is connected,
12 | - he has the premium suscription,
13 | - he has enough money in his account.
14 |
15 | ```php
16 | andRule(new IsSuscriberRule($user, 'PREMIUM'))
21 | ->andRule(new HasMoneyRule($user, 300))
22 | ->orRule(new IsAdminRule($user));
23 |
24 | // PHP 5.4
25 | $rule = (new IsConnectedRule($user))
26 | ->andRule(new IsSuscriberRule($user, 'PREMIUM'))
27 | ->andRule(new HasMoneyRule($user, 300))
28 | ->orRule(new IsAdminRule($user));
29 |
30 | try {
31 | if ($rule->isSatisfied()) {
32 | echo 'activated';
33 | }
34 | } catch (NotConnectedException $e) {
35 | // show connection form
36 | } catch (NotSuscriberException $e) {
37 | // show subscription form
38 | } catch (NotEnoughMoneyException $e) {
39 | echo 'not enough Money';
40 | } catch(\CleverAge\Ruler\Exception\Exception $e) {
41 | echo 'Failed : '.$e->getMessage();
42 | }
43 | ```
44 |
45 | ```php
46 | _user = $user;
57 | }
58 |
59 | public function doIsSatisfied()
60 | {
61 | return $this->_user->isLoggedOn();
62 | }
63 | }
64 | ```
65 |
66 | Combination of rules can even be done in a single rule class, in order to simplify your application code and increase maintability.
67 |
68 | ```php
69 | // ActiveFeatureXRule class
70 | class ActiveFeatureXRule extends \CleverAge\Ruler\RuleAbstract
71 | {
72 | public function __construct(\User $user)
73 | {
74 | $this->andRule(new IsSuscriberRule($user, 'PREMIUM'))
75 | ->andRule(new HasMoneyRule($user, 300))
76 | ->orRule(new IsAdminRule($user));
77 | }
78 |
79 | public function doIsSatisfied()
80 | {
81 | // method is abstract, and this container rule always satisfies.
82 | return true;
83 | }
84 | }
85 | ```
86 |
87 | Now, you can use this rule class everywhere you need it, and just change the construct to have th rules reverberated everewhere.
88 |
89 | ## How logic is handled
90 |
91 | The order in which you set OR/AND/NAND rules is not important. At the end, they are grouped by type.
92 |
93 | 1) You want your ruleset satisfied :
94 |
95 | ```php
96 | // A,B,C,D,G,Z are rules
97 | $A->andRule($B)
98 | ->orRule($C->andRule($Z))
99 | ->andRule($D)
100 | ->nandRule($G)
101 | ->isSatisfied();
102 |
103 | // PHP =>($A && $B && $D && !$G) || ($C && $Z)
104 | // Binary => (A.B.D.!G)+(C.Z)
105 | ```
106 |
107 | 2) You want your ruleset not satisfied :
108 |
109 | ```php
110 | // A,B,C,D,G,Z are rules
111 | $A->andRule($B)
112 | ->orRule($C->andRule($Z))
113 | ->andRule($D)
114 | ->nandRule($G)
115 | ->isNotSatisfied()
116 |
117 | // PHP => (!$A || !$B || !$D || $G) && (!$C || !$Z)
118 | // Binary => (!A+!B+!D+G).(!C+!Z)
119 | ```
120 |
121 | by default, isNotSatisfied() returns !isSatisfied(). But sometimes, you may want to personnalize the doIsNotSatisfied() method in order to optimize workflow (like SQL queries).
122 |
123 | ## Customization
124 |
125 | ### Setting Exception class and message error
126 |
127 | If you have one generic rule (e.g. : ObjectIsEqual), you may need to have different exception thrown when composing complex rules.
128 | Each Rule has a setter for this :
129 |
130 | ```php
131 | $A = new MyRule();
132 | $A->setException('My\Name\Space\Ruler\Exceptions\MyException', 'my custom error message');
133 |
134 | $A->isSatisfied();
135 |
136 | // If rule is not satisfied
137 | // it throws a new My\Name\Space\Ruler\Exceptions\MyException('my custom error message') exception
138 | ```
139 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cleverage/ruler",
3 | "description": "A business rules engine",
4 | "keywords": ["rules"],
5 | "homepage": "https://github.com/cleverage/Ruler",
6 | "license": "MIT",
7 | "require": {
8 | "php": ">=5.3.0"
9 | },
10 | "autoload": {
11 | "psr-0": { "CleverAge\\Ruler": "src/" }
12 | },
13 | "authors": [
14 | {
15 | "name": "Florian Vilpoix"
16 | },
17 | {
18 | "name": "Florian Lonqueu-Brochard"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./tests/CleverAge/Ruler/Test/
7 |
8 |
9 |
10 |
11 |
12 | ./src/CleverAge/Ruler/
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/CleverAge/Ruler/Exception/Exception.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Florian Vilpoix
11 | * @since 2012-10-17
12 | */
13 | class Exception extends \Exception
14 | {
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/CleverAge/Ruler/Exception/NotSatisfiedException.php:
--------------------------------------------------------------------------------
1 | object1 = $object1;
33 | $this->object2 = $object2;
34 | $this->identifierMethod = $identifierMethod;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function doIsSatisfied()
41 | {
42 | $class = is_object($this->object1) ? get_class($this->object1) : false;
43 |
44 | return $class
45 | && (($this->object1 === $this->object2)
46 | || (($this->object2 instanceof $class)
47 | && ($this->object1->{$this->identifierMethod}()
48 | == $this->object2->{$this->identifierMethod}())
49 | ));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/CleverAge/Ruler/RuleAbstract.php:
--------------------------------------------------------------------------------
1 |
12 | * @author Florian Vilpoix
13 | * @since 2012-10-17
14 | */
15 | abstract class RuleAbstract implements RuleInterface
16 | {
17 | /**
18 | * @var string Full qualified class name for the exception to throw when the rule must be satisfied but is not.
19 | * Must inherit from CleverAge\Ruler\Exception\Exception
20 | */
21 | protected $_failure_exception_class = 'CleverAge\Ruler\Exception\NotSatisfiedException';
22 |
23 | /**
24 | * @var string String to put into the exception thrown when rule is not satisfied when it should.
25 | * Should be a generic label, then use your translation system to replace it
26 | * in your application.
27 | */
28 | protected $_failure_message;
29 |
30 | /**
31 | * @var string Full qualified class name for the exception to throw when the rule must not be satisfied but is.
32 | */
33 | protected $_not_failure_exception_class = 'CleverAge\Ruler\Exception\NotSatisfiedException';
34 |
35 | /**
36 | * @var string String to put into the exception thrown when rule is satisfied when it should not.
37 | * Should be a generic label, then use your translation system to replace it
38 | * in your application.
39 | */
40 | protected $_not_failure_message;
41 |
42 | /**
43 | * @var array Collection of all OR rules
44 | */
45 | protected $_or_children = array();
46 |
47 | /**
48 | * @var array Collection of all AND rules
49 | */
50 | protected $_and_children = array();
51 |
52 | /**
53 | * @var array Collection of all NAND rules
54 | */
55 | protected $_nand_children = array();
56 |
57 | /**
58 | * Executes the current rule and check that it's satisfied
59 | *
60 | * @return boolean true when satisfied, false otherwise
61 | */
62 | abstract protected function doIsSatisfied();
63 |
64 | /**
65 | * Executes the current rule and check that it's not satisfied
66 | *
67 | * @return boolean true when not satisfied, false otherwise
68 | */
69 | protected function doIsNotSatisfied()
70 | {
71 | return !$this->doIsSatisfied();
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function isSatisfied()
78 | {
79 | $exception_to_throw = null;
80 |
81 | // first, check that current rule and all AND are satisfied, and NAND not
82 | // satisfied.
83 | try {
84 | if (!$this->doIsSatisfied()) {
85 | $this->_throwException();
86 | }
87 |
88 | foreach ($this->_and_children as $child) {
89 | $child->isSatisfied();
90 | }
91 |
92 | foreach ($this->_nand_children as $child) {
93 | $child->isNotSatisfied();
94 | }
95 | } catch (RulerException $e) {
96 | if (is_null($exception_to_throw)) {
97 | $exception_to_throw = $e;
98 | }
99 | }
100 |
101 | // if satisfied, then rule is a success
102 | if (is_null($exception_to_throw)) {
103 | return true;
104 | }
105 |
106 | // if one rule failed before, then check all OR rules. At least one must
107 | // satisfied in order to validate everything.
108 | foreach($this->_or_children as $child) {
109 | try {
110 | $child->isSatisfied();
111 | return true;
112 | } catch (RulerException $e) {
113 | if (is_null($exception_to_throw)) {
114 | $exception_to_throw = $e;
115 | }
116 | }
117 | }
118 |
119 | // If we are here, no rules group has been satisfied, so throws first
120 | // error found.
121 | throw $exception_to_throw;
122 | }
123 |
124 | /**
125 | * {@inheritdoc}
126 | */
127 | public function isNotSatisfied()
128 | {
129 | $exception_to_throw = null;
130 |
131 | // first, check that at least one rule bewteen current and AND is not
132 | // satisfied or that one NAND is satisfied
133 | try {
134 | if (!$this->doIsNotSatisfied()) {
135 | $this->_throwNotException();
136 | }
137 | } catch (RulerException $e) {
138 | if (is_null($exception_to_throw)) {
139 | $exception_to_throw = $e;
140 | }
141 | }
142 |
143 | if (!is_null($exception_to_throw)) {
144 | foreach ($this->_and_children as $child) {
145 | try {
146 | $child->isNotSatisfied();
147 | } catch (RulerException $e) {
148 | if (is_null($exception_to_throw)) {
149 | $exception_to_throw = $e;
150 | }
151 | }
152 | }
153 | }
154 |
155 | if (!is_null($exception_to_throw)) {
156 | foreach ($this->_nand_children as $child) {
157 | try {
158 | $child->isSatisfied();
159 | } catch (RulerException $e) {
160 | if (is_null($exception_to_throw)) {
161 | $exception_to_throw = $e;
162 | }
163 | }
164 | }
165 | }
166 |
167 | // if no rule satisfied, then rule is failure
168 | if (!is_null($exception_to_throw)) {
169 | throw $exception_to_throw;
170 | }
171 |
172 | // All OR rules must not satisfied
173 | foreach($this->_or_children as $child) {
174 | $child->isNotSatisfied();
175 | }
176 |
177 | return true;
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function andRule(RuleInterface $rule)
184 | {
185 | $this->_and_children[] = $rule;
186 | return $this;
187 | }
188 |
189 | /**
190 | * {@inheritdoc}
191 | */
192 | public function orRule(RuleInterface $rule)
193 | {
194 | $this->_or_children[] = $rule;
195 | return $this;
196 | }
197 |
198 | /**
199 | * {@inheritdoc}
200 | */
201 | public function nandRule(RuleInterface $rule)
202 | {
203 | $this->_nand_children[] = $rule;
204 | return $this;
205 | }
206 |
207 | /**
208 | * @throws CleverAge\Ruler\Exception\Exception
209 | */
210 | protected function _throwException()
211 | {
212 | $class = $this->_failure_exception_class;
213 | throw new $class($this->_failure_message);
214 | }
215 |
216 | /**
217 | * @throws CleverAge\Ruler\Exception\Exception
218 | */
219 | protected function _throwNotException()
220 | {
221 | $class = $this->_not_failure_exception_class;
222 | throw new $class($this->_not_failure_message);
223 | }
224 |
225 | /**
226 | * Override the default class used to generate error.
227 | * Use this if you want to use the same Rule but to generate differents
228 | * errors.
229 | *
230 | * @param string $exceptionClass The Full qualified name of the classe. Must inherit from CleverAge\Ruler\Exception\Exception
231 | * @param string $msg The message returned by the exception
232 | *
233 | * @return \CleverAge\Ruler\RuleInterface
234 | */
235 | public function setException($exceptionClass, $msg = null)
236 | {
237 | $this->_failure_exception_class = $exceptionClass;
238 | $this->_failure_message = $msg;
239 |
240 | return $this;
241 | }
242 |
243 | /**
244 | * @see self::setException()
245 | *
246 | * @param string $exceptionClass
247 | * @param string $msg
248 | *
249 | * @return \CleverAge\Ruler\RuleInterface
250 | */
251 | public function setNotException($exceptionClass, $msg = null)
252 | {
253 | $this->_not_failure_exception_class = $exceptionClass;
254 | $this->_not_failure_message = $msg;
255 |
256 | return $this;
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/src/CleverAge/Ruler/RuleInterface.php:
--------------------------------------------------------------------------------
1 |
10 | * @author Florian Vilpoix
11 | * @since 2012-10-17
12 | */
13 | interface RuleInterface
14 | {
15 | /**
16 | * Executes the current rule, and its and|or associated rules.
17 | *
18 | * A->and(B)
19 | * ->or(C->and(Z))
20 | * ->and(D)
21 | * ->nand(G)
22 | * ->isSatisfied()
23 | *
24 | * => (A.B.D.!G)+(C.Z)
25 | *
26 | * @return boolean true when satisfied, throws exception otherwise.
27 | * @throws \CleverAge\Ruler\Exception\Exception
28 | */
29 | public function isSatisfied();
30 |
31 | /**
32 | * Executes the current rule, and its and|or associated rules. Check that
33 | * the rule fail.
34 | *
35 | * A->and(B)
36 | * ->or(C->and(Z))
37 | * ->and(D)
38 | * ->nand(G)
39 | * ->isNotSatisfied()
40 | *
41 | * => (!A+!B+!D+G).(!C+!Z)
42 | *
43 | * @return boolean true if rule failed, throws exception otherwise
44 | * @throws \CleverAge\Ruler\Exception\Exception
45 | */
46 | public function isNotSatisfied();
47 |
48 | /**
49 | * Add a sub-rule to current. Both current and new rule must be satisfied
50 | *
51 | * @param \CleverAge\Ruler\RuleInterface $rule
52 | * @return \CleverAge\Ruler\RuleInterface
53 | */
54 | public function andRule(RuleInterface $rule);
55 |
56 | /**
57 | * Add a sub-rule to current.
58 | * (Current rule and AND rule) or (new OR rule) must satisfied.
59 | *
60 | * @param \CleverAge\Ruler\RuleInterface $rule
61 | * @return \CleverAge\Ruler\RuleInterface
62 | */
63 | public function orRule(RuleInterface $rule);
64 |
65 | /**
66 | * Add a sub-rule to current.
67 | * rule must not satisfied to have current rule satisfied.
68 | *
69 | * @param \CleverAge\Ruler\RuleInterface $rule
70 | * @return \CleverAge\Ruler\RuleInterface
71 | */
72 | public function nandRule(RuleInterface $rule);
73 | }
74 |
--------------------------------------------------------------------------------
/tests/CleverAge/Ruler/Test/Fixtures/BarObject.php:
--------------------------------------------------------------------------------
1 | id = $id;
12 | }
13 |
14 | public function getId()
15 | {
16 | return $this->id;
17 | }
18 | }
--------------------------------------------------------------------------------
/tests/CleverAge/Ruler/Test/Fixtures/Exception/Custom2Exception.php:
--------------------------------------------------------------------------------
1 | id = $id;
12 | }
13 |
14 | public function getId()
15 | {
16 | return $this->id;
17 | }
18 |
19 | public function setId($id)
20 | {
21 | $this->id = $id;
22 |
23 | return $this;
24 | }
25 | }
--------------------------------------------------------------------------------
/tests/CleverAge/Ruler/Test/Fixtures/Rule/FalsyRule.php:
--------------------------------------------------------------------------------
1 | assertTrue($rule->isSatisfied());
14 |
15 | $this->setExpectedException(
16 | 'CleverAge\Ruler\Test\Fixtures\Exception\CustomException',
17 | 'Hello Pony !'
18 | );
19 |
20 | $rule = new Example\FalsyRule();
21 | $rule->isSatisfied();
22 | }
23 |
24 | public function testIsNotSatisfied()
25 | {
26 | $rule = new Example\FalsyRule();
27 | $this->assertTrue($rule->isNotSatisfied());
28 |
29 | $this->setExpectedException(
30 | 'CleverAge\Ruler\Test\Fixtures\Exception\Custom2Exception',
31 | 'Bye Pony !'
32 | );
33 |
34 | $rule = new Example\TruelyRule();
35 | $rule->isNotSatisfied();
36 | }
37 |
38 | public function testCombination1()
39 | {
40 | // 1 . 1 . 1
41 | $rule = new Example\TruelyRule();
42 | $rule
43 | ->andRule(new Example\TruelyRule())
44 | ->andRule(new Example\TruelyRule());
45 | $this->assertTrue($rule->isSatisfied());
46 |
47 | // 0 . 1 + 1
48 | $rule2 = new Example\FalsyRule();
49 | $rule2
50 | ->orRule(new Example\TruelyRule())
51 | ->andRule(new Example\TruelyRule());
52 | $this->assertTrue($rule2->isSatisfied());
53 |
54 | // 1 . 1 + 0
55 | $rule3 = new Example\TruelyRule();
56 | $rule3
57 | ->orRule(new Example\FalsyRule())
58 | ->andRule(new Example\TruelyRule());
59 | $this->assertTrue($rule3->isSatisfied());
60 |
61 | // 0 . 0 + 0 + 1
62 | $rule4 = new Example\FalsyRule();
63 | $rule4
64 | ->orRule(new Example\FalsyRule())
65 | ->orRule(new Example\TruelyRule())
66 | ->andRule(new Example\FalsyRule());
67 | $this->assertTrue($rule4->isSatisfied());
68 |
69 | // 1 . !0 + 0
70 | $rule5 = new Example\TruelyRule();
71 | $rule5
72 | ->orRule(new Example\FalsyRule())
73 | ->nandRule(new Example\FalsyRule());
74 | $this->assertTrue($rule5->isSatisfied());
75 |
76 | $this->setExpectedException('CleverAge\Ruler\Test\Fixtures\Exception\CustomException');
77 |
78 | // 1 . 1 . 0
79 | $rule6 = new Example\TruelyRule();
80 | $rule6
81 | ->andRule(new Example\TruelyRule())
82 | ->andRule(new Example\FalsyRule());
83 | $rule6->isSatisfied();
84 | }
85 |
86 | /**
87 | * @expectedException CleverAge\Ruler\Test\Fixtures\Exception\CustomException
88 | */
89 | public function testCombinationFail1()
90 | {
91 | // 1 . 0 + 0 + 0
92 | $rule = new Example\TruelyRule();
93 | $rule
94 | ->andRule(new Example\FalsyRule())
95 | ->orRule(new Example\FalsyRule());
96 | $rule->isSatisfied();
97 | }
98 |
99 | /**
100 | * @expectedException CleverAge\Ruler\Test\Fixtures\Exception\CustomException
101 | */
102 | public function testCombinationFail2()
103 | {
104 | // 1 . 0 + 0 . 1 + 0
105 | $subrule = new Example\TruelyRule();
106 | $subrule->andRule(new Example\FalsyRule());
107 |
108 | $rule = new Example\TruelyRule();
109 | $rule
110 | ->andRule(new Example\FalsyRule())
111 | ->orRule(new Example\FalsyRule())
112 | ->orRule($subrule);
113 |
114 | $rule->isSatisfied();
115 | }
116 |
117 | /**
118 | * @expectedException CleverAge\Ruler\Test\Fixtures\Exception\CustomException
119 | */
120 | public function testCombinationFail3()
121 | {
122 | // 0 . 1 + 1 . !1
123 | $subrule = new Example\TruelyRule();
124 | $subrule->nandRule(new Example\TruelyRule());
125 |
126 | $rule = new Example\FalsyRule();
127 | $rule
128 | ->andRule(new Example\TruelyRule())
129 | ->orRule($subrule);
130 |
131 | $rule->isSatisfied();
132 | }
133 |
134 | /**
135 | * @expectedException CleverAge\Ruler\Test\Fixtures\Exception\Custom2Exception
136 | */
137 | public function testOverrideException()
138 | {
139 | $rule = new Example\FalsyRule();
140 | $rule->setException('CleverAge\Ruler\Test\Fixtures\Exception\Custom2Exception');
141 | $rule->isSatisfied();
142 | }
143 | }
--------------------------------------------------------------------------------
/tests/CleverAge/Ruler/Test/Rule/ObjectIsEqualTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($satisfyingRule->isSatisfied());
22 | }
23 |
24 | public function satifyingRules()
25 | {
26 | $foo = new Foo();
27 |
28 | return array(
29 | array(new ObjectIsEqual($foo, $foo)),
30 | array(new ObjectIsEqual($foo, $foo->setId(1))),
31 | array(new ObjectIsEqual(new Foo(2), new Foo(2))),
32 | array(new ObjectIsEqual(new Foo(2), new FooChild(2))),
33 | );
34 | }
35 |
36 | /**
37 | * @dataProvider unsatifyingRules
38 | * @test
39 | */
40 | public function it_should_not_be_satisfied($unsatisfyingRule)
41 | {
42 | $this->setExpectedException('CleverAge\Ruler\Exception\NotSatisfiedException');
43 |
44 | $unsatisfyingRule->isSatisfied();
45 | }
46 |
47 | public function unsatifyingRules()
48 | {
49 | return array(
50 | array(new ObjectIsEqual(new Foo(), new Bar())),
51 | array(new ObjectIsEqual(new Foo(2), new Bar(2))),
52 | array(new ObjectIsEqual(1, new Bar())),
53 | array(new ObjectIsEqual(new Foo(), 'pony'))
54 | );
55 | }
56 |
57 | /**
58 | * @test
59 | */
60 | public function it_should_be_instantiable()
61 | {
62 | $this->assertInstanceOf(
63 | 'CleverAge\Ruler\Rule\ObjectIsEqual',
64 | new ObjectIsEqual(new Foo(), new Bar(), 'getName')
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | add('CleverAge\Ruler\Test', __DIR__);
16 | $loader->register();
--------------------------------------------------------------------------------