Completed
Push — master ( 522add...5860cf )
by Vitaly
02:11
created

Generator::generate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
nc 2
nop 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
8
/**
9
 * Views generator, this class scans resource for view files and creates
10
 * appropriate View class ancestors with namespace as relative view location
11
 * and file name as View class name ending with "View".
12
 *
13
 * Generator also analyzes view files content and creates protected class field
14
 * members for every variable used inside with chainable setter for this field,
15
 * to help IDE and developer in creating awesome code.
16
 *
17
 * TODO: Somehow know view variable type(typehint??) and add comments and typehints to generated classes.
18
 * @package samsonframework\view
19
 */
20
class Generator
21
{
22
    /** string All generated view classes will end with this suffix */
23
    const VIEW_CLASSNAME_SUFFIX = 'View';
24
25
    /** @var Metadata[] Collection of view metadata */
26
    protected $metadata = array();
27
28
    /** @var \samsonphp\generator\Generator */
29
    protected $generator;
30
31
    /** @var string Generated classes namespace prefix */
32
    protected $namespacePrefix;
33
34
    /**
35
     * Generator constructor.
36
     *
37
     * @param \samsonphp\generator\Generator $generator
38
     * @param string                               $namespacePrefix
39
     */
40
    public function __construct(\samsonphp\generator\Generator $generator, $namespacePrefix)
41
    {
42
        $this->generator = $generator;
43
        $this->namespacePrefix = ltrim($namespacePrefix, '\\');
44
    }
45
46
    /**
47
     * Recursively scan path for files with specified extensions.
48
     *
49
     * @param string $path Entry path for scanning
50
     * @param array  $extensions Collection of file extensions without dot
51
     */
52
    public function scan($path, array $extensions = array(View::DEFAULT_EXT), $sourcepath)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
53
    {
54
        // Recursively go deeper into inner folders for scanning
55
        $folders  = glob($path.'/*', GLOB_ONLYDIR);
56
        foreach ($folders as $folder) {
57
            $this->scan($folder, $extensions, $sourcepath);
58
        }
59
60
        // Iterate file extensions
61
        foreach ($extensions as $extension) {
62
            foreach (glob(rtrim($path, '/') . '/*.'.$extension) as $file) {
63
                $this->metadata[$file] = $this->analyze($file, $extension, $path);
0 ignored issues
show
Unused Code introduced by
The call to Generator::analyze() has too many arguments starting with $extension.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
64
                list($this->metadata[$file]->className,
65
                    $this->metadata[$file]->namespace) = $this->generateClassName($file, $sourcepath);
66
            }
67
        }
68
    }
69
70
    /**
71
     * Generic class name and its name space generator.
72
     *
73
     * @param string $file Full path to view file
74
     * @param string $entryPath Entry path
75
     *
76
     * @return array Class name[0] and namespace[1]
77
     */
78
    protected function generateClassName($file, $entryPath)
79
    {
80
        // Get only file name as a class name with suffix
81
        $className = pathinfo($file, PATHINFO_FILENAME).self::VIEW_CLASSNAME_SUFFIX;
82
83
        // Get namespace as part of file path relatively to entry path
84
        $nameSpace = rtrim(ltrim(
85
            str_replace(
86
                '/',
87
                '\\',
88
                str_replace($entryPath, '', pathinfo($file, PATHINFO_DIRNAME))
89
            ),
90
            '\\'
91
        ), '\\');
92
93
        return array($className, rtrim($this->namespacePrefix.$nameSpace, '\\'));
94
    }
95
96
    /**
97
     * Analyze view file and create its metadata.
98
     *
99
     * @param string $file Path to view file
100
     * @return Metadata View file metadata
101
     */
102
    public function analyze($file)
103
    {
104
        $metadata = new Metadata();
105
        $metadata->path = $file;
106
107
        // Use PHP tokenizer to find variables
108
        foreach ($tokens = token_get_all(file_get_contents($file)) as $idx => $token) {
109
            if (!is_string($token) && $token[0] === T_VARIABLE) {
110
                // Store variable
111
                $variableText = $token[1];
112
                // Store variable name
113
                $variableName = ltrim($token[1], '$');
114
                // If next token is object operator
115
                if ($tokens[$idx+1][0] === T_OBJECT_OPERATOR) {
116
                    $variableName = $tokens[$idx+2][1];
117
                    // And two more tokens
118
                    $variableText .= $tokens[$idx+1][1].$variableName;
119
                }
120
                // Store view variable key - actual object name => full varaible usage
121
                $metadata->variables[$variableName] = $variableText;
122
            }
123
        }
124
125
        return $metadata;
126
    }
127
128
    /**
129
     * Generate constructor for application class.
130
     *
131
     * @param string $variable View variable name
132
     *
133
     * @return string View variable setter method
134
     */
135
    protected function generateViewVariableSetter($variable)
136
    {
137
        $class = "\n\t".'/**';
138
        $class .= "\n\t".' * Setter for '.$variable.' view variable';
139
        $class .= "\n\t".' *';
140
        $class .= "\n\t".' * @param mixed $value View variable value';
141
        $class .= "\n\t".' * @return $this Chaining';
142
        $class .= "\n\t".' */';
143
        $class .= "\n\t".'public function '.$variable.'($value)';
144
        $class .= "\n\t".'{';
145
        $class .= "\n\t\t".'return parent::set($value, \''.$variable.'\');';
146
        $class .= "\n\t".'}'."\n";
147
148
        return $class;
149
    }
150
151
    protected function generateViewClass(Metadata $metadata, $path)
152
    {
153
        $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...
154
            ->defNamespace($metadata->namespace)
0 ignored issues
show
Documentation introduced by
$metadata->namespace is of type object<samsonframework\view\View>, but the function expects a string.

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...
155
            ->multiComment(array('Class for view "'.$metadata->path.'" rendering'))
156
            ->defClass($metadata->className, '\\'.View::class)
0 ignored issues
show
Documentation introduced by
$metadata->className is of type object<samsonframework\view\View>, but the function expects a string.

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...
157
            ->commentVar('string', 'Path to view file')
158
            ->defClassVar('$path', 'protected', $metadata->path)
159
            ->commentVar('array', 'Collection of view variables')
160
            ->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...
161
        ;
162
163
        // Iterate all view variables
164
        foreach ($metadata->variables as $name => $test) {
165
            $this->generator
166
                ->commentVar('mixed', 'View variable')
167
                ->defClassVar('$'.$name, 'public')
168
                ->text($this->generateViewVariableSetter($name));
169
        }
170
171
        file_put_contents(
172
            $path.'/'.$metadata->className.'.php',
173
            '<?php'.$this->generator->endClass()->flush()
174
        );
175
    }
176
177
    public function generate($path = __DIR__)
178
    {
179
        foreach ($this->metadata as $metadata) {
180
            $this->generateViewClass($metadata, $path);
181
        }
182
    }
183
}
184