Skip to content

Commit 35a93c4

Browse files
authored
Merge pull request #10 from BackEndTea/dont-call
Dont `__call` nor `__callStatic`
2 parents 86cc7bf + 54894f8 commit 35a93c4

File tree

11 files changed

+319
-1
lines changed

11 files changed

+319
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ composer require roave/dont
1818

1919
## Usage
2020

21-
The package currently provides five traits:
21+
The package currently provides seven traits:
2222

2323
* `Dont\DontDeserialise`
2424
* `Dont\DontSerialize`
2525
* `Dont\DontClone`
2626
* `Dont\DontGet`
2727
* `Dont\DontSet`
28+
* `Dont\DontCall`
29+
* `Dont\DontCallStatic`
2830

2931
Usage is straightforward:
3032

src/Dont/DontCall.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dont;
6+
7+
use Dont\Exception\NonCallableObject;
8+
use Dont\Exception\TypeError;
9+
10+
trait DontCall
11+
{
12+
/**
13+
* @throws NonCallableObject
14+
* @throws TypeError
15+
*/
16+
final public function __call($name, $arguments)
17+
{
18+
throw NonCallableObject::fromAttemptedCall($this, $name);
19+
}
20+
}

src/Dont/DontCallStatic.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dont;
6+
7+
use Dont\Exception\NonStaticCallableClass;
8+
9+
trait DontCallStatic
10+
{
11+
/**
12+
* @throws NonStaticCallableClass
13+
*/
14+
final public static function __callStatic($name, $arguments)
15+
{
16+
throw NonStaticCallableClass::fromAttemptedStaticCall(static::class, $name);
17+
}
18+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dont\Exception;
6+
7+
use LogicException;
8+
9+
class NonCallableObject extends LogicException implements ExceptionInterface
10+
{
11+
private const ERROR_TEMPLATE = <<<'ERROR'
12+
The given object %s#%s is not designed to allow any undefined or inaccessible methods to be called.
13+
14+
You tried to call a method called "%s".
15+
16+
Perhaps you made a typo in the method name, or tried to call an inaccessible method?
17+
ERROR;
18+
19+
/**
20+
* @param object $object
21+
* @throws TypeError
22+
*/
23+
public static function fromAttemptedCall($object, string $method) : self
24+
{
25+
if (! is_object($object)) {
26+
throw TypeError::fromNonObject($object);
27+
}
28+
29+
$className = get_class($object);
30+
31+
return new self(sprintf(
32+
self::ERROR_TEMPLATE,
33+
$className,
34+
spl_object_hash($object),
35+
$method
36+
));
37+
}
38+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dont\Exception;
6+
7+
use LogicException;
8+
9+
class NonStaticCallableClass extends LogicException implements ExceptionInterface
10+
{
11+
private const ERROR_TEMPLATE = <<<'ERROR'
12+
The given class %s is not designed to allow any undefined or inaccessible static methods to be called.
13+
14+
You tried to call a static method called "%s".
15+
16+
Perhaps you made a typo in the method name, or tried to call an inaccessible method?
17+
ERROR;
18+
19+
/**
20+
* @throws TypeError
21+
*/
22+
public static function fromAttemptedStaticCall(string $class, string $method) : self
23+
{
24+
return new self(sprintf(
25+
self::ERROR_TEMPLATE,
26+
$class,
27+
$method
28+
));
29+
}
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DontTest;
6+
7+
use Dont\DontCallStatic;
8+
use Dont\Exception\NonStaticCallableClass;
9+
use DontTestAsset\NonStaticCallable;
10+
use PHPUnit\Framework\TestCase;
11+
12+
/**
13+
* @covers \Dont\DontCallStatic
14+
*/
15+
final class DontCallStaticTest extends TestCase
16+
{
17+
public function testWillThrowOnStaticCallAttempt() : void
18+
{
19+
$this->expectException(NonStaticCallableClass::class);
20+
$this->expectExceptionMessage('NonStaticCallable');
21+
22+
NonStaticCallable::undefinedMethod();
23+
}
24+
25+
public function testCallStaticPreventionIsFinal() : void
26+
{
27+
self::assertTrue((new \ReflectionMethod(DontCallStatic::class, '__callStatic'))->isFinal());
28+
}
29+
}

tests/DontTest/DontCallTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DontTest;
6+
7+
use Dont\DontCall;
8+
use Dont\Exception\NonCallableObject;
9+
use DontTestAsset\NonCallable;
10+
use PHPUnit\Framework\TestCase;
11+
12+
/**
13+
* @covers \Dont\DontCall
14+
*/
15+
final class DontCallTest extends TestCase
16+
{
17+
public function testWillThrowOnCallingAttempt() : void
18+
{
19+
$object = new NonCallable();
20+
21+
$this->expectException(NonCallableObject::class);
22+
23+
$object->undefinedMethod();
24+
}
25+
26+
public function testCallPreventionIsFinal() : void
27+
{
28+
self::assertTrue((new \ReflectionMethod(DontCall::class, '__call'))->isFinal());
29+
}
30+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DontTest\Exception;
6+
7+
use Dont\Exception\ExceptionInterface;
8+
use Dont\Exception\NonCallableObject;
9+
use Dont\Exception\TypeError;
10+
use LogicException;
11+
use PHPUnit\Framework\TestCase;
12+
use stdClass;
13+
14+
/**
15+
* @covers \Dont\Exception\NonCallableObject
16+
*/
17+
final class NonCallableObjectTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider objectProvider
21+
*
22+
* @param object $object
23+
*/
24+
public function testFromAttemptedCall($object) : void
25+
{
26+
$exception = NonCallableObject::fromAttemptedCall($object, 'methodName');
27+
28+
self::assertInstanceOf(NonCallableObject::class, $exception);
29+
self::assertInstanceOf(LogicException::class, $exception);
30+
self::assertInstanceOf(ExceptionInterface::class, $exception);
31+
32+
$expected = 'The given object ' . get_class($object)
33+
. '#' . spl_object_hash($object) . " is not designed to allow any undefined or inaccessible methods to be called.\n\n"
34+
. 'You tried to call a method called "methodName".' . "\n\n"
35+
. 'Perhaps you made a typo in the method name, or tried to call an inaccessible method?';
36+
37+
self::assertSame($expected, $exception->getMessage());
38+
}
39+
40+
/**
41+
* @return object[][]
42+
*/
43+
public function objectProvider() : array
44+
{
45+
return [
46+
[new stdClass()],
47+
[$this],
48+
];
49+
}
50+
51+
/**
52+
* @dataProvider nonObjectProvider
53+
*
54+
* @param mixed $nonObject
55+
*/
56+
public function testWillThrowOnNonObject($nonObject) : void
57+
{
58+
$this->expectException(TypeError::class);
59+
60+
NonCallableObject::fromAttemptedCall($nonObject, 'propertyName');
61+
}
62+
63+
/**
64+
* @return mixed[][]
65+
*/
66+
public function nonObjectProvider() : array
67+
{
68+
return [
69+
[null],
70+
[true],
71+
[123],
72+
[12.3],
73+
['foo'],
74+
[[]],
75+
[STDERR],
76+
];
77+
}
78+
79+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DontTest\Exception;
6+
7+
use Dont\Exception\ExceptionInterface;
8+
use Dont\Exception\NonStaticCallableClass;
9+
use LogicException;
10+
use PHPUnit\Framework\TestCase;
11+
use stdClass;
12+
13+
/**
14+
* @covers \Dont\Exception\NonStaticCallableClass
15+
*/
16+
final class NonStaticCallableClassTest extends TestCase
17+
{
18+
/**
19+
* @dataProvider objectProvider
20+
*/
21+
public function testFromAttemptedCall(string $class) : void
22+
{
23+
$exception = NonStaticCallableClass::fromAttemptedStaticCall($class, 'methodName');
24+
25+
self::assertInstanceOf(NonStaticCallableClass::class, $exception);
26+
self::assertInstanceOf(LogicException::class, $exception);
27+
self::assertInstanceOf(ExceptionInterface::class, $exception);
28+
29+
$expected = 'The given class ' . $class
30+
. " is not designed to allow any undefined or inaccessible static methods to be called.\n\n"
31+
. 'You tried to call a static method called "methodName".' . "\n\n"
32+
. 'Perhaps you made a typo in the method name, or tried to call an inaccessible method?';
33+
34+
self::assertSame($expected, $exception->getMessage());
35+
}
36+
37+
/**
38+
* @return string[][]
39+
*/
40+
public function objectProvider() : array
41+
{
42+
return [
43+
[stdClass::class],
44+
['Dont\Call\Me\Maybe'],
45+
[self::class],
46+
];
47+
}
48+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DontTestAsset;
6+
7+
use Dont\DontCall;
8+
9+
final class NonCallable
10+
{
11+
use DontCall;
12+
}

0 commit comments

Comments
 (0)