Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ php:
- 5.6
- 7.0
- 7.1
- 7.2

env:
matrix:
Expand All @@ -21,7 +22,7 @@ matrix:

cache:
directories:
- .composer/cache
- vendor

install:
- alias composer=composer\ -n && composer selfupdate
Expand Down
2 changes: 1 addition & 1 deletion src/Collection/RecordCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ abstract class RecordCollection implements \Iterator

private $previousCollection;

public function __construct(\Iterator $records, RecordCollection $previousCollection = null)
public function __construct(\Iterator $records, self $previousCollection = null)
{
$this->records = $records;
$this->previousCollection = $previousCollection;
Expand Down
14 changes: 6 additions & 8 deletions src/Connector/CachingConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use ScriptFUSION\Porter\Cache\InvalidCacheKeyException;
use ScriptFUSION\Porter\Cache\JsonCacheKeyGenerator;
use ScriptFUSION\Porter\Cache\MemoryCache;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;

/**
* Wraps a connector to cache fetched data using PSR-6-compliant objects.
Expand Down Expand Up @@ -39,28 +38,27 @@ public function __construct(
}

/**
* @param ConnectionContext $context
* @param string $source
* @param EncapsulatedOptions|null $options
*
* @return mixed
*
* @throws InvalidCacheKeyException
*/
public function fetch(ConnectionContext $context, $source, EncapsulatedOptions $options = null)
public function fetch(ConnectionContext $context, $source)
{
if ($context->mustCache()) {
$optionsCopy = $options ? $options->copy() : [];
$options = $this->connector instanceof ConnectorOptions ? $this->connector->getOptions()->copy() : [];
ksort($options);

ksort($optionsCopy);

$this->validateCacheKey($key = $this->cacheKeyGenerator->generateCacheKey($source, $optionsCopy));
$this->validateCacheKey($key = $this->cacheKeyGenerator->generateCacheKey($source, $options));

if ($this->cache->hasItem($key)) {
return $this->cache->getItem($key)->get();
}
}

$data = $this->connector->fetch($context, $source, $options);
$data = $this->connector->fetch($context, $source);

isset($key) && $this->cache->save($this->cache->getItem($key)->set($data));

Expand Down
5 changes: 1 addition & 4 deletions src/Connector/Connector.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php
namespace ScriptFUSION\Porter\Connector;

use ScriptFUSION\Porter\Options\EncapsulatedOptions;

/**
* Provides a method for fetching data from a remote source.
*/
Expand All @@ -13,9 +11,8 @@ interface Connector
*
* @param ConnectionContext $context Runtime connection settings and methods.
* @param string $source Source.
* @param EncapsulatedOptions|null $options Optional. Options.
*
* @return mixed Data.
*/
public function fetch(ConnectionContext $context, $source, EncapsulatedOptions $options = null);
public function fetch(ConnectionContext $context, $source);
}
12 changes: 12 additions & 0 deletions src/Connector/ConnectorOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
namespace ScriptFUSION\Porter\Connector;

use ScriptFUSION\Porter\Options\EncapsulatedOptions;

interface ConnectorOptions
{
/**
* @return EncapsulatedOptions
*/
public function getOptions();
}
15 changes: 12 additions & 3 deletions src/Connector/ImportConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
namespace ScriptFUSION\Porter\Connector;

use ScriptFUSION\Porter\Cache\CacheUnavailableException;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;

/**
* Connector whose lifecycle is synchronised with an import operation. Ensures correct ConnectionContext is delivered
Expand All @@ -26,8 +25,18 @@ public function __construct(Connector $connector, ConnectionContext $context)
$this->context = $context;
}

public function fetch($source, EncapsulatedOptions $options = null)
public function fetch($source)
{
return $this->connector->fetch($this->context, $source, $options);
return $this->connector->fetch($this->context, $source);
}

/**
* Gets the wrapped connector. Useful for resources to reconfigure connector options during this import.
*
* @return Connector
*/
public function getWrappedConnector()
{
return $this->connector;
}
}
4 changes: 1 addition & 3 deletions src/Connector/NullConnector.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<?php
namespace ScriptFUSION\Porter\Connector;

use ScriptFUSION\Porter\Options\EncapsulatedOptions;

