「ふつうのPHP」がpixivになるまで

公開日:

Download PDF

スライドテキスト

Page 1

「ふつうの 」が

PHP
   になるまで

pixiv.inc
USAMI Kenta @tadsan

2018-07-14 PHPカンファレンス関西 #php

Page 2

お前誰よ

  • うさみけんた

(@tadsan) / Zonu.EXE

  • ピクシブ株式会社 運営本部技術基盤チーム

pixiv

  • 年末から現職、 を実装したりしたよ
    2012 WEB API
  • 今回発表するようなところを見つめてきたよ
  • 現行メンテナ
    Emacs PHP Mode / https://github.com/emacs-php
  • 適当な記事を書いてきたよ
    Qiita: https://qiita.com/tadsan

Page 3

   の紹介

Page 4

Page 5

イラストコミュニケーションサービス

pixiv

  • 年 月 日に開始されたイラスト ・投稿サイト
    2007 9 10 SNS
    • イラスト約 万作品 ベース、画像枚数は 億 万枚以上

: 7000 (ID 1 4000 )

  • ユーザー約 万アカウント ベース

: 3000 (ID )

  • イラストブックマーク約 億回

: 35

  • アクセス数月間 億 ログベース、一般的な意味の とは異なる

: 76 (HTTPd PV )

  • サーバー台数 台 のデプロイ対象になっている のみ

: 53 (pixiv Apache HTTPd )

Page 6

用語

  • 本体
    pixiv www.pixiv.net
    • デスクトップ版いわゆる 向け

( PC )

  • モバイル版いわゆるスマートフォン向け、旧

( touch.pixiv.net)

Page 7

pixiv.git

  • とデータベースを共有するコードは一個のリポジトリに含まれる
    pixiv
  • それ以前はサービス横断の で共有されてたが効率悪かった

git submodule

accounts.pixiv.net pixiv-lib
! !
admin public-api.pixiv.net
! !
app-api.pixiv.net rpc.pixiv.private
! !
batch sensei.pixiv.net
! !
bin source.pixiv.net
! !
bungei-api.pixiv.net spotlight.pics
! !
comic-api.pixiv.net ssl.pixiv.net
! !
embed.pixiv.net tests
! !
fanbox.pixiv.net touch.pixiv.net
! !
m.pixiv.net util
! !
me.pixiv.net www.pixiv.net
! !
oauth.pixiv.net www.pixivision.net
" "

Page 8

https://niconare.nicovideo.jp/watch/kn1259

Page 9

よくある質問

  • フレームワークは使ってないの?
  • 使ってません。マイクロフレームワークのようなのはいくつかある
  • べんりユーティリティの集合で実際フレームワークになってる
  • フレームワークを指向して開発されたものもいくつかある
  • フレームワーク/言語の移行やフルスクラッチしないの ?

  • 直近での予定はありません。過去に痛い目にも遭ってるし

Page 10

   の歴史

Page 11

お断り

数々の変更は が直接行っ

@tadsan
たものではなく、歴代の開発者が 改善を積み重ねてきたものです

むしろ恩恵を受けてきた立場

( )

Page 12

2

2 2

2 2 2

2

2 2

2

0

2

0 0

0

0 0

0

0 0

0

0

0

1 1

0 1 1

1

1 1

1

7

1

1 2

8 6 7

0

3 4

年 年

年 年 年

8

年 年

5

p p p P p p p p ppp
ピ 国 う ス

p p p

P

r

i i i i i i i iii

u

i i i

x x x x x x x xxx

x x x

ク 際 ご マ

i

b

v

i i i i i i i iii

i i i

v v v v v v v vvv

v v v

l

a

シ 版 イ ┃

i

(

t

c

F F

P

W

e

ブ ラ ト

p

p

A A

A

H

i

i

E

A

N N

百 機 フ

x

P

x

P

B

P

i

B B

i

I

5

)

v

v
科 能 ォ

I

O O

.

(

(

5

ル リ

β

β

X X

事 リ ン

)

)

ニ 公 イ

典 リ 版

ュ 開 ル

┃ ド

(

ス メ

t

まだ

o

Web制作会社

u

Pア

c

入社ここ

@tadsan

h

社名

Aル

.

年 月

(2012 11 )

p

変更

i x

サービスの公開 で見る年表

終了

i

( )

v

. n

pixiv以外のサー

e

ビスも増えてく

t )

