Skip to content

Commit cf312a2

Browse files
author
Alexander Obuhovich
committed
Merge pull request php-annotations#94 from php-annotations/stop-inheritance
Don't inherit class/property/method annotations after finding `@stop` annotation on class level
2 parents 2d0a19d + 75881e6 commit cf312a2

File tree

4 files changed

+166
-11
lines changed

4 files changed

+166
-11
lines changed

src/annotations/AnnotationManager.php

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class AnnotationManager
8383
'throws' => false,
8484
'type' => 'mindplay\annotations\standard\TypeAnnotation',
8585
'usage' => 'mindplay\annotations\UsageAnnotation',
86+
'stop' => 'mindplay\annotations\StopAnnotation',
8687
'uses' => false,
8788
'var' => 'mindplay\annotations\standard\VarAnnotation',
8889
'version' => false,
@@ -247,19 +248,10 @@ protected function getAnnotations($class_name, $member_type = self::MEMBER_CLASS
247248

248249
if (!isset($this->initialized[$key])) {
249250
$annotations = array();
251+
$classAnnotations = array();
250252

251253
if ($member_type !== self::MEMBER_CLASS) {
252-
$this->getAnnotations($class_name, self::MEMBER_CLASS);
253-
}
254-
255-
if ($parent = get_parent_class($class_name)) {
256-
if ($parent !== __NAMESPACE__ . '\Annotation') {
257-
foreach ($this->getAnnotations($parent, $member_type, $member_name) as $annotation) {
258-
if ($this->getUsage(get_class($annotation))->inherited) {
259-
$annotations[] = $annotation;
260-
}
261-
}
262-
}
254+
$classAnnotations = $this->getAnnotations($class_name, self::MEMBER_CLASS);
263255
}
264256

265257
$reflection = new \ReflectionClass($class_name);
@@ -268,6 +260,8 @@ protected function getAnnotations($class_name, $member_type = self::MEMBER_CLASS
268260
$file = $this->getAnnotationFile($reflection->getFileName());
269261
}
270262

263+
$inherit = true; // inherit parent annotations unless directed not to
264+
271265
if (isset($file) && isset($file->data[$key])) {
272266
foreach ($file->data[$key] as $spec) {
273267
$name = $spec['#name']; // currently unused
@@ -293,6 +287,30 @@ protected function getAnnotations($class_name, $member_type = self::MEMBER_CLASS
293287

294288
$annotations[] = $annotation;
295289
}
290+
291+
if ($member_type === self::MEMBER_CLASS) {
292+
$classAnnotations = $annotations;
293+
}
294+
}
295+
296+
foreach ($classAnnotations as $classAnnotation) {
297+
if ($classAnnotation instanceof StopAnnotation) {
298+
$inherit = false; // do not inherit parent annotations
299+
}
300+
}
301+
302+
if ($inherit && $parent = get_parent_class($class_name)) {
303+
$parent_annotations = array();
304+
305+
if ($parent !== __NAMESPACE__ . '\Annotation') {
306+
foreach ($this->getAnnotations($parent, $member_type, $member_name) as $annotation) {
307+
if ($this->getUsage(get_class($annotation))->inherited) {
308+
$parent_annotations[] = $annotation;
309+
}
310+
}
311+
}
312+
313+
$annotations = array_merge($parent_annotations, $annotations);
296314
}
297315

298316
$this->annotations[$key] = $this->applyConstraints($annotations, $member_type);

src/annotations/StopAnnotation.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the php-annotation framework.
5+
*
6+
* (c) Rasmus Schultz <[email protected]>
7+
*
8+
* This software is licensed under the GNU LGPL license
9+
* for more information, please see:
10+
*
11+
* <https://github.com/mindplay-dk/php-annotations>
12+
*/
13+
14+
namespace mindplay\annotations;
15+
16+
/**
17+
* This Annotation indicates that parent class should not be parsed for annotations.
18+
*
19+
* @usage('class'=>true)
20+
*/
21+
class StopAnnotation extends Annotation
22+
{
23+
24+
25+
}

test/suite/Annotations.case.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,67 @@ public function run()
196196
}
197197
}
198198

199+
/**
200+
* @Note('class-first')
201+
*/
202+
class FirstClass
203+
{
204+
/**
205+
* @var string
206+
* @Note('prop-first')
207+
*/
208+
protected $prop;
209+
210+
/**
211+
* @Note('method-first')
212+
*/
213+
protected function someMethod()
214+
{
215+
216+
}
217+
}
218+
219+
/**
220+
* @Note('class-second')
221+
* @stop
222+
*/
223+
class SecondClass extends FirstClass
224+
{
225+
/**
226+
* @var string
227+
* @Note('prop-second')
228+
*/
229+
protected $prop;
230+
231+
/**
232+
* @Note('method-second')
233+
*/
234+
protected function someMethod()
235+
{
236+
237+
}
238+
}
239+
240+
/**
241+
* @Note('class-third')
242+
*/
243+
class ThirdClass extends SecondClass
244+
{
245+
/**
246+
* @var string
247+
* @Note('prop-third')
248+
*/
249+
protected $prop;
250+
251+
/**
252+
* @Note('method-third')
253+
*/
254+
protected function someMethod()
255+
{
256+
257+
}
258+
}
259+
199260

200261
/**
201262
* Test that using an core class will not break parsing.

test/suite/Annotations.test.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,57 @@ protected function assertApplyConstrains(array &$annotations, $memberType)
743743
$this->check(count($annotations) > 0);
744744
}
745745

746+
public function testStopAnnotationPreventsClassLevelAnnotationInheritance()
747+
{
748+
$annotations = Annotations::ofClass('SecondClass', '@note');
749+
$this->check(count($annotations) === 1, 'class level annotation after own "@stop" not present');
750+
$this->check($annotations[0]->note === 'class-second', 'non-inherited annotation goes first');
751+
752+
$annotations = Annotations::ofClass('ThirdClass', '@note');
753+
$this->check(count($annotations) === 2, 'class level annotation after parent "@stop" not present');
754+
$this->check($annotations[0]->note === 'class-second', 'inherited annotation goes first');
755+
$this->check($annotations[1]->note === 'class-third', 'non-inherited annotation goes second');
756+
}
757+
758+
public function testStopAnnotationPreventsPropertyLevelAnnotationInheritance()
759+
{
760+
$annotations = Annotations::ofProperty('SecondClass', 'prop', '@note');
761+
$this->check(count($annotations) === 1, 'property level annotation after own "@stop" not present');
762+
$this->check($annotations[0]->note === 'prop-second', 'non-inherited annotation goes first');
763+
764+
$annotations = Annotations::ofProperty('ThirdClass', 'prop', '@note');
765+
$this->check(count($annotations) === 2, 'property level annotation after parent "@stop" not present');
766+
$this->check($annotations[0]->note === 'prop-second', 'inherited annotation goes first');
767+
$this->check($annotations[1]->note === 'prop-third', 'non-inherited annotation goes second');
768+
}
769+
770+
public function testStopAnnotationPreventsMethodLevelAnnotationInheritance()
771+
{
772+
$annotations = Annotations::ofMethod('SecondClass', 'someMethod', '@note');
773+
$this->check(count($annotations) === 1, 'method level annotation after own "@stop" not present');
774+
$this->check($annotations[0]->note === 'method-second', 'non-inherited annotation goes first');
775+
776+
$annotations = Annotations::ofMethod('ThirdClass', 'someMethod', '@note');
777+
$this->check(count($annotations) === 2, 'method level annotation after parent "@stop" not present');
778+
$this->check($annotations[0]->note === 'method-second', 'inherited annotation goes first');
779+
$this->check($annotations[1]->note === 'method-third', 'non-inherited annotation goes second');
780+
}
781+
782+
public function testDirectAccessToClassIgnoresStopAnnotation()
783+
{
784+
$annotations = Annotations::ofClass('FirstClass', '@note');
785+
$this->check(count($annotations) === 1);
786+
$this->check($annotations[0]->note === 'class-first');
787+
788+
$annotations = Annotations::ofProperty('FirstClass', 'prop', '@note');
789+
$this->check(count($annotations) === 1);
790+
$this->check($annotations[0]->note === 'prop-first');
791+
792+
$annotations = Annotations::ofMethod('FirstClass', 'someMethod', '@note');
793+
$this->check(count($annotations) === 1);
794+
$this->check($annotations[0]->note === 'method-first');
795+
}
796+
746797
protected function testFilterUnresolvedAnnotationClass()
747798
{
748799
$annotations = Annotations::ofClass('TestBase', false);

0 commit comments

Comments
 (0)