Skip to content

Commit

Permalink
Introduce Class, Trait, and Interface components
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz committed May 14, 2021
1 parent 7c38f8c commit e858b11
Show file tree
Hide file tree
Showing 16 changed files with 367 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"autoload-dev": {
"psr-4": {
"Psl\\Tests\\StaticAnalysis\\": "tests/static-analysis",
"Psl\\Tests\\Unit\\": "tests/unit"
"Psl\\Tests\\Unit\\": "tests/unit",
"Psl\\Tests\\Fixture\\": "tests/fixture"
}
},
"extra": {
Expand Down
26 changes: 26 additions & 0 deletions src/Psl/Class/defined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use function class_exists;

/**
* Checks if the class with the given name has already been defined.
*
* @param string $class_name
*
* @psalm-assert-if-true class-string $classname
*
* @pure
*/
function defined(string $class_name): bool
{
/**
* @psalm-suppress ImpureFunctionCall - call is pure.
*
* @var bool
*/
return class_exists($class_name, false);
}
20 changes: 20 additions & 0 deletions src/Psl/Class/exists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use function class_exists;

/**
* Checks if the class with the given name exists.
*
* @param string $class_name
*
* @psalm-assert-if-true class-string $classname
*/
function exists(string $class_name): bool
{
/** @var bool */
return class_exists($class_name, true);
}
23 changes: 23 additions & 0 deletions src/Psl/Class/has_constant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use Psl;
use ReflectionClass;

/**
* Checks if constant is defined in the given class.
*
* @param class-string $class_name
*
* @throws Psl\Exception\InvariantViolationException If $class_name does not exist.
*/
function has_constant(string $class_name, string $constant_name): bool
{
Psl\invariant(namespace\exists($class_name), 'Classname "%s" does not exist.', $class_name);

/** @psalm-suppress MissingThrowsDocblock */
return (new ReflectionClass($class_name))->hasConstant($constant_name);
}
23 changes: 23 additions & 0 deletions src/Psl/Class/has_method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use Psl;
use ReflectionClass;

/**
* Checks if method is defined in the given class.
*
* @param class-string $class_name
*
* @throws Psl\Exception\InvariantViolationException If $class_name does not exist.
*/
function has_method(string $class_name, string $method_name): bool
{
Psl\invariant(namespace\exists($class_name), 'Classname "%s" does not exist.', $class_name);

/** @psalm-suppress MissingThrowsDocblock */
return (new ReflectionClass($class_name))->hasMethod($method_name);
}
23 changes: 23 additions & 0 deletions src/Psl/Class/is_abstract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use Psl;
use ReflectionClass;

/**
* Checks if class is abstract.
*
* @param class-string $class_name
*
* @throws Psl\Exception\InvariantViolationException If $class_name does not exist.
*/
function is_abstract(string $class_name): bool
{
Psl\invariant(namespace\exists($class_name), 'Classname "%s" does not exist.', $class_name);

/** @psalm-suppress MissingThrowsDocblock */
return (new ReflectionClass($class_name))->isAbstract();
}
23 changes: 23 additions & 0 deletions src/Psl/Class/is_final.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Psl\Class;

use Psl;
use ReflectionClass;

/**
* Checks if class is final.
*
* @param class-string $class_name
*
* @throws Psl\Exception\InvariantViolationException If $class_name does not exist.
*/
function is_final(string $class_name): bool
{
Psl\invariant(namespace\exists($class_name), 'Classname "%s" does not exist.', $class_name);

/** @psalm-suppress MissingThrowsDocblock */
return (new ReflectionClass($class_name))->isFinal();
}
20 changes: 20 additions & 0 deletions src/Psl/Interface/defined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Psl\Interface;

use function interface_exists;

/**
* Checks if the interface with the given name has already been defined.
*
* @param string $interface_name
*
* @pure
*/
function defined(string $interface_name): bool
{
/** @var bool */
return interface_exists($interface_name, false);
}
18 changes: 18 additions & 0 deletions src/Psl/Interface/exists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Interface;

use function interface_exists;

/**
* Checks if the interface with the given name exists.
*
* @param string $interface_name
*/
function exists(string $interface_name): bool
{
/** @var bool */
return interface_exists($interface_name, true);
}
10 changes: 10 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,16 @@ final class Loader
'Psl\IO\input_handle',
'Psl\IO\output_handle',
'Psl\IO\error_handle',
'Psl\Class\exists',
'Psl\Class\defined',
'Psl\Class\has_constant',
'Psl\Class\has_method',
'Psl\Class\is_abstract',
'Psl\Class\is_final',
'Psl\Interface\exists',
'Psl\Interface\defined',
'Psl\Trait\exists',
'Psl\Trait\defined',
];