Page 13

from

React

REST API

Anti REST

Redmine

2 2 2

2

2

2

2
0 0 0

0

0

0

0
1 1 1

1

1

1

1
2 6 7

3

年 年 年

4

8

5

共 正

T C

p

p p p

デ ス

P p G

P J p

P

h

e

o

i

i i i

H

r

i

i

u

i

通 規

x

x x x

プ マ

x

x

t

n

i

r

m

b

P

v

i

i i i

L

i

i

i

k

v

v v v

f

v

v

l

ロ ┃

U

a

p

a

t

i

i

(

t

b

n

o

c

F

n

P

P

W

e イ ト

p

A

s

s

i

H

H

A

t

E

e

i

A

N

刷 フ

P

P

x

P

B

r

P

B

i

5

7

I

の A

自 ┃

)

新 ォ

v

I

O

.

.

(

5

1

テ リ

C P
開 動 ス

X

β

Symfony

発 化 化

)

ィ ニ

I I

発 ロ

Lime

導 開

グ ュ

社内

(

独立リポ

t

導 ア

フレームワーク

o

最高便利

ジトリを合体

u

(PHPCon2013)

入 ル

P ア

c h

ActiveResource

A ル

アトミック

規則的

PEAR依存廃止

. p

(Rails)

URL生成

i x

@tadsan

使用技術の変遷で見る年表

i v

が血走って

.

n

ただし 入社前後に限る

フレームワーク作る

e

tadsan

include

t )

地獄

Page 14

お断り

今回の発表で言及されないもの は概ね時間の制約で取捨選択さ れたか忘れてるだけなので、懇 親会や で にきいて

Twitter @tadsan

Page 15

ふつうの

PHP #

とは

Page 16

pixiv PHP

  • は 一時 を使ってた箇所あり
    PHP Apache+mod_php ( fpm )
    • フロントの から、リバースプロキシして にリクエスト

nginx Apache

  • むかしながらの にある ファイル=

DocumentRoot .php URL


  • : https://www.pixiv.net/member.php?id=105589
  • とか とか、よくある のスタックに載ってる
    Linux MySQL LAMP

Page 17

年のコードのイメージ

2012

<?php // www.pixiv.net/htdocs/hoge.php
require_once __DIR__ . '/../inc/bootstrap.php';

include_onceいっぱい

include_once INC_PATH . '/Hoge/Fuga.php';
include_once INC_PATH . '/Hoge/Piyo.class.php';
try {
display()

エラーハンドリング

} catch (Exception $e) {

(してないページもあった)

error::exception_error($e);
}

この下にもいっぱい

