1<?php
2
3/*
4 * This file is part of the Prophecy.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6 *     Marcello Duarte <marcello.duarte@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Prophecy\Doubler\ClassPatch;
13
14use Prophecy\Doubler\Generator\Node\ClassNode;
15use Prophecy\Doubler\Generator\Node\MethodNode;
16use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever;
17use Prophecy\PhpDocumentor\MethodTagRetrieverInterface;
18
19/**
20 * Discover Magical API using "@method" PHPDoc format.
21 *
22 * @author Thomas Tourlourat <thomas@tourlourat.com>
23 * @author Kévin Dunglas <dunglas@gmail.com>
24 * @author Théo FIDRY <theo.fidry@gmail.com>
25 */
26class MagicCallPatch implements ClassPatchInterface
27{
28    private $tagRetriever;
29
30    public function __construct(MethodTagRetrieverInterface $tagRetriever = null)
31    {
32        $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever;
33    }
34
35    /**
36     * Support any class
37     *
38     * @param ClassNode $node
39     *
40     * @return boolean
41     */
42    public function supports(ClassNode $node)
43    {
44        return true;
45    }
46
47    /**
48     * Discover Magical API
49     *
50     * @param ClassNode $node
51     */
52    public function apply(ClassNode $node)
53    {
54        $types = array_filter($node->getInterfaces(), function ($interface) {
55            return 0 !== strpos($interface, 'Prophecy\\');
56        });
57        $types[] = $node->getParentClass();
58
59        foreach ($types as $type) {
60            $reflectionClass = new \ReflectionClass($type);
61
62            while ($reflectionClass) {
63                $tagList = $this->tagRetriever->getTagList($reflectionClass);
64
65                foreach ($tagList as $tag) {
66                    $methodName = $tag->getMethodName();
67
68                    if (empty($methodName)) {
69                        continue;
70                    }
71
72                    if (!$reflectionClass->hasMethod($methodName)) {
73                        $methodNode = new MethodNode($methodName);
74                        $methodNode->setStatic($tag->isStatic());
75                        $node->addMethod($methodNode);
76                    }
77                }
78
79                $reflectionClass = $reflectionClass->getParentClass();
80            }
81        }
82    }
83
84    /**
85     * Returns patch priority, which determines when patch will be applied.
86     *
87     * @return integer Priority number (higher - earlier)
88     */
89    public function getPriority()
90    {
91        return 50;
92    }
93}
94
95