class NullConnector implements Connector
{
public function fetch(ConnectionContext $context, $source, EncapsulatedOptions $options = null)
public function fetch(ConnectionContext $context, $source)
{
// Intentionally empty.
}
Expand Down
17 changes: 12 additions & 5 deletions src/Porter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
use ScriptFUSION\Porter\Collection\RecordCollection;
use ScriptFUSION\Porter\Connector\ConnectionContext;
use ScriptFUSION\Porter\Connector\ConnectionContextFactory;
use ScriptFUSION\Porter\Connector\ConnectorOptions;
use ScriptFUSION\Porter\Connector\ImportConnector;
use ScriptFUSION\Porter\Provider\ForeignResourceException;
use ScriptFUSION\Porter\Provider\ObjectNotCreatedException;
use ScriptFUSION\Porter\Provider\Provider;
use ScriptFUSION\Porter\Provider\ProviderFactory;
use ScriptFUSION\Porter\Provider\ProviderOptions;
use ScriptFUSION\Porter\Provider\Resource\ProviderResource;
use ScriptFUSION\Porter\Specification\ImportSpecification;
use ScriptFUSION\Porter\Transform\Transformer;
Expand Down Expand Up @@ -109,10 +109,17 @@ private function fetch(ProviderResource $resource, $providerName, ConnectionCont
));
}

$records = $resource->fetch(
new ImportConnector($provider->getConnector(), $context),
$provider instanceof ProviderOptions ? clone $provider->getOptions() : null
);
$connector = $provider->getConnector();

/* __clone method cannot be specified in interface due to Mockery limitation.
See https://github.com/mockery/mockery/issues/669 */
if ($connector instanceof ConnectorOptions && !method_exists($connector, '__clone')) {
throw new \LogicException(
'Connector with options must implement __clone() method to deep clone options.'
);
}

$records = $resource->fetch(new ImportConnector(clone $connector, $context));

if (!$records instanceof \Iterator) {
throw new ImportException(get_class($resource) . '::fetch() did not return an Iterator.');
Expand Down
17 changes: 0 additions & 17 deletions src/Provider/ProviderOptions.php

This file was deleted.

4 changes: 1 addition & 3 deletions src/Provider/Resource/ProviderResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
namespace ScriptFUSION\Porter\Provider\Resource;

use ScriptFUSION\Porter\Connector\ImportConnector;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;

/**
* Defines methods for fetching data from a specific provider resource.
Expand All @@ -20,9 +19,8 @@ public function getProviderClassName();
* Fetches data from the provider using the the specified connector and presents its data as an enumerable series.
*
* @param ImportConnector $connector Connector.
* @param EncapsulatedOptions $options Optional. Options.
*
* @return \Iterator Enumerable data series.
*/
public function fetch(ImportConnector $connector, EncapsulatedOptions $options = null);
public function fetch(ImportConnector $connector);
}
2 changes: 1 addition & 1 deletion src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static function toArray($mixed)
}

if (is_array($mixed)) {
return array_map('self::' . __FUNCTION__, $mixed);
return array_map(__METHOD__, $mixed);
}

return $mixed;
Expand Down
56 changes: 35 additions & 21 deletions test/Integration/Porter/Connector/CachingConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use ScriptFUSION\Porter\Connector\CachingConnector;
use ScriptFUSION\Porter\Connector\ConnectionContext;
use ScriptFUSION\Porter\Connector\Connector;
use ScriptFUSION\Porter\Connector\ConnectorOptions;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;
use ScriptFUSIONTest\FixtureFactory;
use ScriptFUSIONTest\Stubs\TestOptions;
Expand All @@ -24,7 +25,7 @@ final class CachingConnectorTest extends \PHPUnit_Framework_TestCase
private $connector;

/**
* @var Connector|MockInterface
* @var Connector|ConnectorOptions|MockInterface
*/
private $wrappedConnector;

Expand All @@ -40,25 +41,30 @@ final class CachingConnectorTest extends \PHPUnit_Framework_TestCase