function display() {

ファイルローカルな

// ...

グローバル関数

Page 18

年のコードのイメージ

2015

<?php // www.pixiv.net/htdocs/hoge.php
require_once __DIR__ . '/../inc/bootstrap.php';

include_once一個だけ

AppRunner::execute(new Www_HogeController);

このクラスは

自動ロードされる

Page 19

名前の由来は知らない

AppRunner ( )

<?php

final class PCAppRunner {
public static function execute(Controller $controller) {
try {

whoops

Controller_Util::turnOnWhoops();
Controller_Util::redirectToHttps();

パソコン版

$controller->main();
} catch (\Throwable $exception) {
PCAppRunner::setHttpStatus(500);
Controller_Util::displayWhoops($exception);

Page 20

とエラーハンドリング

AppRunner

  • サービスごとの例外ハンドリングは が受け持つ

AppRunner

  • はエラー処理用のフレームワークなので、これだけを使って

whoops!
例外/エラー処理を完結させることも可能ではあるが、割と複雑になるの
で、開発環境でエラー画面を表示するだけの目的に専念してる

  • エラーログは の標準機能は利用せず でログファイル

PHP file_put_contents()

に書き込んでる

  • とエラーログの話は に書きました
    whoops! WEB+DB PRESS Vol.96

Page 21

年のコードのイメージ

2018

final class Controller_WwwRoutes {

htdocs/hoge.phpは消滅

public static function getRoutes() {
$route_map = [
'/' => [
'action' => function () {

URLとファイルは切り離され 

Www_IndexController::main();

マップの一要素に

},
],
'/hoge.php' => [
'action' => function () {

全URLが1ファイルに

Www_HogeController::main();
},

Page 22

カンファレンス で発表

PHP 2017

Page 23

命名規則

  • クラス名は 的な を使った擬似名前空間

PSR-0 _

  • 組織内で一貫してれば に拘る必要がない

PSR

  • 「 の誤解」
    PSR https://qiita.com/tadsan/items/942a381e952e12a8fa5a
  • 基本は で、クラスはインスタンス化せずに利用

static

  • クラスを擬似的な関数・定数置き場にしている
  • どうしてこんなことを続けてるのかは去年の で話した

LT

Page 24

去年の当日募集 で話した

LT

Page 25

開発しやすさの

ための取り組み

Page 26

で開発しにくいところ

PHP

  • クエリパラメータのハンドリング
  • の機能の貧弱さ
    PDO
  • テンプレートエンジンと の問題

URL

  • にあるようなカッコイイ機能がない
    Rails
    • かっこいいエラー表示
  • 対話環境

(rails console)

Page 27

クエリパラメータの問題

$id = $_GET['user_id'];
if (is_numeric($id)) User_Common::getById($id);

不正な入力です

else error(" ");

  • こういうコードを書いてはいけない
  • クエリパラメータは数字ではない値が入ってくる可能性
  • 入力が空、入力が任意の文字列、入力が不正な数字列、入力が配列
  • をかけると安全にはなるが、それでも面倒
    filter_input()

Page 28


ParamHelper

$id = ParamHelper::getPositiveInt('id');
User_Common::getById($id);

  • クエリパラメータまたは から値を取り出すヘルパー

( form)

  • 未入力や不正な入力があると例外を投げる
  • 前述の でキャッチしてエラー画面を描画する

AppRunner

Page 29


ParamHelper

$mode = ParamHelper::getEnum('mode', ['hoge', 'fuga'], ['post' => 'only']);

  • または のみを期待するようなパターン
    ?mode=hoge ?mode=fuga
  • 不正な形式や のような期待しない入力で例外を投げる

?mode=piyo

Page 30

テンプレートと 生成の問題

URL

<a href="{$smarty.const.SYSTEM_URL_WWW|escape}member_illust.php?id={$user.id|
escape}">{$user.name|escape}</a>

  • こういうコードを書くと事故が入り込みやすい
  • 純粋に の危険性

typo

  • などのパラメータに不正な値を入れるリスク
    id

Page 31


ReverseRoute

<a href="{reverse_route page='fullWwwMemberProfile' id=$user.id}">

{$user.name|escape}</a>

  • あるページに名前をつけて、 関数に 引数で渡す

reverse_route page

  • 生成結果は変更前と同じ
  • ルーティングの逆関数にあたるので、一部のフレームワークは

や ヘルパーなどの名前でサポートしてる

ReverseRouting URL

Page 32


ReverseRoute

/**
* @route\example https://www.pixiv.net/member.php?id=12345 {id: 12345}
* @route\example https://www.pixiv.net/member.php?id=12345&utm_source=xxxxx
{id: 12345, utm_source: "xxxxx"}
*/
public static function fullWwwMemberProfile(array $params)
{
Util_Assert::num($params['id']);

return ReverseRoute::buildUrl(SYSTEM_URL_WWW, '/member.php', ['id'],
$params);
}

Page 33

に書きました

blog

https://devpixiv.hatenablog.com/entry/2016/10/25/093000

Page 34

の問題

PDO

  • は自動生成じゃなくて手で書きたい

SQL …
でも で複雑なクエリを書こうとすると文字列結合が避けられない

PDO …

  • で書きにくいクエリの代表例
    PDO : IN
  • もう を並べてインデックスをインクリメントしながら はやだお

? bindValue

  • のクエリの に を書くか で書くか問題
    PDO placeholder ? :hoge
  • 型チェックできるように書くのもちょっとめんどう

Page 35

PxvSql

Page 36

PxvSql

  • 文字列結合を一切やらなくてもクエリが書けるようになった
  • 実行時に型検査をするので などの不穏な値は入り込まない

null

  • エラーチェックのボイラープレートがグッと減る
  • 脆弱性のリスクなしただし の実装の不備はないと信じる
    SQLi( ) ( mysqlnd )
  • で整数の埋め込み、 で複数の整数を展開する
    :hoge@int :fuga_ids@int[]
  • や の記法で条件分岐や複数 なども可能
    %if %for SET
  • 問題意識は に書いた

Qiita https://qiita.com/tadsan/items/e615a779baa6eabdab47

Page 37

のラッパー

PDO

  • これは、少なくとも 年以前からあった仕組み

2011

  • 書き込み用 と読み込み用 の系統を明示的に分けて利用可能

(master) (slave)

  • に や などのクエリを発行しようとすると例外
    slave INSERT UPDATE
  • 全クエリに のクエリ単一行にして、トレースをコメントにして埋め込む

SQL

  • スロークエリのログにどのコントローラに起因するか集計しやすい
  • スロークエリを一覧できるビューアーもある

Page 38

新機能をリリース時にどうする?

  • 新機能が実装されたブランチをリリースするときマージする
    A:
  • リリース前にあらかじめマージして、実行されないようにしておく
    B:
  • よくある問題
  • は機能の規模が大きいとビッグバンマージを引き起こし、

A
予期せぬ問題を引き起こすことがある

  • 理想としては で、一般ユーザー向けに実行されないとしても

B

鮮度のよいうちにマージしておきたいリリース時の変更を最小限に

( )

Page 39

テスト/機能の有効化/無効化

A/B

if ($_SERVER['REMOTE_ADDR'] === OFFICE_IP) {
hogehoge();
}

  • 新しい機能をサービスに実装して、社内でだけ有効化したいとする。
  • 無造作にこんなコードを書かれると後から意味不明になる
  • そもそも社外と社内での挙動が別物になるので嫌な予感しかしない

Page 40

テストフレームワーク

DevToggle (A/B )

if (ABTest_DevToggle::isEnabledDevToggle('hogeFunc')) {
hogehoge();
}

  • 専用のコンソール画面でボタンを押すことで有効化/無効化される
  • 設定を一行足すだけで一般ユーザーに対してリリースできる
  • リリース後に障害などが発生しないことが確認できたら を消す

if

  • 元は テストのための機構だった一般ユーザーに対して確率で適用

A/B ( )

Page 41

まとめ

Page 42

何もない野原に秩序をつくる

  • べんり機能を導入すると、部分的に生産性が体感で数倍になったりする
  • 一からサービスを作るとしたら同じことを繰り返せるかは、悩む
  • いまだったら を入れるかもしれない

Laravel

  • ただ、それは 年のチート未来人だからできる発言

2018

  • サービスの価値はコードではなく、その結果のユーザー体験
  • フレームワークがなかったとしても、

が 年からユーザーに価値を提供してきた結果は変らない

pixiv 2007

Page 43

続きは で

Web

あと

( WEB+DB PRESS)

  • ニコナレ

https://niconare.nicovideo.jp/users/5962


  • Real World PHP in pixiv
  • pixiv inside
    • で のユニットテストの書きやすさを劇的に改善する手法
      DocComment PHP
    • 連載『 大規模開発入門』を振り返る
      WEB+DB PRESS PHP
    • の基盤ノウハウ大公開! カンファレンス 登壇レポート
      pixiv PHP 2017

Page 44

続きは で

Web

あと

( WEB+DB PRESS)

  • Qiita
    • 憂鬱な のためのアレ、または と仲良くして枕を高くしてねむる

SQL PDO

  • の誤解
    PSR
  • インスパイヤされて掲示板を作りたくなったシリーズ
  • って書きたくない僕たちのためのオートローディングと
    include Composer
  • GitHub
    • やエラー処理の例
      whoops https://github.com/zonuexe/wdb-php-96-sample

Page 45

続きは で

Web

あと

( WEB+DB PRESS)

  • これ一冊を購入すれば歴代の 連載が全部

PHP

読めるのでおすすめ

  • まとめ

https://inside.pixiv.blog/tadsan/3991

  • 今回の内容に近いものだと
  • vol.81, vol.87, vol.91, vol.94, vol. 96
  • 買って読んでね! にサンプルコードもあるよ

Web