Skip to content

Commit 1706a34

Browse files
committed
ObjectHelpers: improved error messages, added scope
1 parent 967cfc4 commit 1706a34

File tree

4 files changed

+224
-35
lines changed

4 files changed

+224
-35
lines changed

src/Utils/ObjectHelpers.php

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,59 @@ public static function strictSet(string $class, string $name): void
4747
/** @throws MemberAccessException */
4848
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
4949
{
50-
$hint = self::getSuggestion(array_merge(
51-
get_class_methods($class),
52-
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
53-
$additionalMethods
54-
), $method);
50+
$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
51+
$context = ($trace[1]['function'] ?? null) === '__call'
52+
? ($trace[2]['class'] ?? null)
53+
: null;
54+
55+
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
56+
$class = get_parent_class($context);
57+
}
5558

56-
if (method_exists($class, $method)) { // called parent::$method()
57-
$class = 'parent';
59+
if (method_exists($class, $method)) { // insufficient visibility
60+
$rm = new \ReflectionMethod($class, $method);
61+
$visibility = $rm->isPrivate()
62+
? 'private '
63+
: ($rm->isProtected() ? 'protected ' : '');
64+
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
65+
66+
} else {
67+
$hint = self::getSuggestion(array_merge(
68+
get_class_methods($class),
69+
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
70+
$additionalMethods
71+
), $method);
72+
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
5873
}
59-
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
6074
}
6175

6276

6377
/** @throws MemberAccessException */
6478
public static function strictStaticCall(string $class, string $method): void
6579
{
66-
$hint = self::getSuggestion(
67-
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
68-
$method
69-
);
70-
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
80+
$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
81+
$context = ($trace[1]['function'] ?? null) === '__callStatic'
82+
? ($trace[2]['class'] ?? null)
83+
: null;
84+
85+
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
86+
$class = get_parent_class($context);
87+
}
88+
89+
if (method_exists($class, $method)) { // insufficient visibility
90+
$rm = new \ReflectionMethod($class, $method);
91+
$visibility = $rm->isPrivate()
92+
? 'private '
93+
: ($rm->isProtected() ? 'protected ' : '');
94+
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
95+
96+
} else {
97+
$hint = self::getSuggestion(
98+
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
99+
$method
100+
);
101+
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
102+
}
71103
}
72104

73105

tests/Utils/ObjectHelpers.strictness.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Assert::exception(function () {
6262

6363
Assert::exception(function () {
6464
ObjectHelpers::strictCall('TestChild', 'callParent');
65-
}, MemberAccessException::class, 'Call to undefined method parent::callParent().');
65+
}, MemberAccessException::class, 'Call to method TestChild::callParent() from global scope.');
6666

6767
Assert::exception(function () {
6868
ObjectHelpers::strictCall('TestClass', 'publicMethodX');
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\SmartObject undeclared method hints.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Tester\Assert;
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
13+
14+
class TestClass
15+
{
16+
use Nette\SmartObject;
17+
18+
private function methodO()
19+
{
20+
}
21+
22+
23+
public function methodO2()
24+
{
25+
}
26+
27+
28+
private static function methodS()
29+
{
30+
}
31+
32+
33+
public static function methodS2()
34+
{
35+
}
36+
}
37+
38+
39+
Assert::exception(function () {
40+
$obj = new TestClass;
41+
$obj->abc();
42+
}, Nette\MemberAccessException::class, 'Call to undefined method TestClass::abc().');
43+
44+
Assert::exception(function () {
45+
$obj = new TestClass;
46+
$obj->method();
47+
}, Nette\MemberAccessException::class, 'Call to undefined method TestClass::method(), did you mean methodO2()?');
48+
49+
Assert::exception(function () {
50+
TestClass::abc();
51+
}, Nette\MemberAccessException::class, 'Call to undefined static method TestClass::abc().');
52+
53+
Assert::exception(function () {
54+
TestClass::method();
55+
}, Nette\MemberAccessException::class, 'Call to undefined static method TestClass::method(), did you mean methodS2()?');
56+
57+
if (extension_loaded('gd')) {
58+
Assert::exception(function () {
59+
Nette\Utils\Image::fromBlank(1, 1)->filledElippse();
60+
}, Nette\MemberAccessException::class, 'Call to undefined method Nette\Utils\Image::filledElippse(), did you mean filledEllipse()?');
61+
}
Lines changed: 117 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
/**
4-
* Test: Nette\SmartObject undeclared method.
4+
* Test: Nette\SmartObject error messages for undeclared method.
55
*/
66

77
declare(strict_types=1);
@@ -11,51 +11,147 @@ use Tester\Assert;
1111
require __DIR__ . '/../bootstrap.php';
1212

1313

14-
class TestClass
14+
class ParentClass
1515
{
1616
use Nette\SmartObject;
1717

18-
private function methodO()
18+
public function callPrivate()
1919
{
20+
$this->privateMethod();
2021
}
2122

2223

23-
public function methodO2()
24+
public function callPrivateStatic()
2425
{
26+
static::privateStaticMethod();
2527
}
2628

2729

28-
private static function methodS()
30+
private function callPrivateParent()
2931
{
3032
}
33+
}
3134

3235

33-
public static function methodS2()
36+
class InterClass extends ParentClass
37+
{
38+
public function callParents()
3439
{
40+
parent::callParents();
3541
}
3642
}
3743

3844

45+
class ChildClass extends InterClass
46+
{
47+
public function callParents()
48+
{
49+
parent::callParents();
50+
}
51+
52+
53+
public function callMissingParent()
54+
{
55+
parent::callMissingParent();
56+
}
57+
58+
59+
public static function callMissingParentStatic()
60+
{
61+
parent::callMissingParentStatic();
62+
}
63+
64+
65+
public function callPrivateParent()
66+
{
67+
parent::callPrivateParent();
68+
}
69+
70+
71+
protected function protectedMethod()
72+
{
73+
}
74+
75+
76+
protected static function protectedStaticMethod()
77+
{
78+
}
79+
80+
81+
private function privateMethod()
82+
{
83+
}
84+
85+
86+
private static function privateStaticMethod()
87+
{
88+
}
89+
}
90+
91+
92+
3993
Assert::exception(function () {
40-
$obj = new TestClass;
41-
$obj->abc();
42-
}, Nette\MemberAccessException::class, 'Call to undefined method TestClass::abc().');
94+
$obj = new ParentClass;
95+
$obj->undef();
96+
}, Nette\MemberAccessException::class, 'Call to undefined method ParentClass::undef().');
4397

