Skip to content

Commit

Permalink
Merge pull request #15 from timdev/topic/3-psalm-integration
Browse files Browse the repository at this point in the history
Psalm Integration
  • Loading branch information
Ocramius authored Sep 14, 2022
2 parents db8f6f6 + 4100b64 commit 5133c59
Show file tree
Hide file tree
Showing 10 changed files with 2,904 additions and 884 deletions.
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
},
"require-dev": {
"laminas/laminas-coding-standard": "~2.4.0",
"phpunit/phpunit": "^9.5.24"
"phpunit/phpunit": "^9.5.24",
"psalm/plugin-phpunit": "^0.16.1",
"vimeo/psalm": "^4.9"
},
"autoload": {
"psr-4": {
Expand All @@ -61,7 +63,8 @@
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"static-analysis": "psalm --shepherd --stats"
},
"conflict": {
"zendframework/zend-expressive-flash": "*"
Expand Down
3,666 changes: 2,828 additions & 838 deletions composer.lock

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src"/>
<directory name="test"/>
<ignoreFiles>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>

<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>
3 changes: 2 additions & 1 deletion src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public function getDependencies(): array
return [
// Legacy Zend Framework aliases
'aliases' => [
\Zend\Expressive\Flash\FlashMessageMiddleware::class => FlashMessageMiddleware::class,
// phpcs:ignore
'Zend\Expressive\Flash\FlashMessageMiddleware' => FlashMessageMiddleware::class,
],
'invokables' => [
FlashMessageMiddleware::class => FlashMessageMiddleware::class,
Expand Down
14 changes: 5 additions & 9 deletions src/FlashMessageMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

use function class_exists;
use function class_implements;
use function in_array;
use function is_callable;

class FlashMessageMiddleware implements MiddlewareInterface
{
Expand All @@ -22,7 +20,7 @@ class FlashMessageMiddleware implements MiddlewareInterface
/** @var string */
private $attributeKey;

/** @var callable */
/** @psalm-var callable(SessionInterface, string): FlashMessagesInterface */
private $flashMessageFactory;

/** @var string */
Expand All @@ -33,14 +31,12 @@ public function __construct(
string $sessionKey = FlashMessagesInterface::FLASH_NEXT,
string $attributeKey = self::FLASH_ATTRIBUTE
) {
if (
! class_exists($flashMessagesClass)
|| ! in_array(FlashMessagesInterface::class, class_implements($flashMessagesClass), true)
) {
$factory = [$flashMessagesClass, 'createFromSession'];
if (! is_callable($factory)) {
throw Exception\InvalidFlashMessagesImplementationException::forClass($flashMessagesClass);
}

$this->flashMessageFactory = [$flashMessagesClass, 'createFromSession'];
$this->flashMessageFactory = $factory;
$this->sessionKey = $sessionKey;
$this->attributeKey = $attributeKey;
}
Expand Down
32 changes: 25 additions & 7 deletions src/FlashMessages.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

use Mezzio\Session\SessionInterface;

use function is_array;

/**
* Create, retrieve, and manipulate flash messages.
*
Expand All @@ -26,10 +24,12 @@
*
* In order to keep messages made available to the current request for another
* hop, use the `prolongFlash()` method.
*
* @psalm-type StoredMessages = array<string,array{value:mixed,hops:int}>
*/
class FlashMessages implements FlashMessagesInterface
{
/** @var array */
/** @var array<string,mixed> */
private $currentMessages = [];

/** @var SessionInterface */
Expand Down Expand Up @@ -71,7 +71,7 @@ public function flash(string $key, $value, int $hops = 1): void
throw Exception\InvalidHopsValueException::valueTooLow($key, $hops);
}

$messages = $this->session->get($this->sessionKey, []);
$messages = $this->getStoredMessages();
$messages[$key] = [
'value' => $value,
'hops' => $hops,
Expand Down Expand Up @@ -145,7 +145,9 @@ public function clearFlash(): void
*/
public function prolongFlash(): void
{
$messages = $this->session->get($this->sessionKey, []);
$messages = $this->getStoredMessages();

/** @var mixed $value */
foreach ($this->currentMessages as $key => $value) {
if (isset($messages[$key])) {
continue;
Expand All @@ -161,11 +163,17 @@ public function prepareMessages(SessionInterface $session, string $sessionKey):
return;
}

$sessionMessages = $session->get($sessionKey);
$sessionMessages = ! is_array($sessionMessages) ? [] : $sessionMessages;
$sessionMessages = $this->getStoredMessages($sessionKey);

/** @var array<string,mixed> $currentMessages */
$currentMessages = [];
foreach ($sessionMessages as $key => $data) {
/**
* The public API of flash() and flashNow() explicitly allow calling
* code to pass `$value`s of any type, making this unavoidable.
*
* @psalm-suppress MixedAssignment
*/
$currentMessages[$key] = $data['value'];

if ($data['hops'] === 1) {
Expand All @@ -183,4 +191,14 @@ public function prepareMessages(SessionInterface $session, string $sessionKey):

$this->currentMessages = $currentMessages;
}

/**
* @return StoredMessages
*/
private function getStoredMessages(?string $sessionKey = null): array
{
/** @var StoredMessages|null $messages */
$messages = $this->session->get($sessionKey ?? $this->sessionKey, []);
return $messages ?? [];
}
}
11 changes: 1 addition & 10 deletions test/ConfigProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,9 @@ public function setUp(): void
$this->provider = new ConfigProvider();
}

public function testInvocationReturnsArray(): array
public function testReturnedArrayContainsDependencies(): void
{
$config = ($this->provider)();
$this->assertIsArray($config);
return $config;
}

/**
* @depends testInvocationReturnsArray
*/
public function testReturnedArrayContainsDependencies(array $config)
{
$this->assertArrayHasKey('dependencies', $config);
$this->assertIsArray($config['dependencies']);
}
Expand Down
10 changes: 6 additions & 4 deletions test/FlashMessageMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@

class FlashMessageMiddlewareTest extends TestCase
{
public function testConstructorRaisesExceptionIfFlashMessagesClassIsNotAClass()
public function testConstructorRaisesExceptionIfFlashMessagesClassIsNotAClass(): void
{
$this->expectException(Exception\InvalidFlashMessagesImplementationException::class);
$this->expectExceptionMessage('not-a-class');
new FlashMessageMiddleware('not-a-class');
}

public function testConstructorRaisesExceptionIfFlashMessagesClassDoesNotImplementCorrectInterface()
public function testConstructorRaisesExceptionIfFlashMessagesClassDoesNotImplementCorrectInterface(): void
{
$this->expectException(Exception\InvalidFlashMessagesImplementationException::class);
$this->expectExceptionMessage('stdClass');
new FlashMessageMiddleware(stdClass::class);
}

public function testProcessRaisesExceptionIfRequestSessionAttributeDoesNotReturnSessionInterface()
public function testProcessRaisesExceptionIfRequestSessionAttributeDoesNotReturnSessionInterface(): void
{
$request = $this->createMock(ServerRequestInterface::class);
$request
Expand Down Expand Up @@ -61,8 +61,10 @@ public function testProcessRaisesExceptionIfRequestSessionAttributeDoesNotReturn
$middleware->process($request, $handler);
}

public function testProcessUsesConfiguredClassAndSessionKeyAndAttributeKeyToCreateFlashMessagesAndPassToHandler()
// @codingStandardsIgnoreStart
public function testProcessUsesConfiguredClassAndSessionKeyAndAttributeKeyToCreateFlashMessagesAndPassToHandler(): void
{
// @codingStandardsIgnoreEnd
$session = $this->createMock(SessionInterface::class);

$request = $this->createMock(ServerRequestInterface::class);
Expand Down
24 changes: 12 additions & 12 deletions test/FlashMessagesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

class FlashMessagesTest extends TestCase
{
/** @var SessionInterface|MockObject */
/** @var SessionInterface&MockObject */
private $session;

public function setUp(): void
{
$this->session = $this->createMock(SessionInterface::class);
}

public function testCreationAggregatesNothingIfNoMessagesExistUnderSpecifiedSessionKey()
public function testCreationAggregatesNothingIfNoMessagesExistUnderSpecifiedSessionKey(): void
{
$this->session
->expects($this->once())
Expand All @@ -38,7 +38,7 @@ public function testCreationAggregatesNothingIfNoMessagesExistUnderSpecifiedSess
$this->assertSame([], $flash->getFlashes());
}

public function testCreationAggregatesItemsMarkedNextAndRemovesThemFromSession()
public function testCreationAggregatesItemsMarkedNextAndRemovesThemFromSession(): void
{
$messages = [
'test' => [
Expand Down Expand Up @@ -74,7 +74,7 @@ public function testCreationAggregatesItemsMarkedNextAndRemovesThemFromSession()
$this->assertSame(['test' => 'value1', 'test-2' => 'value2'], $flash->getFlashes());
}

public function testCreationAggregatesPersistsItemsWithMultipleHopsInSessionWithDecrementedHops()
public function testCreationAggregatesPersistsItemsWithMultipleHopsInSessionWithDecrementedHops(): void
{
$messages = [
'test' => [
Expand Down Expand Up @@ -116,7 +116,7 @@ public function testCreationAggregatesPersistsItemsWithMultipleHopsInSessionWith
$this->assertSame(['test' => 'value1', 'test-2' => 'value2'], $flash->getFlashes());
}

public function testFlashingAValueMakesItAvailableInNextSessionButNotFlashMessages()
public function testFlashingAValueMakesItAvailableInNextSessionButNotFlashMessages(): void
{
$this->session
->expects($this->once())
Expand Down Expand Up @@ -148,7 +148,7 @@ public function testFlashingAValueMakesItAvailableInNextSessionButNotFlashMessag
$this->assertSame([], $flash->getFlashes());
}

public function testFlashNowMakesValueAvailableBothInNextSessionAndCurrentFlashMessages()
public function testFlashNowMakesValueAvailableBothInNextSessionAndCurrentFlashMessages(): void
{
$this->session
->expects($this->once())
Expand Down Expand Up @@ -180,7 +180,7 @@ public function testFlashNowMakesValueAvailableBothInNextSessionAndCurrentFlashM
$this->assertSame(['test' => 'value'], $flash->getFlashes());
}

public function testProlongFlashAddsCurrentMessagesToNextSession()
public function testProlongFlashAddsCurrentMessagesToNextSession(): void
{
$messages = [
'test' => [
Expand Down Expand Up @@ -243,7 +243,7 @@ public function testProlongFlashAddsCurrentMessagesToNextSession()
$flash->prolongFlash();
}

public function testProlongFlashDoesNotReFlashMessagesThatAlreadyHaveMoreHops()
public function testProlongFlashDoesNotReFlashMessagesThatAlreadyHaveMoreHops(): void
{
$messages = [
'test' => [
Expand Down Expand Up @@ -290,7 +290,7 @@ public function testProlongFlashDoesNotReFlashMessagesThatAlreadyHaveMoreHops()
$flash->prolongFlash();
}

public function testClearFlashShouldRemoveAnyUnexpiredMessages()
public function testClearFlashShouldRemoveAnyUnexpiredMessages(): void
{
$messages = [
'test' => [
Expand Down Expand Up @@ -337,7 +337,7 @@ public function testClearFlashShouldRemoveAnyUnexpiredMessages()
$flash->clearFlash();
}

public function testCreationAggregatesThrowsExceptionIfInvalidNumberOfHops()
public function testCreationAggregatesThrowsExceptionIfInvalidNumberOfHops(): void
{
$this->expectException(InvalidHopsValueException::class);

Expand All @@ -363,15 +363,15 @@ public function testCreationAggregatesThrowsExceptionIfInvalidNumberOfHops()
$flash->flash('test', 'value', 0);
}

public function testFlashNowAcceptsZeroHops()
public function testFlashNowAcceptsZeroHops(): void
{
$flash = FlashMessages::createFromSession($this->session);
$flash->flashNow('test', 'value', 0);

$this->assertSame('value', $flash->getFlash('test'));
}

public function testFlashNowWithZeroHopsShouldNotSetValueToSession()
public function testFlashNowWithZeroHopsShouldNotSetValueToSession(): void
{
$this->session
->expects($this->never())
Expand Down
2 changes: 1 addition & 1 deletion test/TestAsset/FlashMessages.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function flashNow(string $key, $value, int $hops = 1): void
}

/**
* @param null $default
* @param mixed|null $default
* @return mixed|void
*/
public function getFlash(string $key, $default = null)
Expand Down

0 comments on commit 5133c59

Please sign in to comment.