Completed
Push — master ( 438bbb...793df3 )
by Vitaly
02:14 queued 13s
created

Generator::generateViewVariableSetter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 15
ccs 12
cts 12
cp 1
rs 9.4285
cc 1
eloc 12
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 18.02.16 at 14:17
5
 */
6
namespace samsonframework\view;
7
use samsonframework\view\exception\GeneratedViewPathHasReservedWord;
8
9
/**
10
 * Views generator, this class scans resource for view files and creates
11
 * appropriate View class ancestors with namespace as relative view location
12
 * and file name as View class name ending with "View".
13
 *
14
 * Generator also analyzes view files content and creates protected class field
15
 * members for every variable used inside with chainable setter for this field,
16
 * to help IDE and developer in creating awesome code.
17
 *
18
 * TODO: Check for reserved keywords(like list) in namespaces
19
 * TODO: Somehow know view variable type(typehint??) and add comments and type-hints to generated classes.
20
 * TODO: Clever analysis for foreach, if, and so on language structures, we do not need to create variables for loop iterator.
21
 * TODO: If a variable is used in foreach - this is an array or Iteratable ancestor - we can add typehint automatically
22
 * TODO: Analyze view file php doc comments to get variable types
23
 * TODO: If a token variable is not $this and has "->" - this is object, maybe type-hint needs to be added.
24
 * TODO: Add caching logic to avoid duplicate file reading
25
 *
26
 * @package samsonframework\view
27
 */
