ふとした気づき
今時CGIなんて言わない?知らない?そんなこたぁいいんですよ。
ふと思ったんですよ、CGIとCLIアプリって本質的に同じものなんじゃないかなって。
だってPHPってこんなふうに書いてブラウザでアクセスしたらいろはにほへとって出るじゃないですか?
そしてこれをphp index.phpみたいにして叩いてもいろはにほへとって出ますよね?
<?php
echo 'いろはにほへと';
そう、気付いてしまったのです。
実はCGIって本質的にはCLIアプリなのではないかと…。
むしろ何が違うんでしょう?
仕組みを調べてみることに
気になったのでどういう仕組で動いてるのか軽く調べるために、敢えてC言語でCGIを作ってみることに。
今までC言語でCGIとか狂人のやることって印象があったのですが、意外とやってみると大したことはないというか、まぁ大したものを作ってないから当たり前なんですが…。
これが何をしているかというと、環境変数を取ってきて標準出力に吐いてるだけなので、どっからどう見てもCLIアプリです。
#include <stdio.h>
// getenv
#include <stdlib.h>
// strcmp
#include <string.h>
/*
get path for after hostname
*/
char* getPath(void) {
char *path = getenv("REQUEST_URI");
return path;
}
int main(void) {
// put http header
printf("Content-type: text/plain\n\n");
char *path = getPath();
// show current path
printf("Current path: %s\n", path);
// routing, required .htaccess to serve on apache
if (strcmp(path, "/foo") == 0) {
printf("hoge");
} else if (strcmp(path, "/bar") == 0) {
printf("piyo");
} else {
printf("fuga");
}
return 0;
}
実際に動かしてみたところ
CGIとして
CLI として
辿り着いた結論
恐らくこれは動作環境が重要なのであって、多分CGIとCLIアプリの作りとしては大きな違いはないのではないか?というのが辿り着いた結論です。
いやまぁ引数をargvから取るか環境変数から取るかは結構違う気もしますが、基本的に表示したいものを標準出力に吐く点は同じですし、別にCGIでTCPソケットを触るわけでもないので、同じようなものでは?と思った次第。
これは多分、 HTTPサーバーを通すことで標準出力がHTTPレスポンスに出力され、 Xサーバーを通すなら画面に、プリンタサーバーを通せば紙にといった具合に、どこのサーバーを通して標準出力をするかで出てくるところが変わるのでは?と感じました。(細かいことを言うともっと複雑らしい)
多分ApacheとかのHTTPサーバーはTCPソケットをいい感じにやってくれて、 HTTPヘッダを環境変数に入れてCGIスクリプトを起動といったところをしてくれているのではないかと思ったので、暇な時にTCP通信から始めるHTTPサーバーの実装でもやってみたいところですね…。
おまけ
取り敢えずPHPのCGIをCLIとして動かせるやつです。
<?php
echo $_SERVER['REQUEST_URI'];
画像マーカーを使ったリストタグを使った時に文字列がvertical-align: middle的になってほしい!
が結論から言うと多分無理です
なってくれないんだなこれが!
- 残念ながら現実にはなりません
- 上の画像のソースはこんなん
::beforeのcontentに画像を置くとかしても無駄です
<ul>
<li>リストの要素だよ~</li>
<li>改行のある<br>リストの要素だよ~</li>
</ul>
ul {
list-style-image: url("https://i.imgur.com/hX2OCbY.png");
}
li {
vertical-align: middle;
}
どうにか対策してみる
- 一見するとなんとかなったように見えますが、改行すると崩れます
- ぶっちゃけ無理にリストタグ使う必要もないと思うので、素直に div とかで代替するのが一番だと思います
- 何が何でもリストタグ使いたい場合はリストタグでラップしてしまうのが一番でしょうね…
- ぶっちゃけ無理にリストタグ使う必要もないと思うので、素直に div とかで代替するのが一番だと思います
- 上の画像のソースはこんなん
- リストのマーカーを使うことを放棄しているので、画像を抜けると字が落ちるのはどうしようもないですね
<ul>
<li>リストの要素だよ~</li>
<li>改行のある<br>リストの要素だよ~</li>
</ul>
.ul {
list-style: none;
}
.li::before {
padding-right: 3px;
min-height: 36px;
content: url("https://i.imgur.com/hX2OCbY.png");
vertical-align: middle;
}
react-routerでRouteを切る実装例
確認環境
| Env | Ver |
|---|---|
| React | 17.0.1 |
| react-router-dom | 5.2.0 |
| TypeScript | 4.1.3 |
サンプルコード
index.tsx
- ここに大元のルーティングを実装
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import { BrowserRouter } from 'react-router-dom';
import { RootRoute } from './routes/RootRoute';
// ドメインルート以外にReactを設置する場合に必要
// e.g. `https://www.example.com/react/` に設置する場合 `/react` を設定
const basename = process.env.REACT_APP_BASE_NAME ?? undefined;
ReactDOM.render(
<React.StrictMode>
{/* `useHistory()` 用のDIラッパー */}
<BrowserRouter basename={basename}>
{/* ルーティング設定をまとめたコンポーネント */}
<RootRoute />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
AppRoute.ts
- ルーティングの定義、これがあると後々の管理が楽
- スプレッド演算子で
Routeに対して展開できるように記述しておく
export const AppRoute = {
// React のルート定義
root: {
path: '/',
key: '/',
exact: true, // 完全一致
component: App,
},
// 404 定義
notfound: {
component: NotFound,
},
// `/foo` 以下のルーティング定義
foo: {
// `/foo` のルート定義
root: {
path: '/foo',
key: '/foo',
component: FooRoute,
},
// 以下、 `/foo` 配下のページのルーティング定義
hoge: {
path: '/foo/hoge',
key: '/foo/hoge',
component: HogePage,
},
piyo: {
path: '/foo/piyo',
key: '/foo/piyo',
component: PiyoPage,
},
},
};
RootRoute.tsx
- 各階層の基底ルーティング管理
export const RootRoute = () => {
return (
{/* `Route` は `Switch` で囲む */}
<Switch>
{/* root */}
<Route {...AppRoute.root} />
{/* foo */}
<Route {...AppRoute.foo.root} />
{/* bar */}
<Route {...AppRoute.bar.root} />
{/* 404 */}
<Route {...AppRoute.notfound} />
</Switch>
);
};
FooRoute.tsx
/foo階層配下の管理用
export const FooRoute = () => {
return (
{/*
`Context` は `Switch` の外に出す
`Switch` の中に `Context` を書くと更に `Switch` でネストする必要がある
*/}
<FooContext.Provider value={FooContextDefault}>
<Switch>
{/* eslint-disable-next-line array-callback-return */}
{Object.entries(AppRoute.foo).map(([idx, route]) => {
if (idx !== 'root') {
/* `'root'` 以外の全ノードを展開 */
return <Route {...route} />;
}
})}
{/* 404 */}
<Route {...AppRoute.notfound} />
</Switch>
</FooContext.Provider>
);
};
PHPでClassをrequireせずに使えるやつ
Laravelのrequest()とかがどうやって呼ばれてるのかを調べていくうちに辿り着いたのでメモがてら
Classは簡単に読み込めるけど、Functionは一筋縄ではいかなさそう
ついでにClassに好き勝手プロパティ増やせることも発見した
autoloaderを実装
spl_autoload_registerを使う。Laravelはcomposerが上手いことやってくれてるっぽかった
<?
function regist() {
spl_autoload_register(function() {
require './Hoge.php';
require './Piyo.php';
});
}
使いたいクラス
適当に用意
<?
class Hoge {
public $hoge;
}
<?
class Piyo {
public $piyo;
}
autoloaderを呼んで使う
ついでに好き勝手にプロパティも生やす
<?
require './bootstrap.php';
// ここでクラスをautoloadする
regist();
// どこからも呼んでないけど
$h = new Hoge;
// 生やせる
$h->fuga = "aaa";
$h->hogepiyo = "bbb";
// 使える
$p = new Piyo;
$p->fuga = "aaa";
$p->hogepiyo = "bbb";
var_dump($h, $p);
/*
結果
class Hoge#2 (3) {
public $hoge =>
NULL
public $fuga =>
string(3) "aaa"
public $hogepiyo =>
string(3) "bbb"
}
class Piyo#3 (3) {
public $piyo =>
NULL
public $fuga =>
string(3) "aaa"
public $hogepiyo =>
string(3) "bbb"
}
*/
手順
composer initでプロジェクトを作成composer require [パッケージ名]でパッケージを入れる- 名前空間を作る場合、
composer.jspnに以下のセクションを作成- リファレンス: getcomposer.org
"autoload": {
"psr-4": {
"App\\": "src/App" // 名前空間と対応するパス
},
"classmap": [
"src/App" // クラスを読み込むディレクトリのルート
]
}
autoloadやclass_aliasなどを設定するbootstrap.phpを適当に作成- この
bootstrap.phpはエントリポイントからrequire_onceなどで読み込無事で有効化する
- この
phpunitを組み込む場合composer require --dev phpunit/phpunitでインストールcomposer.jsonに以下のセクションを作成
"scripts": {
"test": [
"phpunit --bootstrap bootstrap.php test"
]
}




