Skip to content

Instantly share code, notes, and snippets.

@ngyuki
Last active August 29, 2015 14:05
Show Gist options
  • Save ngyuki/e0b694a674574b320fcc to your computer and use it in GitHub Desktop.
Save ngyuki/e0b694a674574b320fcc to your computer and use it in GitHub Desktop.
PhpRenderer slide
PhpRenderer slide.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/css/reveal.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/css/theme/default.css" id="theme">
<!-- For syntax highlighting -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/css/zenburn.css">
<!-- If the query includes 'print-pdf', include the PDF print sheet -->
<script>
if( window.location.search.match( /print-pdf/gi ) ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '//cdn.jsdelivr.net/reveal.js/2.6.2/css/print/pdf.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
</script>
<!--[if lt IE 9]>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section data-markdown="index.md"
data-separator="^\n---$"
data-vertical="^\n>>>$"
data-charset="utf-8">
</section>
</div>
</div>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/js/head.min.js"></script>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/js/reveal.min.js"></script>
<script>
var cdn = "//cdn.jsdelivr.net/reveal.js/2.6.2/";
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
slideNumber: true,
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
// Parallax scrolling
// parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
// parallaxBackgroundSize: '2100px 900px',
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: cdn + 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: cdn + 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: cdn + 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: cdn + 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
</script>
</body>
</html>

PHP の新しいテンプレートエンジンのご提案


PHP のテンプレートエンジン

  • Smarty
  • Twig
  • etc...

問題点

  • テンプレートエンジン特有の構文を覚えなければならない
  • IDE がシンタックスハイライトなどに対応していないことがある

例. Smarty

<h1>Users</h1>
<ul>
    {foreach from=$users item=user}
        <li>{$user.username}</li>
    {/foreach}
</ul>

例. Twig

<h1>Users</h1>
<ul>
    {% for user in users %}
        <li>{{ user.username }}</li>
    {% endfor %}
</ul>

新しいテンプレートエンジンの特徴

  • テンプレートエンジン特有の構文を覚える必要がない
  • たいていの IDE がシンタックスハイライトなどに対応している

例. Syntax

<h1>Users</h1>
<ul>
    <?php foreach ($users as $user): ?>
        <li><?= $user['username'] ?></li>
    <?php endforeach; ?>
</ul>

とか

<h1>Users</h1>
<ul>
    <?php foreach ($users as $user) { ?>
        <li><?= $user['username'] ?></li>
    <?php } ?>
</ul>

PHP とまったく同じ

テンプレートエンジン特有の構文を覚える必要がない


例. Highlight

for PhpStorm

  • ハイライトどころかアウトライン解析までできている
  • もちろん Eclipse にも対応
  • とうぜん NetBeans にも対応 使ったこと無いけど

むしろ対応していないなら PHP の IDE を名乗るべきではない


IDE が対応するのではない、対応しているものが IDE なのだ


例. IntelliSense

テンプレートにアサインする変数がオブジェクトなら

ちょっとしたおまじないで入力補完にまで対応

<?php /* @var $users User[] */ ?>

<h1>Users</h1>
<ul>
    <?php foreach ($users as $user): ?>
        <li><?= $user->getUsername() ?></li>
    <?php endforeach; ?>
</ul>

img02.png


このステキなテンプレートエンジンの名前は


PHP: Hypertext Preprocessor


← ここまで茶番 | ここから本題 →


素の PHP がテンプレートとして使われることは多い

  • Zend Framework
  • CakePHP
  • FuelPHP
  • etc...

例えばこんなの

index.php

<?php
$name = filter_input(INPUT_GET, 'name');
include __DIR__ . '/index.html.php';

index.html.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
< body>
Hello <?= $name ?>
< /body>
</html>

ここ、いわゆる XSS です

Hello <?= $name ?>

エスケープが必要

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
< body>
Hello <?= htmlspecialchars($name, ENT_QUOTES) ?>
</ body>
</html>

さすがに毎度こんなのを書いてたらめんどくさくて死ぬ

<?= htmlspecialchars($name, ENT_QUOTES) ?>

のでフレームワークによって別の書き方が提供されています


Zend Framework

<?= $this->escapeHtml($name) ?>
  • ビューヘルパーと呼ばれるもの
  • あんまり楽になった気がしない

CakePHP

<?= h($name) ?>
  • h というグローバル関数が定義されている
  • basics.php を見ると他にもいろいろ定義されている

FuelPHP

<?= $name ?>
  • あらかじめエスケープされた変数がアサインされる
  • ただしオブジェクトをアサインするときに困りそう

Smarty や Twig

  • Smarty2 の自動エスケープはクソ
    • 出力時ではなく参照時にエスケープされるから
  • Smarty3 なら良い感じに自動でエスケープしてくれる
  • Twig も自動でエスケープしてくれる

...

素の PHP でも同じことがしたい


やってみました


index.php

<?php
require __DIR__ . '/../vendor/autoload.php';

use PhpRenderer\Renderer;

$data = array(
    'name' => "<script>alert('!!!')< /script>",
    'html' => "<strong>oreore</strong>",
);

echo (new Renderer)->render(__DIR__ . '/index.html.php', $data);

index.html.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
< body>
Hello <?= $name ?>.
<br>
None Escape <?php echo $html ?>
< /body>
</html>

説明

  • <?= $name ?> のようなショートタグ
    • 自動エスケープ
  • <?php echo $html ?> のような普通の PHP タグ
    • エスケープなし

仕組み

  • StreamWrapper
  • Tokenizer

1. 独自のストリームラッパー

<?php
stream_wrapper_register('oreore', 'OreOreClass');

oreore:path/to/file

のようなストリームラッパーが使えるようになります。


OreOreClass には "path/to/file" が渡されるので、

そのファイルを開いて独自の処理ができます。

2. Tokenizer で字句解析

$tokens = token_get_all($source);

開いたファイルを PHP のソースコードとして、

トークン(字句)に分割します。

3. 一部のトークンを置換

<?=  T_OPEN_TAG_WITH_ECHO
 ?>  T_CLOSE_TAG

この2つのトークンを見つけて、

<?= ... ?>

を、

<?=func( ... )?>

のように置換します。



4. インクルード

include 'oreore://index.html.php'

のようにインクルードすると、

<?= ... ?>

を置換したソースコードがインクルードされます。


問題点


  • index.html.php には OPCache は効かない
    • file:phar: しか対応しないようにハードコードされてる
    • 置換後の opcode がキャッシュされると思ってた・・・

  • index.html.php にブレークポイントを仕込んでも無駄
    • デバッガが止まりません

おわり

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment