Skip to content

Commit f3b53eb

Browse files
authored
Add Assert isInitialized. (#325)
1 parent 193aad6 commit f3b53eb

File tree

8 files changed

+111
-0
lines changed

8 files changed

+111
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Method | Description
9797
`object($value, $message = '')` | Check that a value is an object
9898
`objectish($value, $message = '')` | Check that a value is an object or a string of a class that exists
9999
`resource($value, $type = null, $message = '')` | Check that a value is a resource
100+
`isInitialized($value, $property, $message = '')` | Check that a value has an initialized property
100101
`isCallable($value, $message = '')` | Check that a value is a callable
101102
`isArray($value, $message = '')` | Check that a value is an array
102103
`isIterable($value, $message = '')` | Check that a value is an array or a `\Traversable`

bin/generate.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
require_once __DIR__.'/../vendor/autoload.php';
1313

1414
file_put_contents(__DIR__.'/../src/Mixin.php', (new MixinGenerator())->generate());
15+
16+
echo "Done.";

bin/src/MixinGenerator.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ final class MixinGenerator
4242
'allIsNonEmptyMap', // not supported by psalm (https://github.com/vimeo/psalm/issues/3444)
4343
];
4444

45+
private array $skipGenerateForMethods = [
46+
'isInitialized',
47+
];
48+
4549
/**
4650
* @psalm-var list<string>
4751
*
@@ -568,6 +572,10 @@ private function getMethods(ReflectionClass $assert): array
568572
$staticMethods = $assert->getMethods(ReflectionMethod::IS_STATIC);
569573

570574
foreach ($staticMethods as $staticMethod) {
575+
if (in_array($staticMethod->name, $this->skipGenerateForMethods)) {
576+
continue;
577+
}
578+
571579
$modifiers = $staticMethod->getModifiers();
572580
if (0 === ($modifiers & ReflectionMethod::IS_PUBLIC)) {
573581
continue;

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"composer --working-dir=tools/psalm update",
5858
"composer --working-dir=tools/roave-bc-check update"
5959
],
60+
"generate-mixin": "php bin/generate.php",
6061
"bc-check": "./tools/roave-bc-check/vendor/bin/roave-backward-compatibility-check",
6162
"cs-check" : "./tools/php-cs-fixer/vendor/bin/php-cs-fixer check",
6263
"cs-fix": "./tools/php-cs-fixer/vendor/bin/php-cs-fixer fix",

src/Assert.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Countable;
1818
use DateTime;
1919
use DateTimeImmutable;
20+
use ReflectionProperty;
2021
use Throwable;
2122
use Traversable;
2223

@@ -302,6 +303,29 @@ public static function resource(mixed $value, ?string $type = null, string $mess
302303
return $value;
303304
}
304305

306+
/**
307+
* @psalm-pure
308+
*
309+
* @psalm-assert object $value
310+
*
311+
* @throws InvalidArgumentException
312+
*/
313+
public static function isInitialized(mixed $value, string $property, string $message = ''): object
314+
{
315+
Assert::object($value);
316+
317+
$reflectionProperty = new ReflectionProperty($value, $property);
318+
319+
if (!$reflectionProperty->isInitialized($value)) {
320+
static::reportInvalidArgument(\sprintf(
321+
$message ?: 'Expected property %s to be initialized.',
322+
$property,
323+
));
324+
}
325+
326+
return $value;
327+
}
328+
305329
/**
306330
* @psalm-pure
307331
*

tests/AssertTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
*/
3535
class AssertTest extends TestCase
3636
{
37+
private const SKIP_MIXIN_ASSERTION_TESTS = array(
38+
'isInitialized',
39+
);
40+
3741
public static function getResource()
3842
{
3943
static $resource;
@@ -116,6 +120,20 @@ public static function getTests(): array
116120
array('resource', array(self::getResource(), 'stream'), true),
117121
array('resource', array(self::getResource(), 'other'), false),
118122
array('resource', array(1), false),
123+
array('isInitialized', array(new class {
124+
public mixed $a = null;
125+
}, 'a'), true),
126+
array('isInitialized', array(new class {
127+
public mixed $a;
128+
}, 'a'), false),
129+
array('isInitialized', array(new class {
130+
public mixed $a;
131+
public mixed $b = true;
132+
}, 'a'), false),
133+
array('isInitialized', array(new class {
134+
public mixed $a;
135+
public mixed $b = true;
136+
}, 'b'), true),
119137
array('isCallable', array('strlen'), true),
120138
array('isCallable', array(array(self::class, 'getTests')), true),
121139
array('isCallable', array(function () {}), true),
@@ -614,6 +632,10 @@ public function testAssert(string $method, array $args, bool $success, bool $mul
614632
#[DataProvider('getTests')]
615633
public function testNullOr(string $method, array $args, bool $success, bool $multibyte = false): void
616634
{
635+
if (in_array($method, self::SKIP_MIXIN_ASSERTION_TESTS)) {
636+
$this->markTestSkipped("The method $method does not have nullOr Mixin.");
637+
}
638+
617639
if (in_array($method, array('null', 'notNull'))) {
618640
$this->markTestSkipped('Meaningless test of '.$method);
619641
}
@@ -633,6 +655,10 @@ public function testNullOr(string $method, array $args, bool $success, bool $mul
633655
#[DataProvider('getMethods')]
634656
public function testNullOrAcceptsNull(string $method): void
635657
{
658+
if (in_array($method, self::SKIP_MIXIN_ASSERTION_TESTS)) {
659+
$this->markTestSkipped("The method $method does not have nullOr Mixin.");
660+
}
661+
636662
if (in_array($method, array('null', 'notNull'))) {
637663
$this->markTestSkipped('Meaningless test of '.$method);
638664
}
@@ -644,6 +670,10 @@ public function testNullOrAcceptsNull(string $method): void
644670
#[DataProvider('getTests')]
645671
public function testAllArray(string $method, array $args, bool $success, bool $multibyte = false): void
646672
{
673+
if (in_array($method, self::SKIP_MIXIN_ASSERTION_TESTS)) {
674+
$this->markTestSkipped("The method $method does not have all Mixin.");
675+
}
676+
647677
if ($multibyte && !function_exists('mb_strlen')) {
648678
$this->markTestSkipped('The function mb_strlen() is not available');
649679
}
@@ -662,6 +692,10 @@ public function testAllArray(string $method, array $args, bool $success, bool $m
662692
#[DataProvider('getTests')]
663693
public function testAllNullOrArray(string $method, array $args, bool $success, bool $multibyte = false): void
664694
{
695+
if (in_array($method, self::SKIP_MIXIN_ASSERTION_TESTS)) {
696+
$this->markTestSkipped("The method $method does not have allNullOr Mixin.");
697+
}
698+
665699
if (in_array($method, array('null', 'notNull'))) {
666700
$this->markTestSkipped('Meaningless test of '.$method);
667701
}
@@ -691,6 +725,10 @@ public function testAllNullOrArray(string $method, array $args, bool $success, b
691725
#[DataProvider('getTests')]
692726
public function testAllTraversable(string $method, array $args, bool $success, bool $multibyte = false): void
693727
{
728+
if (in_array($method, self::SKIP_MIXIN_ASSERTION_TESTS)) {
729+
$this->markTestSkipped("The method $method does not have all Mixin.");
730+
}
731+
694732
if ($multibyte && !function_exists('mb_strlen')) {
695733
$this->markTestSkipped('The function mb_strlen() is not available');
696734
}

tests/ProjectCodeTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ class ProjectCodeTest extends TestCase
1919
private static string $assertDocComment;
2020
private static array $mixinMethodNames;
2121

22+
/** @var string[] */
23+
private array $methodDoesNotHaveNullOrMixin = array(
24+
'isInitialized',
25+
);
26+
27+
/** @var string[] */
28+
private array $methodDoesNotHaveAllMixin = array(
29+
'isInitialized',
30+
);
31+
2232
#[BeforeClass]
2333
public static function scanStaticContent(): void
2434
{
@@ -36,6 +46,10 @@ public static function scanStaticContent(): void
3646
#[DataProvider('providesMethodNames')]
3747
public function testHasNullOr(string $method): void
3848
{
49+
if (in_array($method, $this->methodDoesNotHaveNullOrMixin)) {
50+
$this->markTestSkipped("The method $method does not have nullOr Mixin.");
51+
}
52+
3953
$fullMethodName = 'nullOr'.ucfirst($method);
4054

4155
if ($method === 'notNull' || $method === 'null') {
@@ -61,6 +75,10 @@ public function testHasNullOr(string $method): void
6175
#[DataProvider('providesMethodNames')]
6276
public function testHasAll(string $method): void
6377
{
78+
if (in_array($method, $this->methodDoesNotHaveAllMixin)) {
79+
$this->markTestSkipped("The method $method does not have all Mixin.");
80+
}
81+
6482
$fullMethodName = 'all'.ucfirst($method);
6583

6684
$correct = strpos((string) self::$assertDocComment, '@method static void '.$fullMethodName);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webmozart\Assert\Tests\StaticAnalysis;
6+
7+
use Webmozart\Assert\Assert;
8+
9+
/**
10+
* @psalm-pure
11+
*
12+
* @param object $value
13+
*/
14+
function isInitialized(mixed $value, string $property): object
15+
{
16+
Assert::isInitialized($value, $property);
17+
18+
return $value;
19+
}

0 commit comments

Comments
 (0)