summaryrefslogtreecommitdiff
path: root/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php
blob: 52e5e0455e6e0903e5abeae945eb4ae4198e4a77 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <[email protected]>
 *     Marcello Duarte <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator;

use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
use Prophecy\Doubler\Generator\Node\TypeNodeAbstract;

/**
 * Class code creator.
 * Generates PHP code for specific class node tree.
 *
 * @author Konstantin Kudryashov <[email protected]>
 */
class ClassCodeGenerator
{
    public function __construct(TypeHintReference $typeHintReference = null)
    {
    }

    /**
     * Generates PHP code for class node.
     *
     * @param string         $classname
     * @param Node\ClassNode $class
     *
     * @return string
     */
    public function generate($classname, Node\ClassNode $class)
    {
        $parts     = explode('\\', $classname);
        $classname = array_pop($parts);
        $namespace = implode('\\', $parts);

        $code = sprintf("class %s extends \%s implements %s {\n",
            $classname, $class->getParentClass(), implode(', ',
                array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces())
            )
        );

        foreach ($class->getProperties() as $name => $visibility) {
            $code .= sprintf("%s \$%s;\n", $visibility, $name);
        }
        $code .= "\n";

        foreach ($class->getMethods() as $method) {
            $code .= $this->generateMethod($method)."\n";
        }
        $code .= "\n}";

        return sprintf("namespace %s {\n%s\n}", $namespace, $code);
    }

    private function generateMethod(Node\MethodNode $method)
    {
        $php = sprintf("%s %s function %s%s(%s)%s {\n",
            $method->getVisibility(),
            $method->isStatic() ? 'static' : '',
            $method->returnsReference() ? '&':'',
            $method->getName(),
            implode(', ', $this->generateArguments($method->getArguments())),
            ($ret = $this->generateTypes($method->getReturnTypeNode())) ? ': '.$ret : ''
        );
        $php .= $method->getCode()."\n";

        return $php.'}';
    }

    private function generateTypes(TypeNodeAbstract $typeNode): string
    {
        if (!$typeNode->getTypes()) {
            return '';
        }

        // When we require PHP 8 we can stop generating ?foo nullables and remove this first block
        if ($typeNode->canUseNullShorthand()) {
            return sprintf( '?%s', $typeNode->getNonNullTypes()[0]);
        } else {
            return join('|', $typeNode->getTypes());
        }
    }

    private function generateArguments(array $arguments)
    {
        return array_map(function (Node\ArgumentNode $argument){

            $php = $this->generateTypes($argument->getTypeNode());

            $php .= ' '.($argument->isPassedByReference() ? '&' : '');

            $php .= $argument->isVariadic() ? '...' : '';

            $php .= '$'.$argument->getName();

            if ($argument->isOptional() && !$argument->isVariadic()) {
                $php .= ' = '.var_export($argument->getDefault(), true);
            }

            return $php;
        }, $arguments);
    }
}