diff --git a/src/Mediator/Exposer.php b/src/Mediator/Exposer.php new file mode 100644 index 0000000..0a1cdcf --- /dev/null +++ b/src/Mediator/Exposer.php @@ -0,0 +1,166 @@ + + */ +class Exposer +{ + private $baseUrl; + private $resolvers; + + public function __construct($baseUrl = null) + { + $this->baseUrl = $baseUrl; + $this->resolvers = new \SplObjectStorage(); + } + + /** + * Returns the source for the given media and options + * + * + * @param mixed $media + * @param array $options + * + * @return string + */ + public function getSource($media, array $options = array(), $forceAbsolute = false) + { + $source = null; + + foreach ($this->getSortedSourceResolvers() as $resolver) { + if ($resolver->supports($media, $options)) { + $source = $resolver->getSource($media, $options); + $sourceType = $resolver->getSourceType($media, $options); + } + } + + if (null === $source) { + throw new \RuntimeException( + 'There is no source resolver for the given media and options.' + ); + } + + if ($forceAbsolute && SourceResolver::TYPE_RELATIVE === $sourceType) { + $source = $this->absolutify($source); + } + + return $source; + } + + /** + * Returns the path for the given media and options + * + * @param mixed $media + * @param array $options + * + * @return string + */ + public function getPath($media, array $options = array()) + { + foreach ($this->getSortedPathResolvers() as $resolver) { + if ($resolver->supports($media, $options)) { + return $resolver->getPath($media, $options); + } + } + + throw new \RuntimeException( + 'There is no path resolver for the given media and options.' + ); + } + + public function addResolver(Resolver $resolver, $priority = 0) + { + $this->resolvers[$resolver] = $priority; + } + + /** + * Returns all the registered resolvers in the same order they were added + * + * @return Traversable + */ + public function getResolvers() + { + return new \IteratorIterator($this->resolvers); + } + + /** + * Returns all the registered resolvers sorted by priority + * + * @return Traversable + */ + public function getSortedResolvers() + { + return new SortedResolverIterator($this->resolvers); + } + + /** + * Returns all the registerd source resolvers in the same order they were + * added + * + * @return Traversable + */ + public function getSourceResolvers() + { + return new SourceResolverFilterIterator($this->getResolvers()); + } + + /** + * Returns all the registered source resolvers sorted by priority + * + * @return Traversable + */ + public function getSortedSourceResolvers() + { + return new SourceResolverFilterIterator($this->getSortedResolvers()); + } + + /** + * Returns all the registered path resolvers in the same order they were + * added + * + * @return Traversable + */ + public function getPathResolvers() + { + return new PathResolverFilterIterator($this->getResolvers()); + } + + /** + * Returns all the registered path resolvers sorted by priority + * + * @return Traversable + */ + public function getSortedPathResolvers() + { + return new PathResolverFilterIterator($this->getSortedResolvers()); + } + + /** + * Converts the given relative path into an absolute URL + * + * @param string $relativePath + * + * @return string The result absolute URL + */ + private function absolutify($relativePath) + { + if (null === $this->baseUrl) { + throw new \LogicException(sprintf( + 'Cannot absolutify the relative path \'%s\' as the base url is not configured.', + $relativePath + )); + } + + return $this->baseUrl . $relativePath; + } +} diff --git a/src/Mediator/Iterator/PathResolverFilterIterator.php b/src/Mediator/Iterator/PathResolverFilterIterator.php new file mode 100644 index 0000000..d6502d5 --- /dev/null +++ b/src/Mediator/Iterator/PathResolverFilterIterator.php @@ -0,0 +1,24 @@ + + */ +class PathResolverFilterIterator extends \FilterIterator +{ + /** + * {@inheritDoc} + */ + public function accept() + { + return $this->current() instanceof PathResolver; + } +} diff --git a/src/Mediator/Iterator/SortedResolverIterator.php b/src/Mediator/Iterator/SortedResolverIterator.php new file mode 100644 index 0000000..564bbf7 --- /dev/null +++ b/src/Mediator/Iterator/SortedResolverIterator.php @@ -0,0 +1,37 @@ + + */ +class SortedResolverIterator extends \ArrayIterator +{ + /** + * Constructor + * + * @param SplObjectStorage $objectStorage + */ + public function __construct(\SplObjectStorage $storage) + { + $groupedByPriority = array(); + + foreach ($storage as $object) { + $groupedByPriority[$storage[$object]][] = $object; + } + + ksort($groupedByPriority); + + $sorted = array(); + foreach ($groupedByPriority as $priorityResolvers) { + $sorted = array_merge($sorted, $priorityResolvers); + } + + parent::__construct($sorted); + } +} diff --git a/src/Mediator/Iterator/SourceResolverFilterIterator.php b/src/Mediator/Iterator/SourceResolverFilterIterator.php new file mode 100644 index 0000000..a0847a8 --- /dev/null +++ b/src/Mediator/Iterator/SourceResolverFilterIterator.php @@ -0,0 +1,24 @@ + + */ +class SourceResolverFilterIterator extends \FilterIterator +{ + /** + * {@inheritDoc} + */ + public function accept() + { + return $this->current() instanceof SourceResolver; + } +} diff --git a/src/Mediator/PathResolver.php b/src/Mediator/PathResolver.php new file mode 100644 index 0000000..07d1d78 --- /dev/null +++ b/src/Mediator/PathResolver.php @@ -0,0 +1,23 @@ + + */ +interface PathResolver extends Resolver +{ + /** + * Returns the path of the given media and options + * + * @param mixed $media + * @param array $options + * + * @return string + */ + function getPath($media, array $options); +} diff --git a/src/Mediator/Resolver.php b/src/Mediator/Resolver.php new file mode 100644 index 0000000..fe7e433 --- /dev/null +++ b/src/Mediator/Resolver.php @@ -0,0 +1,23 @@ + + */ +interface Resolver +{ + /** + * Indicates whether the resolver supports the given media and options + * + * @param mixed $media + * @param array $options + * + * @return boolean + */ + public function supports($media, array $options); +} diff --git a/src/Mediator/Resolver/CallbackPath.php b/src/Mediator/Resolver/CallbackPath.php new file mode 100644 index 0000000..914a338 --- /dev/null +++ b/src/Mediator/Resolver/CallbackPath.php @@ -0,0 +1,53 @@ + + */ +class CallbackPath implements PathResolver +{ + /** + * Constructor + * + * @param callable $callbackSupports + * @param callable $callbackGetPath + */ + public function __construct($callbackSupports, $callbackGetPath) + { + $this->callbackSupports = $callbackSupports; + $this->callbackGetPath = $callbackGetPath; + } + + /** + * {@inheritDoc} + */ + public function supports($media, array $options = array()) + { + return (boolean) call_user_func_array( + $this->callbackSupports, + array($media, $options) + ); + } + + /** + * {@inheritDoc} + */ + public function getPath($media, array $options = array()) + { + return (string) call_user_func_array( + $this->callbackGetPath, + array($media, $options) + ); + } +} diff --git a/src/Mediator/Resolver/CallbackSource.php b/src/Mediator/Resolver/CallbackSource.php new file mode 100644 index 0000000..6673397 --- /dev/null +++ b/src/Mediator/Resolver/CallbackSource.php @@ -0,0 +1,71 @@ + + */ +class CallbackSource implements SourceResolver +{ + private $callbackSupports; + private $callbackGetSource; + private $callbackGetSourceType; + + /** + * Constructor + * + * @param callable $callbackSupports + * @param callable $callbackGetSource + * @param callable $callbackGetSourceType + */ + public function __construct($callbackSupports, $callbackGetSource, $callbackGetSourceType) + { + $this->callbackSupports = $callbackSupports; + $this->callbackGetSource = $callbackGetSource; + $this->callbackGetSourceType = $callbackGetSourceType; + } + + /** + * {@inheritDoc} + */ + public function supports($media, array $options = array()) + { + return (boolean) call_user_func_array( + $this->callbackSupports, + array($media, $options) + ); + } + + /** + * {@inheritDoc} + */ + public function getSource($media, array $options = array()) + { + return (string) call_user_func_array( + $this->callbackGetSource, + array($media, $options) + ); + } + + /** + * {@inheritDoc} + */ + public function getSourceType($media, array $options = array()) + { + return (string) call_user_func_array( + $this->callbackGetSourceType, + array($media, $options) + ); + } +} diff --git a/src/Mediator/Resolver/StubPath.php b/src/Mediator/Resolver/StubPath.php new file mode 100644 index 0000000..d9d6a83 --- /dev/null +++ b/src/Mediator/Resolver/StubPath.php @@ -0,0 +1,43 @@ + + */ +class StubPath implements PathResolver +{ + private $path; + + /** + * Constructor + * + * @param string $path The path that will be returned anyway + */ + public function __construct($path) + { + $this->path = $path; + } + + /** + * {@inheritDoc} + */ + public function supports($media, array $options = array()) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getPath($media, array $options = array()) + { + return (string) $this->path; + } +} diff --git a/src/Mediator/Resolver/StubSource.php b/src/Mediator/Resolver/StubSource.php new file mode 100644 index 0000000..eeb1972 --- /dev/null +++ b/src/Mediator/Resolver/StubSource.php @@ -0,0 +1,53 @@ + + */ +class StubSource implements SourceResolver +{ + private $source; + + /** + * Constructor + * + * @param string $source The source that will be returned anyway + * @param string $sourceType The source type + */ + public function __construct($source, $sourceType) + { + $this->source = $source; + $this->sourceType = $sourceType; + } + + /** + * {@inheritDoc} + */ + public function supports($media, array $options = array()) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getSource($media, array $options = array()) + { + return (string) $this->source; + } + + /** + * {@inheritDoc} + */ + public function getSourceType($media, array $options = array()) + { + return (string) $this->sourceType; + } +} diff --git a/src/Mediator/SourceResolver.php b/src/Mediator/SourceResolver.php new file mode 100644 index 0000000..1b3f94c --- /dev/null +++ b/src/Mediator/SourceResolver.php @@ -0,0 +1,35 @@ + + */ +interface SourceResolver extends Resolver +{ + const TYPE_ABSOLUTE = 'absolute'; + const TYPE_RELATIVE = 'relative'; + + /** + * Returns the source for the given media and options + * + * @param mixed $media + * @param array $options + * + * @return string + */ + function getSource($media, array $options); + + /** + * Returns type of the source for the given media and options + * + * @param mixed $media + * @param array $options + * + * @return string One of the TYPE_* constants + */ + function getSourceType($media, array $options); +} diff --git a/tests/Mediator/ExposerTest.php b/tests/Mediator/ExposerTest.php new file mode 100644 index 0000000..04de1eb --- /dev/null +++ b/tests/Mediator/ExposerTest.php @@ -0,0 +1,118 @@ +addResolver($a = $this->getSourceResolverMock(), 10); + $exposer->addResolver($b = $this->getPathResolverMock(), -10); + $exposer->addResolver($c = $this->getSourceResolverMock(), 0); + $exposer->addResolver($d = $this->getPathResolverMock(), 0); + + $this->assertEquals( + array($a, $b, $c, $d), + iterator_to_array($exposer->getResolvers(), false), + 'Should return all the resolvers in the same order they were added.' + ); + } + + public function testGetSortedResolvers() + { + $exposer = new Exposer(); + $exposer->addResolver($a = $this->getSourceResolverMock(), 10); + $exposer->addResolver($b = $this->getPathResolverMock(), -10); + $exposer->addResolver($c = $this->getSourceResolverMock(), 0); + $exposer->addResolver($d = $this->getPathResolverMock(), 0); + + $this->assertEquals( + array($b, $c, $d, $a), + iterator_to_array($exposer->getSortedResolvers(), false), + 'Should return all the resolver sorted by priority (from the smallest to the largest).' + ); + } + + public function testGetSourceResolvers() + { + $a = $this->getSourceResolverMock(); + $b = $this->getPathResolverMock(); + $c = $this->getSourceResolverMock(); + + $exposer = new Exposer(); + $exposer->addResolver($a, 10); + $exposer->addResolver($b, -10); + $exposer->addResolver($c, 0); + + $this->assertEquals( + array($a, $c), + iterator_to_array($exposer->getSourceResolvers(), false), + 'Should return the sources in the order they were added.' + ); + } + + public function testGetSortedSourceResolvers() + { + $a = $this->getSourceResolverMock(); + $b = $this->getPathResolverMock(); + $c = $this->getSourceResolverMock(); + + $exposer = new Exposer(); + $exposer->addResolver($a, 10); + $exposer->addResolver($b, -10); + $exposer->addResolver($c, 0); + + $this->assertEquals( + array($c, $a), + iterator_to_array($exposer->getSortedSourceResolvers(), false), + 'Should return the source resolvers sorted by priority from the smallest to the largest.' + ); + } + + public function testGetPathResolvers() + { + $a = $this->getPathResolverMock(); + $b = $this->getSourceResolverMock(); + $c = $this->getPathResolverMock(); + + $exposer = new Exposer(); + $exposer->addResolver($a, 10); + $exposer->addResolver($b, -10); + $exposer->addResolver($c, 0); + + $this->assertEquals( + array($a, $c), + iterator_to_array($exposer->getPathResolvers(), false), + 'Should return the path resolvers in the same order they were added.' + ); + } + + public function testGetSortedPathResolvers() + { + $a = $this->getPathResolverMock(); + $b = $this->getSourceResolverMock(); + $c = $this->getPathResolverMock(); + + $exposer = new Exposer(); + $exposer->addResolver($a, 10); + $exposer->addResolver($b, -10); + $exposer->addResolver($c, 0); + + $this->assertEquals( + array($c, $a), + iterator_to_array($exposer->getSortedPathResolvers(), false), + 'Should return the path resolvers sorted by priority from the smallest to the largest).' + ); + } + + private function getSourceResolverMock() + { + return $this->getMock('Mediator\SourceResolver'); + } + + private function getPathResolverMock() + { + return $this->getMock('Mediator\PathResolver'); + } +} diff --git a/tests/Mediator/Resolver/CallbackPathTest.php b/tests/Mediator/Resolver/CallbackPathTest.php new file mode 100644 index 0000000..5d9c57a --- /dev/null +++ b/tests/Mediator/Resolver/CallbackPathTest.php @@ -0,0 +1,107 @@ +getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue(true)) + ; + + $resolver = new CallbackPath( + function() use($callback) { + return call_user_func_array( + array($callback, 'callback'), + func_get_args() + ); + }, + function() {} + ); + + $this->assertTrue($resolver->supports('TheMedia', array('foo' => 'bar'))); + } + + public function testSupportsWithACallbackArray() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue(true)) + ; + + $resolver = new CallbackPath( + array($callback, 'callback'), + function() {} + ); + + $this->assertTrue($resolver->supports('TheMedia', array('foo' => 'bar'))); + } + + public function testGetPathWithAClosure() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('ThePath')) + ; + + $resolver = new CallbackPath( + function() {}, + function() use($callback) { + return call_user_func_array( + array($callback, 'callback'), + func_get_args() + ); + } + ); + + $this->assertEquals('ThePath', $resolver->getPath('TheMedia', array('foo' => 'bar'))); + } + + public function testGetPathWithACallback() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('ThePath')) + ; + + $resolver = new CallbackPath( + function() {}, + array($callback, 'callback') + ); + + $this->assertEquals('ThePath', $resolver->getPath('TheMedia', array('foo' => 'bar'))); + } + + private function getClassCallbackMock() + { + return $this->getMock('Mediator\Resolver\ClassCallback'); + } +} diff --git a/tests/Mediator/Resolver/CallbackSourceTest.php b/tests/Mediator/Resolver/CallbackSourceTest.php new file mode 100644 index 0000000..7847d49 --- /dev/null +++ b/tests/Mediator/Resolver/CallbackSourceTest.php @@ -0,0 +1,160 @@ +getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue(true)) + ; + + $resolver = new CallbackSource( + function() use($callback) { + return call_user_func_array( + array($callback, 'callback'), + func_get_args() + ); + }, + function() {}, + function() {} + ); + + $this->assertTrue($resolver->supports('TheMedia', array('foo' => 'bar'))); + } + + public function testSupportsWithACallbackArray() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue(true)) + ; + + $resolver = new CallbackSource( + array($callback, 'callback'), + function() {}, + function() {} + ); + + $this->assertTrue($resolver->supports('TheMedia', array('foo' => 'bar'))); + } + + public function testGetSourceWithAClosure() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('TheSource')) + ; + + $resolver = new CallbackSource( + function() {}, + function() use($callback) { + return call_user_func_array( + array($callback, 'callback'), + func_get_args() + ); + }, + function() {} + ); + + $this->assertEquals('TheSource', $resolver->getSource('TheMedia', array('foo' => 'bar'))); + } + + public function testGetSourceWithACallback() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('TheSource')) + ; + + $resolver = new CallbackSource( + function() {}, + array($callback, 'callback'), + function() {} + ); + + $this->assertEquals('TheSource', $resolver->getSource('TheMedia', array('foo' => 'bar'))); + } + + public function testGetSourceTypeWithAClosure() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('TheSourceType')) + ; + + $resolver = new CallbackSource( + function() {}, + function() {}, + function() use($callback) { + return call_user_func_array( + array($callback, 'callback'), + func_get_args() + ); + } + ); + + $this->assertEquals('TheSourceType', $resolver->getSourceType('TheMedia', array('foo' => 'bar'))); + } + + public function testGetSourceTypeWithACallback() + { + $callback = $this->getClassCallbackMock(); + $callback + ->expects($this->once()) + ->method('callback') + ->with( + $this->equalTo('TheMedia'), + $this->equalTo(array('foo' => 'bar')) + ) + ->will($this->returnValue('TheSourceType')) + ; + + $resolver = new CallbackSource( + function() {}, + function() {}, + array($callback, 'callback') + ); + + $this->assertEquals('TheSourceType', $resolver->getSourceType('TheMedia', array('foo' => 'bar'))); + } + + private function getClassCallbackMock() + { + return $this->getMock('Mediator\Resolver\ClassCallback'); + } +} diff --git a/tests/Mediator/Resolver/ClassCallback.php b/tests/Mediator/Resolver/ClassCallback.php new file mode 100644 index 0000000..efcee98 --- /dev/null +++ b/tests/Mediator/Resolver/ClassCallback.php @@ -0,0 +1,7 @@ +string = $string; } + public function __toString() { return $this->string; } +} diff --git a/tests/Mediator/Resolver/StubPathTest.php b/tests/Mediator/Resolver/StubPathTest.php new file mode 100644 index 0000000..993df33 --- /dev/null +++ b/tests/Mediator/Resolver/StubPathTest.php @@ -0,0 +1,41 @@ +assertTrue($resolver->supports('TheMedia')); + } + + /** + * @dataProvider dataForGetPath + */ + public function testGetPath($path, $expected, $message) + { + $resolver = new StubPath($path); + + $this->assertEquals($expected, $resolver->getPath('TheMedia'), $message); + } + + public function dataForGetPath() + { + return array( + array( + '/the/path', + '/the/path', + 'Should simply return the path when it is a string.' + ), + array( + new ClassToString('/the/path'), + '/the/path', + 'Should return the string representation of an object using the __toString method.' + ) + ); + } +} diff --git a/tests/Mediator/Resolver/StubSourceTest.php b/tests/Mediator/Resolver/StubSourceTest.php new file mode 100644 index 0000000..d568c51 --- /dev/null +++ b/tests/Mediator/Resolver/StubSourceTest.php @@ -0,0 +1,69 @@ +assertTrue($resolver->supports('TheMedia')); + } + + /** + * @dataProvider dataForGetSource + */ + public function testGetSource($source, $expected, $message) + { + $resolver = new StubSource($source, SourceResolver::TYPE_RELATIVE); + + $this->assertEquals($expected, $resolver->getSource('TheMedia'), $message); + } + + public function dataForGetSource() + { + return array( + array( + '/the/source', + '/the/source', + 'Should simply return the source when it is a string.' + ), + array( + new ClassToString('/the/source'), + '/the/source', + 'Should return the string representation of an object using the __toString method.' + ) + ); + } + + /** + * @dataProvider dataForGetSourceType + */ + public function testGetSourceType($source, $sourceType, $expected) + { + $resolver = new StubSource($source, $sourceType); + + $this->assertEquals($expected, $resolver->getSourceType('TheMedia')); + } + + public function dataForGetSourceType() + { + return array( + array( + '/the/source', + SourceResolver::TYPE_RELATIVE, + SourceResolver::TYPE_RELATIVE + ), + array( + 'http://the.host/the/source', + SourceResolver::TYPE_ABSOLUTE, + SourceResolver::TYPE_ABSOLUTE + ), + ); + } +}