protected function setUp()
{
$this->options = new TestOptions;

$this->connector = new CachingConnector(
$this->wrappedConnector = \Mockery::mock(Connector::class)
$this->wrappedConnector = \Mockery::mock(Connector::class, ConnectorOptions::class)
->shouldReceive('fetch')
->andReturn('foo', 'bar')
->andReturn('foo', 'bar')
->shouldReceive('getOptions')
->andReturn($this->options)
->byDefault()
->shouldReceive('clone')
->andReturn(null)
->getMock()
);

$this->context = FixtureFactory::buildConnectionContext(true);

$this->options = new TestOptions;
}

/**
* Tests that when cache is enabled, the same result is returned because the wrapped connector is bypassed.
*/
public function testCacheEnabled()
{
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', $this->options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', $this->options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));
}

/**
Expand All @@ -69,37 +75,45 @@ public function testCacheDisabled()
// The default connection context has caching disabled.
$context = FixtureFactory::buildConnectionContext();

self::assertSame('foo', $this->connector->fetch($context, 'baz', $this->options));
self::assertSame('bar', $this->connector->fetch($context, 'baz', $this->options));
self::assertSame('foo', $this->connector->fetch($context, 'baz'));
self::assertSame('bar', $this->connector->fetch($context, 'baz'));
}

/**
* Tests that when sources are the same but options are different, the cache is not reused.
*/
public function testCacheBypassedForDifferentOptions()
{
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', $this->options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));

$this->options->setFoo('bar');
self::assertSame('bar', $this->connector->fetch($this->context, 'baz', $this->options));
self::assertSame('bar', $this->connector->fetch($this->context, 'baz'));
}

/**
* Tests that when the same options are specified by two different object instances, the cache is reused.
*/
public function testCacheUsedForDifferentOptionsInstance()
{
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', $this->options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', clone $this->options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));

$this->wrappedConnector->shouldReceive('getOptions')->andReturn($options = clone $this->options);
self::assertNotSame($this->options, $options);
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));

// Ensure new options have really taken effect by changing option. Cache should no longer be used.
$options->setFoo('bar');
self::assertSame('bar', $this->connector->fetch($this->context, 'baz'));
}

public function testNullAndEmptyOptionsAreEquivalent()
{
/** @var EncapsulatedOptions $options */
$options = \Mockery::mock(EncapsulatedOptions::class)->shouldReceive('copy')->andReturn([])->getMock();
$this->wrappedConnector->shouldReceive('getOptions')->andReturn($options);
self::assertEmpty($this->wrappedConnector->getOptions()->copy());

self::assertEmpty($options->copy());
self::assertSame('foo', $this->connector->fetch($this->context, 'baz', $options));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));
self::assertSame('foo', $this->connector->fetch($this->context, 'baz'));
}

Expand All @@ -125,7 +139,7 @@ function ($key) use ($reservedCharacters) {
->shouldReceive('set')->andReturn(\Mockery::mock(CacheItemInterface::class))
;

$connector->fetch($this->context, $reservedCharacters, (new TestOptions)->setFoo($reservedCharacters));
$connector->fetch($this->context, $reservedCharacters);
}

/**
Expand All @@ -143,9 +157,9 @@ public function testCacheKeyGenerator()
->getMock()
);

self::assertSame('foo', $connector->fetch($this->context, $source, $this->options));
self::assertSame('foo', $connector->fetch($this->context, $source, $this->options));
self::assertSame('bar', $connector->fetch($this->context, $source, $this->options));
self::assertSame('foo', $connector->fetch($this->context, $source));
self::assertSame('foo', $connector->fetch($this->context, $source));
self::assertSame('bar', $connector->fetch($this->context, $source));
}

/**
Expand All @@ -162,7 +176,7 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonStringCacheKey()
);

$this->setExpectedException(InvalidCacheKeyException::class, 'Cache key must be a string.');
$connector->fetch($this->context, 'baz', $this->options);
$connector->fetch($this->context, 'baz');
}

public function testFetchThrowsInvalidCacheKeyExceptionOnNonPSR6CompliantCacheKey()
Expand All @@ -176,7 +190,7 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonPSR6CompliantCacheKe
);

$this->setExpectedException(InvalidCacheKeyException::class, 'contains one or more reserved characters');
$connector->fetch($this->context, 'baz', $this->options);
$connector->fetch($this->context, 'baz');
}

private function createConnector(MockInterface $cache = null, MockInterface $cacheKeyGenerator = null)
Expand Down
Loading