public const INTERFACES = [
Expand Down
20 changes: 20 additions & 0 deletions src/Psl/Trait/defined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Psl\Trait;

use function trait_exists;

/**
* Checks if the trait with the given name has already been defined.
*
* @param string $trait_name
*
* @pure
*/
function defined(string $trait_name): bool
{
/** @var bool */
return trait_exists($trait_name, false);
}
18 changes: 18 additions & 0 deletions src/Psl/Trait/exists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Trait;

use function trait_exists;

/**
* Checks if the trait with the given name exists.
*
* @param string $trait_name
*/
function exists(string $trait_name): bool
{
/** @var bool */
return trait_exists($trait_name, true);
}
9 changes: 9 additions & 0 deletions tests/fixture/ExampleTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Fixture;

trait ExampleTrait
{
}
57 changes: 57 additions & 0 deletions tests/unit/Class/ClassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Class;

use PHPUnit\Framework\TestCase;
use Psl\Class;
use Psl\Collection;
use Psl\Type;

final class ClassTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function test(
string $classname,
bool $exists,
bool $final,
bool $abstract,
array $methods = [],
array $constants = []
): void {
static::assertSame($exists, Class\exists($classname));
static::assertSame($exists, Class\defined($classname));
if (!$exists) {
return;
}

static::assertSame($final, Class\is_final($classname));
static::assertSame($abstract, Class\is_abstract($classname));

foreach ($methods as $method) {
static::assertTrue(CLass\has_method($classname, $method));
}

static::assertFalse(Class\has_method($classname, 'ImNotAClassMethod'));

foreach ($constants as $constant) {
static::assertTrue(Class\has_constant($classname, $constant));
}

static::assertFalse(Class\has_constant($classname, 'I_AM_NOT_A_CONSTANT'));
}

public function provideData(): iterable
{
yield [Collection\Vector::class, true, true, false, ['first', 'last'], []];
yield [Collection\MutableVector::class, true, true, false, ['first', 'last'], []];
yield [Collection\Map::class, true, true, false, ['first', 'last'], []];
yield [Collection\MutableMap::class, true, true, false, ['first', 'last'], []];
yield [Type\Type::class, true, false, true, ['matches', 'getTrace', 'withTrace', 'isOptional'], []];

yield ['Psl\\Not\\Class', false, false, false, [], []];
}
}
41 changes: 41 additions & 0 deletions tests/unit/Interface/InterfaceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Interface;

use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Interface;
use Psl\Type;

final class InterfaceTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function test(
string $interface_name,
bool $exists,
): void {
static::assertSame($exists, Interface\exists($interface_name));
static::assertSame($exists, Interface\defined($interface_name));
}

public function provideData(): iterable
{
yield [Collection\VectorInterface::class, true];
yield [Collection\MutableVectorInterface::class, true];
yield [Collection\MapInterface::class, true];
yield [Collection\MutableMapInterface::class, true];
yield [Type\TypeInterface::class, true];

yield [Collection\Vector::class, false];
yield [Collection\MutableVector::class, false];
yield [Collection\Map::class, false];
yield [Collection\MutableMap::class, false];
yield [Type\Type::class, false];

yield ['Psl\\Not\\Interface', false];
}
}
34 changes: 34 additions & 0 deletions tests/unit/Trait/TraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Trait;

use PHPUnit\Framework\TestCase;
use Psl\Tests\Fixture;
use Psl\Trait;

final class TraitTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function test(
string $trait_name,
bool $defined,
bool $exists,
): void {
static::assertSame($defined, Trait\defined($trait_name));
static::assertSame($exists, Trait\exists($trait_name));

if ($exists) {
static::assertTrue(Trait\defined($trait_name));
}
}

public function provideData(): iterable
{
yield [Fixture\ExampleTrait::class, false, true];
yield ['Psl\\Not\\Trait', false, false];
}
}

0 comments on commit e858b11

Please sign in to comment.