Completed
Push — 0.3 ( ecd01e...43266a )
by David
02:49
created

Compiler::getClosureCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 7
rs 9.4286
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace TheCodingMachine\Yaco;
4
5
use Interop\Container\Definition\DefinitionInterface;
6
use Interop\Container\Definition\DefinitionProviderInterface;
7
use TheCodingMachine\Yaco\Definition\DumpableInterface;
8
use TheCodingMachine\Yaco\Definition\InlineEntryInterface;
9
10
/**
11
 * A class that generates a PHP class (a container) from definitions.
12
 */
13
class Compiler
14
{
15
    /**
16
     * @var DefinitionInterface[]
17
     */
18
    private $definitions = [];
19
20
    /**
21
     * @var DumpableInterface[]
22
     */
23
    private $dumpableDefinitions = [];
24
25
    /**
26
     * The object in charge of converting container-interop definitions to our internal standard.
27
     *
28
     * @var DefinitionConverterInterface
29
     */
30
    private $converter;
31
32
    /**
33
     * @param DefinitionConverterInterface $converter The object in charge of converting container-interop definitions to our internal standard.
34
     */
35
    public function __construct(DefinitionConverterInterface $converter = null)
36
    {
37
        if ($converter === null) {
38
            $converter = new DefinitionConverter();
39
        }
40
        $this->converter = $converter;
41
    }
42
43
    /**
44
     * Adds a definition to the list of definitions managed by this compiler.
45
     *
46
     * @param DefinitionInterface $definition
47
     */
48
    public function addDefinition(DefinitionInterface $definition)
49
    {
50
        $this->definitions[$definition->getIdentifier()] = $definition;
51
        unset($this->dumpableDefinitions[$definition->getIdentifier()]);
52
    }
53
54
    /**
55
     * Registers a new definition provider.
56
     *
57
     * @param DefinitionProviderInterface $definitionProvider
58
     */
59
    public function register(DefinitionProviderInterface $definitionProvider) {
60
        foreach ($definitionProvider->getDefinitions() as $definition) {
61
            $this->addDefinition($definition);
62
        }
63
    }
64
65
    /**
66
     * Adds a dumpable definition to the list of definitions managed by this compiler.
67
     * Note: a "dumpable" definition is a definition represented in Yaco internal format.
68
     *
69
     * @param DumpableInterface $dumpableDefinition
70
     */
71
    public function addDumpableDefinition(DumpableInterface $dumpableDefinition)
72
    {
73
        $this->dumpableDefinitions[$dumpableDefinition->getIdentifier()] = $dumpableDefinition;
74
        unset($this->definitions[$dumpableDefinition->getIdentifier()]);
75
    }
76
77
    /**
78
     * @param string $className
79
     *
80
     * @return string
81
     */
82
    public function compile($className)
83
    {
84
        $classCode = <<<EOF
85
<?php
86
%s
87
88
use Mouf\Picotainer\Picotainer;
89
90
class %s extends Picotainer
91
{
92
    public function __construct(ContainerInterface \$delegateLookupContainer = null) {
93
        parent::__construct([
94
%s        ], \$delegateLookupContainer);
95
        \$this->objects = [
96
%s        ];
97
    }
98
}
99
100
EOF;
101
102
        list($shortClassName, $namespaceLine) = $this->splitFQCN($className);
103
104
        $closuresCode = '';
105
        $parametersCode = '';
106
107
        // Let's merge dumpable definitions with standard definitions.
108
        $convertedDefinitions = array_map([$this->converter, 'convert'], $this->definitions);
109
        $allDefinitions = $convertedDefinitions + $this->dumpableDefinitions;
110
111
        foreach ($allDefinitions as $identifier => $definition) {
112
            $inlineEntry = $definition->toPhpCode('$container', ['$container']);
113
114
            if ($inlineEntry->isLazilyEvaluated()) {
115
                $closuresCode .= '            '.var_export($identifier, true).' => '.$this->getClosureCode($inlineEntry).",\n";
116
            } else {
117
                $parametersCode .= '            '.var_export($identifier, true).' => '.$this->getParametersCode($inlineEntry).",\n";
118
            }
119
        }
120
121
        return sprintf($classCode, $namespaceLine, $shortClassName, $closuresCode, $parametersCode);
122
    }
123
124
    private function splitFQCN($className)
125
    {
126
        $pos = strrpos($className, '\\');
127
        if ($pos !== false) {
128
            $shortClassName = substr($className, $pos + 1);
129
            $namespaceLine = 'namespace '.substr($className, 0, $pos).';';
130
        } else {
131
            $shortClassName = $className;
132
            $namespaceLine = '';
133
        }
134
135
        return [
136
            $shortClassName,
137
            $namespaceLine,
138
        ];
139
    }
140
141
    private function getParametersCode(InlineEntryInterface $inlineEntry)
142
    {
143
        if (!empty($inlineEntry->getStatements())) {
144
            throw new CompilerException('An entry that contains parameters (not lazily loaded) cannot return statements.');
145
        }
146
147
        return $inlineEntry->getExpression();
148
    }
149
150
    private function getClosureCode(InlineEntryInterface $inlineEntry)
151
    {
152
        $code = $inlineEntry->getStatements();
153
        $code .= 'return '.$inlineEntry->getExpression().";\n";
154
155
        return sprintf("function(\$container) {\n%s}", $code);
156
    }
157
}
158