View::__get()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * spindle/view
4
 * @license CC0-1.0 (Public Domain)
5
 */
6
namespace Spindle;
7
8
use ArrayObject;
9
10
/**
11
 * 素のPHPテンプレートにlayout機能を付加するシンプルなレンダラーです
12
 */
13
class View implements \IteratorAggregate
14
{
15
    protected
16
        /** @var ArrayObject */ $_storage
17
    ,   /** @var string */ $_basePath
18
    ,   /** @var string */ $_fileName
19
    ,   /** @var string */ $_layoutFileName = ''
20
    ,   /** @var string */ $_content = ''
21
    ;
22
23
    /**
24
     * @param string $fileName 描画したいテンプレートのファイル名を指定します
25
     * @param string $basePath テンプレートの探索基準パスです。相対パスも指定できます。指定しなければinclude_pathから探索します。
26
     * @param ArrayObject $arr view変数の引き継ぎ元です。内部用なので通常は使う必要はありません。
27
     */
28 12
    public function __construct($fileName, $basePath='', ArrayObject $arr=null)
29
    {
30 12
        $this->_storage = $arr ?: new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
31 12
        $this->_fileName = trim($fileName, \DIRECTORY_SEPARATOR);
32 12
        $this->_basePath = rtrim($basePath, \DIRECTORY_SEPARATOR);
33 12
    }
34
35
    /**
36
     * このクラスはforeach可能です
37
     * @return \ArrayIterator
38
     */
39 1
    public function getIterator()
40
    {
41 1
        return $this->_storage->getIterator();
42
    }
43
44
    /**
45
     * @param string|int $name
46
     * @return mixed
47
     */
48 4
    public function __get($name)
49
    {
50 4
        return $this->_storage[$name];
51
    }
52
53
    /**
54
     * @param string|int $name
55
     * @param mixed $value
56
     */
57 4
    public function __set($name, $value)
58
    {
59 4
        $this->_storage[$name] = $value;
60 4
    }
61
62
    /**
63
     * @param string|int $name
64
     * @return bool
65
     */
66 1
    public function __isset($name)
67
    {
68 1
        return isset($this->_storage[$name]);
69
    }
70
71
    /**
72
     * 描画するスクリプトファイルのパスを返します。
73
     * @return string
74
     */
75 4
    public function __toString()
76
    {
77 4
        if ($this->_basePath) {
78 4
            return $this->_basePath . \DIRECTORY_SEPARATOR . $this->_fileName;
79
        } else {
80 1
            return (string)$this->_fileName;
81
        }
82
    }
83
84
    /**
85
     * セットされたview 変数を配列化して返します
86
     * @return array
87
     */
88 2
    public function toArray()
89
    {
90 2
        return (array)$this->_storage;
91
    }
92
93
    /**
94
     * 配列で一気にview変数をセットします
95
     * @param array|\Traversable $array
96
     */
97 3
    public function assign($array)
98
    {
99 3
        if (!is_array($array) && !($array instanceof \Traversable)) {
100 2
            throw new \InvalidArgumentException('$array must be array or Traversable.');
101
        }
102
103 1
        foreach ($array as $key => $value) {
104 1
            $this->_storage[$key] = $value;
105
        }
106 1
    }
107
108
    /**
109
     * @param string $name
110
     * @param array $array
111
     */
112 1
    public function append($name, $array)
113
    {
114 1
        $this->_merge($name, (array)$array, true);
115 1
    }
116
117
    /**
118
     * @param string $name
119
     * @param array $array
120
     */
121 2
    public function prepend($name, $array)
122
    {
123 2
        $this->_merge($name, (array)$array, false);
124 2
    }
125
126
    /**
127
     * @param string $name
128
     * @param array $array
129
     * @param bool  $append
130
     */
131 3
    private function _merge($name, array $array, $append=true)
132
    {
133 3
        $s = $this->_storage;
134 3
        if (isset($s[$name])) {
135 3
            if ($append) {
136 1
                $s[$name] = array_merge((array)$s[$name], $array);
137
            } else {
138 3
                $s[$name] = array_merge($array, (array)$s[$name]);
139
            }
140
        } else {
141 3
            $s[$name] = $array;
142
        }
143 3
    }
144
145
    /**
146
     * テンプレートファイルを描画して文字列にして返します
147
     * テンプレート内では$EでHTMLエスケープでき、
148
     * <?= $E('<script>') ?> と <?= htmlspecialchars('<script>') ?> が等価です。
149
     *
150
     * @return string
151
     */
152 3
    public function render()
153
    {
154 3
        $E = get_class($this) . '::h';
155 3
        extract((array)$this->_storage, \EXTR_OVERWRITE);
156 3
        ob_start();
157 3
        include (string)$this;
158 3
        $html = ob_get_clean();
159
160 3
        if ($this->_layoutFileName) {
161 2
            $layout = new static(
162 2
                $this->_layoutFileName,
163 2
                $this->_basePath,
164 2
                $this->_storage
165
            );
166 2
            $layout->setContent($html);
167 2
            return $layout->render();
168
        } else {
169 3
            return $html;
170
        }
171
    }
172
173
    /**
174
     * @internal
175
     * @param string $content
176
     */
177 2
    protected function setContent($content)
178
    {
179 2
        $this->_content = $content;
180 2
    }
181
182
    /**
183
     * 子テンプレートの描画結果を文字列として返します
184
     * @return string
185
     */
186 2
    public function content()
187
    {
188 2
        return $this->_content;
189
    }
190
191
    /**
192
     * 親となるレイアウトテンプレートのファイル名を指定します。
193
     * レイアウトは__construct()で指定した基準パスと同じパスから探索します。
194
     * @param string $layoutFileName
195
     */
196 2
    public function setLayout($layoutFileName)
197
    {
198 2
        $this->_layoutFileName = $layoutFileName;
199 2
    }
200
201
    /**
202
     * 現在セットされているレイアウトファイル名を返します
203
     * @return string
204
     */
205 1
    public function getLayout()
206
    {
207 1
        return $this->_layoutFileName;
208
    }
209
210
    /**
211
     * 指定したテンプレートファイルを描画します。
212
     * 変数は引き継がれます。
213
     * @param string $partialFileName
214
     * @return string
215
     */
216 1
    public function partial($partialFileName)
217
    {
218 1
        $partial = new static(
219
            $partialFileName,
220 1
            $this->_basePath,
221 1
            $this->_storage
222
        );
223 1
        return $partial->render();
224
    }
225
226
    /**
227
     * htmlspecialchars()のエイリアスです。
228
     *
229
     * @param string $rawStr 
230
     * @param int $mode
231
     * @param string $charset 文字コードです。指定しなければdefault_charsetの値を使用します。
232
     * @return string
233
     */
234 2
    public static function h($rawStr, $mode=\ENT_QUOTES, $charset=null)
235
    {
236 2
        static $solvedCharset = null;
237 2
        if (!$solvedCharset || !empty($charset)) {
238 1
            $solvedCharset = (string)$charset ?: ini_get('default_charset') ?: 'UTF-8';
239
        }
240 2
        return \htmlspecialchars($rawStr, $mode, $solvedCharset);
241
    }
242
243
    /**
244
     * metaタグ群を出力するためのヘルパー関数です。
245
     * <?= self::meta($meta) ?>
246
     *
247
     * @example
248
     *  <pre>
249
     *   $meta = [
250
     *     'charset' => 'utf8',
251
     *     'http-equiv' => [
252
     *       'X-UA-Compatible' => 'IE=edge',
253
     *       'Content-Type' => 'text/html; charset=UTF-8',
254
     *     ],
255
     *     'name' => [
256
     *       'keyword' => 'hoge,fuga,muga',
257
     *       'description' => 'hogehoge',
258
     *       'twitter:title' => 'Mountain sunset',
259
     *     ],
260
     *     'property' => [
261
     *       'og:title' => 'foooooo',
262
     *       'og:description' => 'hogehoge',
263
     *       'og:image' => [
264
     *         'http://hoge.com/hoge1.jpg',
265
     *         'http://hoge.com/hoge2.jpg',
266
     *       ],
267
     *     ],
268
     *   ];
269
     *  </pre>
270
     *
271
     * @param array|Traversable $meta metaタグの定義。
272
     * @param boolean $isXhtml trueを指定すると、タグがXHTML形式になります。
273
     * @param boolean $escape  falseを指定すると、HTMLエスケープを行わなくなります。
274
     * @return string
275
     */
276 2
    public static function meta($meta, $isXhtml=false, $escape=true)
277
    {
278 2
        if (!is_array($meta) && !$meta instanceof \Traversable) {
279 1
            throw new \InvalidArgumentException('$meta should be Traversable or array. type error: ' . gettype($meta));
280
        }
281
282 1
        $html = array();
283 1
        foreach ($meta as $attr => $defs) {
284 1
            if (is_scalar($defs)) {
285 1
                $html[] = array('meta', $attr => $defs);
286 1
                continue;
287
            }
288
289 1
            foreach ($defs as $label => $val) {
290 1
                if (is_scalar($val)) {
291 1
                    $html[] = array('meta', $attr => $label, 'content' => $val);
292 1
                    continue;
293
                }
294
295 1
                foreach ($val as $v) {
296 1
                    $html[] = array('meta', $attr => $label, 'content' => $v);
297
                }
298
            }
299
        }
300
301 1
        $metatags = array();
302 1
        foreach ($html as $def) {
303 1
            $metatags[] = self::_generateTag($def, $isXhtml, $escape);
304
        }
305
306 1
        sort($metatags, \SORT_STRING);
307 1
        return implode(PHP_EOL, $metatags);
308
    }
309
310 1
    private static function _generateTag(array $def, $isXhtml=false, $escape=true)
311
    {
312 1
        $tag = $def[0];
313 1
        unset($def[0]);
314
315 1
        $html = array();
316 1
        if ($escape) {
317 1
            foreach ($def as $key => $val) {
318 1
                $html[] = $key . '="' . static::h($val, \ENT_COMPAT) . '"';
319
            }
320
        } else {
321 1
            foreach ($def as $key => $val) {
322 1
                $html[] = $key . '="' . $val . '"';
323
            }
324
        }
325
326 1
        return "<$tag " . implode(' ', $html) . ($isXhtml ? ' />' : '>');
327
    }
328
}
329