Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ php:
- 5.5
- 5.6
- 7.0
- 7.1

install:
- alias composer=composer\ -n && composer selfupdate
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": "LGPL-3.0",
"require": {
"php": ">=5.5",
"scriptfusion/mapper": "^1|^0",
"scriptfusion/mapper": "^1",
"scriptfusion/static-class": "^1",
"scriptfusion/retry-error-handlers": "^1",
"psr/cache": "^1",
Expand Down
34 changes: 34 additions & 0 deletions src/Options/EncapsulatedOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ final public function copy()
return $this->options + $this->defaults;
}

/**
* Merges the specified overriding options into this instance giving precedence to the overriding options. Only
* option values that have been explicitly set are merged; that is, defaults are not merged.
*
* @param EncapsulatedOptions $overridingOptions Overriding options.
*/
final public function merge(EncapsulatedOptions $overridingOptions)
{
if (!$overridingOptions instanceof static) {
throw new MergeException('Cannot merge: options must be an instance of "' . get_class($this) . '".');
}

$this->options = $this->mergeOptions($this->options, $overridingOptions->options);
}

/**
* Merges the specified options with the specified overrides giving precedence to the overriding options.
*
* Overriding this method in a derived class allows it to implement a custom merging strategy.
*
* @param array $options Options of this instance.
* @param array $overrides Overriding options.
*
* @return array Merged options.
*/
protected function mergeOptions(array $options, array $overrides)
{
return $overrides + $options;
}

/**
* Gets the value for the specified option name. Returns the specified
* default value if option name is not set.
Expand Down Expand Up @@ -61,6 +91,10 @@ final protected function &getReference($option)
*/
final protected function set($option, $value)
{
if (is_object($value) || is_resource($value)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: This value type checking needs to be extended to recursively check array values and defaults.

throw new \InvalidArgumentException('Value must not be an object or resource.');
}

$this->options["$option"] = $value;

return $this;
Expand Down
10 changes: 10 additions & 0 deletions src/Options/MergeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
namespace ScriptFUSION\Porter\Options;

/**
* The exception that is thrown when options cannot be merged.
*/
final class MergeException extends \RuntimeException
{
// Intentionall empty.
}
2 changes: 1 addition & 1 deletion test/Porter/Options/TestOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use ScriptFUSION\Porter\Options\EncapsulatedOptions;

final class TestOptions extends EncapsulatedOptions
class TestOptions extends EncapsulatedOptions
{
public function __construct()
{
Expand Down
63 changes: 63 additions & 0 deletions test/Unit/Porter/Options/EncapsulatedOptionsTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace ScriptFUSIONTest\Unit\Porter\Options;

use ScriptFUSION\Porter\Options\EncapsulatedOptions;
use ScriptFUSION\Porter\Options\MergeException;
use ScriptFUSIONTest\Porter\Options\TestOptions;

final class EncapsulatedOptionsTest extends \PHPUnit_Framework_TestCase
Expand All @@ -25,6 +27,20 @@ public function testSet()
self::assertSame('bar', $this->options->getFoo());
}

public function testSetObject()
{
$this->setExpectedException(\InvalidArgumentException::class);

$this->options->setFoo($this->options);
}

public function testSetResource()
{
$this->setExpectedException(\InvalidArgumentException::class);

$this->options->setFoo(STDIN);
}

public function testSetNullOverridesDefault()
{
$this->options->setFoo(null);
Expand All @@ -41,6 +57,53 @@ public function testCopy()
self::assertSame(['foo' => 'bar'], $this->options->copy());
}

public function testMerge()
{
$a = $this->options;
$b = (new TestOptions)->setFoo('bar');
$c = clone $a;

self::assertSame('foo', $a->getFoo());
self::assertSame('bar', $b->getFoo());
self::assertSame('foo', $c->getFoo());

// Merging in b sets c to 'bar'.
$c->merge($b);

self::assertSame('foo', $a->getFoo());
self::assertSame('bar', $b->getFoo());
self::assertSame('bar', $c->getFoo());

// Merging in a does not change the value of c because no options have been set explicitly for a.
$c->merge($a);

self::assertSame('foo', $a->getFoo());
self::assertSame('bar', $b->getFoo());
self::assertSame('bar', $c->getFoo());

// Merging in a sets c to 'foo' after it has been explicitly set for a.
$a->setFoo('foo');
$c->merge($a);

self::assertSame('foo', $a->getFoo());
self::assertSame('bar', $b->getFoo());
self::assertSame('foo', $c->getFoo());
}

public function testMergeDerivedClass()
{
$this->options->merge(\Mockery::mock(TestOptions::class));

// PHPUnit asserts no exception is thrown.
}

public function testMergeNonDerivedClass()
{
$this->setExpectedException(MergeException::class, TestOptions::class);

$this->options->merge(\Mockery::mock(EncapsulatedOptions::class));
}

public function testGetReference()
{
$this->options->setFoo(['bar' => 'bar', 'baz' => 'baz']);
Expand Down