Completed
Push — master ( d9f7fe...0d6725 )
by Igor
02:53
created

Native::getContent()   B

Complexity

Conditions 9
Paths 49

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
rs 7.5789
c 0
b 0
f 0
cc 9
nc 49
nop 2
1
<?php
2
/**
3
 * @license MIT
4
 */
5
namespace Pivasic\Bundle\Template;
6
7
use Pivasic\Bundle\Template\Exception\FileNotFoundException;
8
9
/**
10
 * Class Native
11
 * @package Pivasic\Bundle\Template
12
 */
13
class Native
14
{
15
    /**
16
     * @param string $packageRoot
17
     */
18
    public function __construct($packageRoot)
19
    {
20
        $this->packageRoot = rtrim($packageRoot, '/');
21
        $this->isRouteView = false;
22
    }
23
24
    /**
25
     * Create content from template and data.
26
     *
27
     * @param string $name
28
     * @param array $data
29
     *
30
     * @return string|null
31
     */
32
    public function getContent($name, array $data = [])
33
    {
34
        $cacheName = $name;
35
        if (false === strpos($name, '.')) {
36
            if (!empty($name)) {
37
                $name = '.' . $name;
38
            }
39
            $name = 'view' . $name . '.html.php';
40
            $this->isRouteView = true;
41
42
            $stack = debug_backtrace();
43
            foreach ($stack as $item) {
44
                if (false !== stripos($item['file'], DIRECTORY_SEPARATOR . 'Route' . DIRECTORY_SEPARATOR)) {
45
                    $cacheName = pathinfo($item['file'], PATHINFO_DIRNAME) . '/' . $name;
46
                    $cacheName = explode('Route' . DIRECTORY_SEPARATOR, $cacheName)[1];
47
                    $cacheName = str_replace(['/', '\\'], '_', $cacheName);
48
                    break;
49
                }
50
            }
51
        }
52
        $path = $this->packageRoot . '/view/_cache/' . str_replace('/', '_', $cacheName);
53
54
        if (!file_exists($path)) {
55
            $code = $this->compile($name, true, true);
56
            if (empty($code)) {
57
                return null;
58
            }
59
60
            $fh = fopen($path, 'wb');
61
            if (flock($fh, LOCK_EX)) {
62
                fwrite($fh, $code);
63
                flock($fh, LOCK_UN);
64
            }
65
            fclose($fh);
66
        }
67
68
        $fh = fopen($path, 'rb');
69
        if (flock($fh, LOCK_SH)) {
70
            $html = self::renderTemplate($path, $data);
71
72
            flock($fh, LOCK_UN);
73
            fclose($fh);
74
75
            return $html;
76
        }
77
78
        return null;
79
    }
80
81
    /**
82
     * Create solid template.
83
     *
84
     * @param string $name
85
     * @param bool $processInclude
86
     * @param bool $processExtends
87
     *
88
     * @return string|null
89
     *
90
     * @throws FileNotFoundException
91
     */
92
    private function compile($name, $processInclude, $processExtends)
93
    {
94
        if ($this->isRouteView) {
95
            $this->isRouteView = false;
96
            $path = '';
97
            $stack = debug_backtrace();
98
            foreach ($stack as $item) {
99
                if (false !== stripos($item['file'], DIRECTORY_SEPARATOR . 'Route' . DIRECTORY_SEPARATOR)) {
100
                    $path = pathinfo($item['file'], PATHINFO_DIRNAME) . '/' . $name;
101
                    break;
102
                }
103
            }
104
        } else {
105
            $path = $this->packageRoot . '/view/' . $name;
106
        }
107
108
        if (file_exists($path)) {
109
            ob_start();
110
            readfile($path);
111
            $code = ob_get_clean();
112
        } else {
113
            throw new FileNotFoundException($path);
114
        }
115
116
        if ($processInclude) {
117
            preg_match_all('/<!-- include (.*) -->/', $code, $matchList);
118
            if (isset($matchList[1])) {
119
                foreach ($matchList[1] as $key => $template) {
120
                    if (!empty($matchList[0][$key]) && false !== strpos($code, $matchList[0][$key])) {
121
                        $template = trim($template);
122
                        $code = str_replace($matchList[0][$key], $this->compile($template, true, false), $code);
123
                    }
124
                }
125
            }
126
        }
127
128
        if ($processExtends) {
129
            preg_match_all('/<!-- extends (.*) -->/', $code, $matchList);
130
            if (isset($matchList[1][0])) {
131
                $template = trim($matchList[1][0]);
132
                $parentHtml = $this->compile($template, true, false);
133
134
                $code = str_replace($matchList[0][0], '', $code);
135
                $parentHtml = str_replace('<!-- section -->', $code, $parentHtml);
136
                $code = $parentHtml;
137
            }
138
        }
139
140
        return $code;
141
    }
142
143
    /**
144
     * Safe include. Used for scope isolation.
145
     *
146
     * @param string $__file__  File to include
147
     * @param array  $data      Data passed to template
148
     *
149
     * @return string
150
     */
151
    private static function renderTemplate($__file__, array $data)
152
    {
153
        ob_start();
154
        extract($data);
155
        include $__file__;
156
        return ob_get_clean();
157
    }
158
159
    private $packageRoot;
160
    private $isRouteView;
161
}
162
163