1<?php
2
3namespace Prophecy\Doubler\ClassPatch;
4
5use Prophecy\Doubler\Generator\Node\ClassNode;
6use Prophecy\Exception\Doubler\ClassCreatorException;
7
8class ThrowablePatch implements ClassPatchInterface
9{
10    /**
11     * Checks if patch supports specific class node.
12     *
13     * @param ClassNode $node
14     * @return bool
15     */
16    public function supports(ClassNode $node)
17    {
18        return $this->implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node);
19    }
20
21    /**
22     * @param ClassNode $node
23     * @return bool
24     */
25    private function implementsAThrowableInterface(ClassNode $node)
26    {
27        foreach ($node->getInterfaces() as $type) {
28            if (is_a($type, 'Throwable', true)) {
29                return true;
30            }
31        }
32
33        return false;
34    }
35
36    /**
37     * @param ClassNode $node
38     * @return bool
39     */
40    private function doesNotExtendAThrowableClass(ClassNode $node)
41    {
42        return !is_a($node->getParentClass(), 'Throwable', true);
43    }
44
45    /**
46     * Applies patch to the specific class node.
47     *
48     * @param ClassNode $node
49     *
50     * @return void
51     */
52    public function apply(ClassNode $node)
53    {
54        $this->checkItCanBeDoubled($node);
55        $this->setParentClassToException($node);
56    }
57
58    private function checkItCanBeDoubled(ClassNode $node)
59    {
60        $className = $node->getParentClass();
61        if ($className !== 'stdClass') {
62            throw new ClassCreatorException(
63                sprintf(
64                    'Cannot double concrete class %s as well as implement Traversable',
65                    $className
66                ),
67                $node
68            );
69        }
70    }
71
72    private function setParentClassToException(ClassNode $node)
73    {
74        $node->setParentClass('Exception');
75
76        $node->removeMethod('getMessage');
77        $node->removeMethod('getCode');
78        $node->removeMethod('getFile');
79        $node->removeMethod('getLine');
80        $node->removeMethod('getTrace');
81        $node->removeMethod('getPrevious');
82        $node->removeMethod('getNext');
83        $node->removeMethod('getTraceAsString');
84    }
85
86    /**
87     * Returns patch priority, which determines when patch will be applied.
88     *
89     * @return int Priority number (higher - earlier)
90     */
91    public function getPriority()
92    {
93        return 100;
94    }
95}
96