4498
Assert::exception(function () {
45-
$obj = new TestClass;
46-
$obj->method();
47-
}, Nette\MemberAccessException::class, 'Call to undefined method TestClass::method(), did you mean methodO2()?');
99+
$obj = new ChildClass;
100+
$obj->undef();
101+
}, Nette\MemberAccessException::class, 'Call to undefined method ChildClass::undef().');
48102

49103
Assert::exception(function () {
50-
TestClass::abc();
51-
}, Nette\MemberAccessException::class, 'Call to undefined static method TestClass::abc().');
104+
$obj = new ChildClass;
105+
$obj->callParents();
106+
}, Nette\MemberAccessException::class, 'Call to undefined method ParentClass::callParents().');
52107

53108
Assert::exception(function () {
54-
TestClass::method();
55-
}, Nette\MemberAccessException::class, 'Call to undefined static method TestClass::method(), did you mean methodS2()?');
109+
$obj = new ChildClass;
110+
$obj->callMissingParent();
111+
}, Nette\MemberAccessException::class, 'Call to undefined method InterClass::callMissingParent().');
56112

57-
if (extension_loaded('gd')) {
58-
Assert::exception(function () {
59-
Nette\Utils\Image::fromBlank(1, 1)->filledElippse();
60-
}, Nette\MemberAccessException::class, 'Call to undefined method Nette\Utils\Image::filledElippse(), did you mean filledEllipse()?');
61-
}
113+
Assert::exception(function () {
114+
$obj = new ChildClass;
115+
$obj->callMissingParentStatic();
116+
}, Nette\MemberAccessException::class, 'Call to undefined static method InterClass::callMissingParentStatic().');
117+
118+
Assert::exception(function () {
119+
$obj = new ChildClass;
120+
$obj::callMissingParentStatic();
121+
}, Nette\MemberAccessException::class, 'Call to undefined static method InterClass::callMissingParentStatic().');
122+
123+
Assert::exception(
124+
function () {
125+
$obj = new ChildClass;
126+
$obj->callPrivateParent();
127+
},
128+
Nette\MemberAccessException::class,
129+
PHP_VERSION_ID < 70400
130+
? 'Call to private method InterClass::callPrivateParent() from scope ChildClass.'
131+
: 'Call to undefined static method InterClass::callPrivateParent().'
132+
);
133+
134+
Assert::exception(function () {
135+
$obj = new ChildClass;
136+
$obj->protectedMethod();
137+
}, Nette\MemberAccessException::class, 'Call to protected method ChildClass::protectedMethod() from global scope.');
138+
139+
Assert::exception(function () {
140+
$obj = new ChildClass;
141+
$obj->protectedStaticMethod();
142+
}, Nette\MemberAccessException::class, 'Call to protected method ChildClass::protectedStaticMethod() from global scope.');
143+
144+
Assert::exception(function () {
145+
$obj = new ChildClass;
146+
$obj::protectedStaticMethod();
147+
}, Nette\MemberAccessException::class, 'Call to protected method ChildClass::protectedStaticMethod() from global scope.');
148+
149+
Assert::exception(function () {
150+
$obj = new ChildClass;
151+
$obj->callPrivate();
152+
}, Nette\MemberAccessException::class, 'Call to private method ChildClass::privateMethod() from scope ParentClass.');
153+
154+
Assert::exception(function () {
155+
$obj = new ChildClass;
156+
$obj->callPrivateStatic();
157+
}, Nette\MemberAccessException::class, 'Call to private method ChildClass::privateStaticMethod() from scope ParentClass.');

0 commit comments

Comments
 (0)