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\Generator;
13
14/**
15 * Class code creator.
16 * Generates PHP code for specific class node tree.
17 *
18 * @author Konstantin Kudryashov <ever.zet@gmail.com>
19 */
20class ClassCodeGenerator
21{
22    /**
23     * @var TypeHintReference
24     */
25    private $typeHintReference;
26
27    public function __construct(TypeHintReference $typeHintReference = null)
28    {
29        $this->typeHintReference = $typeHintReference ?: new TypeHintReference();
30    }
31
32    /**
33     * Generates PHP code for class node.
34     *
35     * @param string         $classname
36     * @param Node\ClassNode $class
37     *
38     * @return string
39     */
40    public function generate($classname, Node\ClassNode $class)
41    {
42        $parts     = explode('\\', $classname);
43        $classname = array_pop($parts);
44        $namespace = implode('\\', $parts);
45
46        $code = sprintf("class %s extends \%s implements %s {\n",
47            $classname, $class->getParentClass(), implode(', ',
48                array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces())
49            )
50        );
51
52        foreach ($class->getProperties() as $name => $visibility) {
53            $code .= sprintf("%s \$%s;\n", $visibility, $name);
54        }
55        $code .= "\n";
56
57        foreach ($class->getMethods() as $method) {
58            $code .= $this->generateMethod($method)."\n";
59        }
60        $code .= "\n}";
61
62        return sprintf("namespace %s {\n%s\n}", $namespace, $code);
63    }
64
65    private function generateMethod(Node\MethodNode $method)
66    {
67        $php = sprintf("%s %s function %s%s(%s)%s {\n",
68            $method->getVisibility(),
69            $method->isStatic() ? 'static' : '',
70            $method->returnsReference() ? '&':'',
71            $method->getName(),
72            implode(', ', $this->generateArguments($method->getArguments())),
73            $this->getReturnType($method)
74        );
75        $php .= $method->getCode()."\n";
76
77        return $php.'}';
78    }
79
80    /**
81     * @return string
82     */
83    private function getReturnType(Node\MethodNode $method)
84    {
85        if (version_compare(PHP_VERSION, '7.1', '>=')) {
86            if ($method->hasReturnType()) {
87                return $method->hasNullableReturnType()
88                    ? sprintf(': ?%s', $method->getReturnType())
89                    : sprintf(': %s', $method->getReturnType());
90            }
91        }
92
93        if (version_compare(PHP_VERSION, '7.0', '>=')) {
94            return $method->hasReturnType() && $method->getReturnType() !== 'void'
95                ? sprintf(': %s', $method->getReturnType())
96                : '';
97        }
98
99        return '';
100    }
101
102    private function generateArguments(array $arguments)
103    {
104        $typeHintReference = $this->typeHintReference;
105        return array_map(function (Node\ArgumentNode $argument) use ($typeHintReference) {
106            $php = '';
107
108            if (version_compare(PHP_VERSION, '7.1', '>=')) {
109                $php .= $argument->isNullable() ? '?' : '';
110            }
111
112            if ($hint = $argument->getTypeHint()) {
113                $php .= $typeHintReference->isBuiltInParamTypeHint($hint) ? $hint : '\\'.$hint;
114            }
115
116            $php .= ' '.($argument->isPassedByReference() ? '&' : '');
117
118            $php .= $argument->isVariadic() ? '...' : '';
119
120            $php .= '$'.$argument->getName();
121
122            if ($argument->isOptional() && !$argument->isVariadic()) {
123                $php .= ' = '.var_export($argument->getDefault(), true);
124            }
125
126            return $php;
127        }, $arguments);
128    }
129}
130