diff --git a/src/Connector/CachingConnector.php b/src/Connector/CachingConnector.php index f04b801..91155f5 100644 --- a/src/Connector/CachingConnector.php +++ b/src/Connector/CachingConnector.php @@ -10,7 +10,7 @@ /** * Wraps a connector to cache fetched data using PSR-6-compliant objects. */ -class CachingConnector implements Connector +class CachingConnector implements Connector, ConnectorWrapper { /** * @var Connector @@ -37,6 +37,14 @@ public function __construct( $this->cacheKeyGenerator = $cacheKeyGenerator ?: new JsonCacheKeyGenerator; } + public function __clone() + { + $this->connector = clone $this->connector; + + /* It doesn't make sense to clone the cache because we want cache state to be shared between imports. + We're also not cloning the CacheKeyGenerator because they're expected to be stateless algorithms. */ + } + /** * @param ConnectionContext $context * @param string $source @@ -87,4 +95,9 @@ private function validateCacheKey($key) )); } } + + public function getWrappedConnector() + { + return $this->connector; + } } diff --git a/src/Connector/ConnectorWrapper.php b/src/Connector/ConnectorWrapper.php new file mode 100644 index 0000000..281b136 --- /dev/null +++ b/src/Connector/ConnectorWrapper.php @@ -0,0 +1,15 @@ +connector = $connector; + $this->connector = clone $connector; $this->context = $context; } @@ -34,13 +34,29 @@ public function fetch($source) /** * Gets the wrapped connector. Useful for resources to reconfigure connector options during this import. * - * @return Connector + * @return Connector Wrapped connector. */ public function getWrappedConnector() { return $this->connector; } + /** + * Finds the base connector by traversing the stack of wrapped connectors. + * + * @return Connector Base connector. + */ + public function findBaseConnector() + { + $connector = $this->connector; + + while ($connector instanceof ConnectorWrapper) { + $connector = $connector->getWrappedConnector(); + } + + return $connector; + } + /** * Sets the exception handler to be called when a recoverable exception is thrown by Connector::fetch(). * diff --git a/src/Porter.php b/src/Porter.php index 5d0f1e6..5a88201 100644 --- a/src/Porter.php +++ b/src/Porter.php @@ -119,7 +119,7 @@ private function fetch(ProviderResource $resource, $providerName, ConnectionCont ); } - $records = $resource->fetch(new ImportConnector(clone $connector, $context)); + $records = $resource->fetch(new ImportConnector($connector, $context)); if (!$records instanceof \Iterator) { throw new ImportException(get_class($resource) . '::fetch() did not return an Iterator.'); diff --git a/src/Specification/ImportSpecification.php b/src/Specification/ImportSpecification.php index 2259686..26c76b3 100644 --- a/src/Specification/ImportSpecification.php +++ b/src/Specification/ImportSpecification.php @@ -80,7 +80,7 @@ final public function getResource() } /** - * Gets the provider name. + * Gets the provider service name. * * @return string Provider name. */ @@ -90,7 +90,7 @@ final public function getProviderName() } /** - * Sets the provider name. + * Sets the provider service name. * * @param string $providerName Provider name. * @@ -211,7 +211,7 @@ final public function disableCache() } /** - * Gets the maximum number of fetch attempts per import. + * Gets the maximum number of fetch attempts per connection. * * @return int Maximum fetch attempts. */ @@ -221,7 +221,7 @@ final public function getMaxFetchAttempts() } /** - * Sets the maximum number of fetch attempts per import. + * Sets the maximum number of fetch attempts per connection before failure is considered permanent. * * @param int $attempts Maximum fetch attempts. * diff --git a/test/Integration/Porter/Connector/CachingConnectorTest.php b/test/Integration/Porter/Connector/CachingConnectorTest.php index 2d76a92..e2db0d4 100644 --- a/test/Integration/Porter/Connector/CachingConnectorTest.php +++ b/test/Integration/Porter/Connector/CachingConnectorTest.php @@ -15,6 +15,9 @@ use ScriptFUSIONTest\FixtureFactory; use ScriptFUSIONTest\Stubs\TestOptions; +/** + * @see CachingConnector + */ final class CachingConnectorTest extends \PHPUnit_Framework_TestCase { use MockeryPHPUnitIntegration; @@ -193,6 +196,24 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonPSR6CompliantCacheKe $connector->fetch($this->context, 'baz'); } + /** + * Tests that getting the wrapped connector returns exactly the same connector as constructed with. + */ + public function testGetWrappedConnector() + { + self::assertSame($this->wrappedConnector, $this->connector->getWrappedConnector()); + } + + /** + * Tests that cloning the caching connector also clones the wrapped connector. + */ + public function testClone() + { + $clone = clone $this->connector; + + self::assertNotSame($this->wrappedConnector, $clone->getWrappedConnector()); + } + private function createConnector(MockInterface $cache = null, MockInterface $cacheKeyGenerator = null) { return new CachingConnector($this->wrappedConnector, $cache, $cacheKeyGenerator); diff --git a/test/Unit/Porter/Connector/ImportConnectorTest.php b/test/Unit/Porter/Connector/ImportConnectorTest.php index b885367..88fa0c3 100644 --- a/test/Unit/Porter/Connector/ImportConnectorTest.php +++ b/test/Unit/Porter/Connector/ImportConnectorTest.php @@ -4,6 +4,7 @@ use ScriptFUSION\Porter\Cache\CacheUnavailableException; use ScriptFUSION\Porter\Connector\CachingConnector; use ScriptFUSION\Porter\Connector\Connector; +use ScriptFUSION\Porter\Connector\ConnectorWrapper; use ScriptFUSION\Porter\Connector\FetchExceptionHandler\FetchExceptionHandler; use ScriptFUSION\Porter\Connector\ImportConnector; use ScriptFUSIONTest\FixtureFactory; @@ -55,7 +56,7 @@ public function testFetchCacheDisabled() public function testFetchCacheEnabled() { $connector = new ImportConnector( - \Mockery::mock(CachingConnector::class) + \Mockery::mock(CachingConnector::class, [\Mockery::mock(Connector::class)]) ->shouldReceive('fetch') ->andReturn($output = 'foo') ->getMock(), @@ -79,7 +80,7 @@ public function testFetchCacheEnabledButNotAvailable() } /** - * Tests that getting the wrapped connector returns exactly the same connector as constructed with. + * Tests that getting the wrapped connector returns a clone of the original connector passed to the constructor. */ public function testGetWrappedConnector() { @@ -88,7 +89,8 @@ public function testGetWrappedConnector() FixtureFactory::buildConnectionContext() ); - self::assertSame($wrappedConnector, $connector->getWrappedConnector()); + self::assertNotSame($wrappedConnector, $connector->getWrappedConnector()); + self::assertSame(get_class($wrappedConnector), get_class($connector->getWrappedConnector())); } /** @@ -106,4 +108,20 @@ public function testSetExceptionHandlerTwice() $this->setExpectedException(\LogicException::class); $connector->setExceptionHandler($handler); } + + /** + * Tests that finding the base connector returns the connector at the bottom of a ConnectorWrapper stack. + */ + public function testFindBaseConnector() + { + $connector = new ImportConnector( + \Mockery::mock(Connector::class, ConnectorWrapper::class) + ->shouldReceive('getWrappedConnector') + ->andReturn($baseConnector = \Mockery::mock(Connector::class)) + ->getMock(), + FixtureFactory::buildConnectionContext() + ); + + self::assertSame($baseConnector, $connector->findBaseConnector()); + } }