Skip to content
Open
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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"require-dev": {
"phpstan/phpstan": "^1.9",
"friendsofphp/php-cs-fixer": "^3.14",
"mockery/mockery": "^1.5",
"phpstan/phpstan-mockery": "^1.1",
"phpunit/phpunit": "^10.0"
"phpunit/phpunit": "^10.0",
"mockery/mockery": "^1.6"
},
"autoload": {
"psr-4": {
Expand Down
47 changes: 29 additions & 18 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 14 additions & 50 deletions src/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace GeekCell\Facade;

use Mockery;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
Expand All @@ -24,7 +23,7 @@ abstract class Facade
/**
* @var array<object>
*/
protected static array $resolvedInstances = [];
protected static array $swappedInstances = [];

/**
* Return the underlying instance behind the facade.
Expand All @@ -39,8 +38,8 @@ abstract class Facade
public static function getFacadeRoot(): object
{
$accessor = static::getFacadeAccessor();
if (isset(static::$resolvedInstances[$accessor])) {
return static::$resolvedInstances[$accessor];
if (isset(static::$swappedInstances[$accessor])) {
return static::$swappedInstances[$accessor];
}

if (!isset(static::$container)) {
Expand All @@ -50,12 +49,10 @@ public static function getFacadeRoot(): object
$instance = static::$container->get($accessor);
if (!is_object($instance)) {
throw new \UnexpectedValueException(
sprintf('The entry for "%s" must return an object.', $accessor),
sprintf('The entry for "%s" must return an object. Got: %s', $accessor, get_debug_type($instance)),
);
}

self::$resolvedInstances[$accessor] = $instance;

return $instance;
}

Expand Down Expand Up @@ -88,61 +85,26 @@ public static function __callStatic(string $method, array $args): mixed
*/
public static function clear(): void
{
static::$resolvedInstances = [];
static::$swappedInstances = [];
static::$container = null;
}

/**
* Return the class name for the underlying instance behind the facade.
*
* @return string
*/
public static function getMockableClass(): string
{
$instance = static::getFacadeRoot();
return get_class($instance);
}

/**
* Return a Mockery mock instance of the underlying instance behind the facade.
* @template T of object
*
* @return Mockery\MockInterface
*
* @throws NotFoundExceptionInterface
* @throws ContainerExceptionInterface
* @throws \LogicException
* @throws \UnexpectedValueException
* @param T $newInstance
* @return T
*/
public static function createMock(): Mockery\MockInterface
{
$classToMock = static::getMockableClass();
return Mockery::mock($classToMock);
}

/**
* Return a Mockery mock instance, but also swap the underlying instance behind the facade
* so consecutive calls to the facade will use the mock.
*
* This behavior can be reset by calling Facade::clear().
*
* @return Mockery\MockInterface
*
* @throws NotFoundExceptionInterface
* @throws ContainerExceptionInterface
* @throws \LogicException
* @throws \UnexpectedValueException
*/
public static function swapMock(): Mockery\MockInterface
public static function swap($newInstance)
{
$accessor = static::getFacadeAccessor();
$mock = static::createMock();
self::$resolvedInstances[$accessor] = $mock;
static::$swappedInstances[$accessor] = $newInstance;

return $mock;
return $newInstance;
}

/**
* Set the container instance.
* Set the container instance. Reset any cache instances in the process.
*
* @codeCoverageIgnore
*
Expand All @@ -151,6 +113,8 @@ public static function swapMock(): Mockery\MockInterface
*/
public static function setContainer(ContainerInterface $container): void
{
static::clear();

static::$container = $container;
}

Expand Down
58 changes: 28 additions & 30 deletions tests/Unit/FacadeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,48 +117,46 @@ public function testCallStaticWithMissingMethod(): void
ServiceFacade::invalid(); // @phpstan-ignore-line
}

public function testGetMockableClass(): void
public function testGetFacadeRootUsesSwappedValueWithoutContainer(): void
{
// Given
ServiceFacade::setContainer($this->container);
$service = new Service();
ServiceFacade::swap($service);

// When
$result = ServiceFacade::getMockableClass();
$root = ServiceFacade::getFacadeRoot();

// Then
$this->assertSame(Service::class, $result);
$this->assertSame($root, $service);
}

public function testCreateMock(): void
public function testSwap(): void
{
// Given
ServiceFacade::setContainer($this->container);

// When
$mock = ServiceFacade::createMock();
$root = ServiceFacade::getFacadeRoot();
$this->assertSame($this->service, $root);

// Then
$this->assertInstanceOf(Mockery\MockInterface::class, $mock);
$otherService = new Service();
ServiceFacade::swap($otherService);

$otherRoot = ServiceFacade::getFacadeRoot();
$this->assertSame($otherRoot, $otherService);
$this->assertNotSame($root, $otherRoot);
}

public function testSwapMock(): void
public function testSettingContainerClearsFacadeCache(): void
{
// Given
ServiceFacade::setContainer($this->container);

// When
$mock = ServiceFacade::swapMock();
$mock->shouldReceive('greeting')->andReturn('Hello Mock!');

// Then
$this->assertInstanceOf(Mockery\MockInterface::class, $mock);

$result = ServiceFacade::greeting('World'); // @phpstan-ignore-line
$this->assertEquals('Hello Mock!', $result);

ServiceFacade::clear();
ServiceFacade::setContainer($this->container);
$result = ServiceFacade::greeting('World'); // @phpstan-ignore-line
$this->assertEquals('Hello World!', $result);
$firstService = new Service();
$firstContainer = new Container($firstService);
ServiceFacade::setContainer($firstContainer);
$firstRoot = ServiceFacade::getFacadeRoot();
$this->assertSame($firstRoot, $firstService);

$secondService = new Service();
$secondContainer = new Container($secondService);
ServiceFacade::setContainer($secondContainer);
$secondRoot = ServiceFacade::getFacadeRoot();
$this->assertSame($secondRoot, $secondService);

$this->assertNotSame($firstRoot, $secondRoot);
}
}