28
class Generator
29
{
30
    /** string All generated view classes will end with this suffix */
31
    const VIEW_CLASSNAME_SUFFIX = 'View';
32
33
    /** @var array Collection of PHP reserved words */
34
    protected static $reservedWords = array(
35
        'list'
36
    );
37
38
    /** @var Metadata[] Collection of view metadata */
39
    protected $metadata = array();
40
41
    /** @var \samsonphp\generator\Generator */
42
    protected $generator;
43
44
    /** @var string Generated classes namespace prefix */
45
    protected $namespacePrefix;
46
47
    /**
48
     * Generator constructor.
49
     *
50
     * @param \samsonphp\generator\Generator $generator
51
     * @param string                               $namespacePrefix
52
     */
53 1
    public function __construct(\samsonphp\generator\Generator $generator, $namespacePrefix)
54
    {
55 1
        $this->generator = $generator;
56 1
        $this->namespacePrefix = ltrim($namespacePrefix, '\\');
57 1
    }
58
59
    /**
60
     * Recursively scan path for files with specified extensions.
61
     *
62
     * @param string $sourcepath Entry point path
63
     * @param string $path       Entry path for scanning
64
     * @param array  $extensions Collection of file extensions without dot
65
     */
66 1
    public function scan($sourcepath, array $extensions = array(View::DEFAULT_EXT), $path = null)
67
    {
68 1
        $path = isset($path) ? $path : $sourcepath;
69
70
        // Recursively go deeper into inner folders for scanning
71 1
        $folders  = glob($path.'/*', GLOB_ONLYDIR);
72 1
        foreach ($folders as $folder) {
73 1
            $this->scan($folder, $extensions, $sourcepath);
74 1
        }
75
76
        // Iterate file extensions
77 1
        foreach ($extensions as $extension) {
78 1
            foreach (glob(rtrim($path, '/') . '/*.'.$extension) as $file) {
79 1
                $this->metadata[$file] = $this->analyze($file);
80 1
                $this->metadata[$file]->path = str_replace($sourcepath, '', $file);
81 1
                list($this->metadata[$file]->className,
82 1
                    $this->metadata[$file]->namespace) = $this->generateClassName($file, $sourcepath);
83 1
            }
84 1
        }
85 1
    }
86
87
    /**
88
     * Analyze view file and create its metadata.
89
     *
90
     * @param string $file Path to view file
91
     *
92
     * @return Metadata View file metadata
93
     */
94 1
    public function analyze($file)
95
    {
96 1
        $metadata = new Metadata();
97
        // Use PHP tokenizer to find variables
98 1
        foreach ($tokens = token_get_all(file_get_contents($file)) as $idx => $token) {
99 1
            if (!is_string($token) && $token[0] === T_VARIABLE) {
100
                // Store variable
101 1
                $variableText = $token[1];
102
                // Store variable name
103 1
                $variableName = ltrim($token[1], '$');
104
                // If next token is object operator
105 1
                if ($tokens[$idx + 1][0] === T_OBJECT_OPERATOR) {
106 1
                    $variableName = $tokens[$idx + 2][1];
107
                    // And two more tokens
108 1
                    $variableText .= $tokens[$idx + 1][1] . $variableName;
109 1
                }
110
                // Store view variable key - actual object name => full varaible usage
111 1
                $metadata->variables[$variableName] = $variableText;
112 1
            }
113 1
        }
114
115 1
        return $metadata;
116
    }
117
118
    /**
119
     * Generic class name and its name space generator.
120
     *
121
     * @param string $file      Full path to view file
122
     * @param string $entryPath Entry path
123
     *
124
     * @return array Class name[0] and namespace[1]
125
     */
126 1
    protected function generateClassName($file, $entryPath)
127
    {
128
        // Get only file name as a class name with suffix
129 1
        $className = pathinfo($file, PATHINFO_FILENAME) . self::VIEW_CLASSNAME_SUFFIX;
130
131
        // Get namespace as part of file path relatively to entry path
132 1
        $nameSpace = rtrim(ltrim(
133 1
            str_replace(
134 1
                '/',
135 1
                '\\',
136 1
                str_replace($entryPath, '', pathinfo($file, PATHINFO_DIRNAME))
137 1
            ),
138
            '\\'
139 1
        ), '\\');
140
141
        // Check generated namespaces
142 1
        foreach (static::$reservedWords as $reserverWord) {
143 1
            if (strpos($nameSpace, '\\' . $reserverWord) !== false) {
144
                throw new GeneratedViewPathHasReservedWord($reserverWord);
145
            }
146 1
        }
147
148
        // Return collection for further usage
149 1
        return array($className, rtrim($this->namespacePrefix . $nameSpace, '\\'));
150
    }
151
152 1
    public function generate($path = __DIR__)
153
    {
154 1
        foreach ($this->metadata as $metadata) {
155 1
            $this->generateViewClass($metadata, $path);
156 1
        }
157 1
    }
158
159 1
    protected function generateViewClass(Metadata $metadata, $path)
160
    {
161 1
        $this->generator
0 ignored issues
show
Bug introduced by
The method defnamespace() cannot be called from this context as it is declared private in class samsonphp\generator\Generator.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
162 1
            ->defNamespace($metadata->namespace)
163 1
            ->multiComment(array('Class for view "'.$metadata->path.'" rendering'))
164 1
            ->defClass($metadata->className, '\\' . View::CLASSNAME)
0 ignored issues
show
Deprecated Code introduced by
The constant samsonframework\view\View::CLASSNAME has been deprecated with message: Class name for old PHP versions

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
165 1
            ->commentVar('string', 'Path to view file')
166 1
            ->defClassVar('$path', 'protected', $metadata->path)
167 1
            ->commentVar('array', 'Collection of view variables')
168 1
            ->defClassVar('$variables', 'public static', array_keys($metadata->variables));
0 ignored issues
show
Documentation introduced by
array_keys($metadata->variables) is of type array<integer,integer|string>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
169
170
        // Iterate all view variables
171 1
        foreach (array_keys($metadata->variables) as $name) {
172 1
            $this->generator
173 1
                ->commentVar('mixed', 'View variable')
174 1
                ->defClassVar('$'.$name, 'public')
175 1
                ->text($this->generateViewVariableSetter($name));
176 1
        }
177
178 1
        file_put_contents(
179 1
            $path.'/'.$metadata->className.'.php',
180 1
            '<?php'.$this->generator->endClass()->flush()
181 1
        );
182 1
    }
183
184
    /**
185
     * Generate constructor for application class.
186
     *
187
     * @param string $variable View variable name
188
     *
189
     * @return string View variable setter method
190
     */
191 1
    protected function generateViewVariableSetter($variable)
192
    {
193 1
        $class = "\n\t" . '/**';
194 1
        $class .= "\n\t" . ' * Setter for ' . $variable . ' view variable';
195 1
        $class .= "\n\t" . ' *';
196 1
        $class .= "\n\t" . ' * @param mixed $value View variable value';
197 1
        $class .= "\n\t" . ' * @return $this Chaining';
198 1
        $class .= "\n\t" . ' */';
199 1
        $class .= "\n\t" . 'public function ' . $variable . '($value)';
200 1
        $class .= "\n\t" . '{';
201 1
        $class .= "\n\t\t" . 'return parent::set($value, \'' . $variable . '\');';
202 1
        $class .= "\n\t" . '}' . "\n";
203
204 1
        return $class;
205
    }
206
}
207