LaravelのRequestオブジェクトの動きを追う
PHPフレームワークのLaravelでは、Requestオブジェクトを通じて、リクエスト情報を簡単に取得できる。
PHPならばスーパーグローバル変数である$_POST[$key]とかで逐一取得するところも、LaravelだとRequestオブジェクトのインスタンス$requestから、$request->input($key)
とかで取得できる。
この流れについて、Laravel内部ではどのような動きをしているのか、ということについて追ってみる。
Requestオブジェクト取得
ここでは、フォームから送信された値を取得するケースを考える。
PHPだと、フォームから送信されたvalue値をそれぞれ$_POST[$key]で取得するケースである。
フォームからの送信値が、name属性としてname
というkey名を持っていたとすると、Laravelでは下記のような記述で取得することができる。
public function store(ValidateRequest $request) { inputName = $request->input('name'); }
今回は、フォームリクエストValidateRequest
から値を取得している。インスタンス化に際しては、サービスコンテナで自動的に解決される。
フォームリクエストの記述を追う
フォームリクエストは、Illuminate\Foundation\Http\FormRequest
を継承しているので、次にこちらのソースコードを追ってみる。
$request->input()で値を取得しているので、input()
メソッドが存在しないか探してみるが、ここには存在していない。
Illuminate\Foundation\Http\FormRequest
は、Illuminate\Http\Request
を継承しているので、次にこちらのソースコードを追ってみる。
Illuminate\Http\Requestを追う
しかし、このファイル内にもinput()
が存在しない、、
しかし、Requestクラス内で、Concerns\InteractsWithInput
traitをuseしており、名前的にここにありそうだと踏む。
そこで次に、Concerns\InteractsWithInput
のソースコードを追ってみる。
Concerns\InteractsWithInputを追う。
同ファイルの206行目、ついにそれらしき記述を発見した。
public function input($key = null, $default = null) { return data_get( $this->getInputSource()->all() + $this->query->all(), $key, $default ); }
上記メソッドの説明を見ると、Retrieve an input item from the request.
とある。中身を見ると、data_get()
を用いているが、これはLaravelのヘルパ関数である。
function data_get($target, $key, $default = null) { if (is_null($key)) { return $target; } $key = is_array($key) ? $key : explode('.', $key); while (! is_null($segment = array_shift($key))) { if ($segment === '*') { if ($target instanceof Collection) { $target = $target->all(); } elseif (! is_array($target)) { return value($default); } $result = []; foreach ($target as $item) { $result[] = data_get($item, $key); } return in_array('*', $key) ? Arr::collapse($result) : $result; } if (Arr::accessible($target) && Arr::exists($target, $segment)) { $target = $target[$segment]; } elseif (is_object($target) && isset($target->{$segment})) { $target = $target->{$segment}; } else { return value($default); } } return $target; }
input()の中身をより詳しくみると、$this->getInputSource()->all() + $this->query->all()
の部分は対象の配列であり、配列に存在する$key名のvaluw値を返却している。
$key名として渡されるのは、ここではフォームのname属性である。ということは、$this->getInputSource()->all() + $this->query->all()
の部分に、$_POSTのような連想配列が渡されていると考えられる。
そこで、次にこの部分を追ってみる。
$this->getInputSource()->all() + $this->query->all()を追う
まずは、getInputSource()が何かを追う。おそらくこの部分で、配列の実体が返却されることが想定される。getInputSource()
がどこにあるのかというと、Illuminate/Http/Request
に定義されている。
protected function getInputSource() { if ($this->isJson()) { return $this->json(); } return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request; }
説明をみると、Get the input source for the request.
とある。リクエスト情報を取得していることが予想できる。
関数の中身を詳しくみてみる。内部は3項演算子で構成されていて、条件式はin_array($this->getRealMethod(), ['GET', 'HEAD'])
である。決め打ちになってしまうが、HTTPリクエストのメソッドを取得しているものだと考えられる。そして、HTTPメソッドがGET, HEAD
のどちらかであれば、$this->query
を返却し、それ以外であれば$this->request
を返却していることが分かる。
今回の場合はPOSTメソッドなので、$this->request
が配列の実体だろうと考えられる。
$this->requestを追う
$this->requestだが、クラスプロパティとしてはSymfony\Component\HttpFoundation\Request
に定義されているので、こちらを追ってみる。
/** * Request body parameters ($_POST). * * @var \Symfony\Component\HttpFoundation\ParameterBag */ public $request;
上記が、$requestのプロパティに該当する。$_POSTのパラメータであることも明記されており、上記で扱っていることが分かる。
中身がどこで渡されているのかだが、まずは__construct()に着目する。
/** * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data */ public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); }
配列の引数として、$requestが渡されているが、実際はinitialize()の引数に渡っている。
/** * Sets the parameters for this request. * * This method also re-initializes all properties. * * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param string|resource|null $content The raw body data */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->request = new ParameterBag($request); $this->query = new ParameterBag($query); $this->attributes = new ParameterBag($attributes); $this->cookies = new ParameterBag($cookies); $this->files = new FileBag($files); $this->server = new ServerBag($server); $this->headers = new HeaderBag($this->server->getHeaders()); $this->content = $content; $this->languages = null; $this->charsets = null; $this->encodings = null; $this->acceptableContentTypes = null; $this->pathInfo = null; $this->requestUri = null; $this->baseUrl = null; $this->basePath = null; $this->method = null; $this->format = null; }
上記関数の$this->request = new ParameterBag($request);
にて、中身が渡されているようだ。左記でインスタンス化されている\Symfony\Component\HttpFoundation\ParameterBag
は、パラメータを総合的に扱っているファイルのように見受けられる。
ライフサイクル的アプローチにより、リクエストを探る
次に観点を変えて、Laravelのライフサイクル的アプローチによって、どこでリクエスト情報が取得されているのかを調査する。
Laravelのライフサイクルだが、周知の通り、エントリポイントであるpublic/index.php
が処理のすべての入り口である。
<?php require __DIR__.'/../vendor/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); $kernel->terminate($request, $response);
上記が、public/index.php
の中身である。(コメントは省略してある。)
この中で、リクエスト情報に関連するのは4行目(空欄を含めると9行目)以降である。
$response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
上記の、Illuminate\Http\Request::capture()
によってRequestが生成され、引数としてhandle()に渡されている。
handle()によって引数のリクエスト情報がルーティングに渡され、リクエスト情報に含まれるパスが判定され、定義したコントローラーのメソッドに処理が移るという流れである。
ということで、Illuminate\Http\Request::capture()
でリクエスト情報を取得している実装を追ってみる。
Illuminate\Http\Request::capture()を追う
実際にIlluminate\Http\Request
に定義されたcapture()
のソースコードを見ると、下記のような記述である。
/** * Create a new Illuminate HTTP request from server variables. * * @return static */ public static function capture() { static::enableHttpMethodParameterOverride(); return static::createFromBase(SymfonyRequest::createFromGlobals()); }
return static::createFromBase(SymfonyRequest::createFromGlobals());
だが、引数にもメソッドcreateFromGlobals()
が呼ばれている。
まずは、上記メソッドを追ってみる。
createFromGlobals()を追う
createFromGlobals()は、Symfony\Component\HttpFoundation\Request
に静的メソッドとして、下記のように定義されている。
/** * Creates a new request with values from PHP's super globals. * * @return static */ public static function createFromGlobals() { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); $request->request = new ParameterBag($data); } return $request; }
記述をみると分かるように、createRequestFromFactory()の引数として、スーパーグローバル変数(関心のある$_POST
含め)が渡されている。
コメントを見ても、Creates a new request with values from PHP's super globals.
とあり、この関数において、スーパーグローバル変数に格納されたリクエスト情報を受け取っていることが分かった。
HTTPリクエスト情報がどこに格納されるのか、ようやく姿を現した次第である。
引数として渡されたスーパーグローバル変数が、どのようにしてRequestオブジェクトに変換されるのかをさらに追いたいため、引き続きcreateRequestFromFactory()
のソースコードを調査する。
createRequestFromFactory()を追う
こちらの関数も、同じくSymfony\Component\HttpFoundation\Request
に静的メソッドとして定義されている。
記述としては、下記である。
private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); if (!$request instanceof self) { throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); } return $request; } return new static($query, $request, $attributes, $cookies, $files, $server, $content); }
第2引数のarray $request = []
が、スーパーグローバル変数の$_POSTに該当する。
冒頭のself::$requestFactory
だが、これは同ファイル内の下記に詳しく記述されている。
/** * Sets a callable able to create a Request instance. * * This is mainly useful when you need to override the Request class * to keep BC with an existing system. It should not be used for any * other purpose. * * @param callable|null $callable A PHP callable */ public static function setFactory($callable) { self::$requestFactory = $callable; }
This is mainly useful when you need to override the Request class * to keep BC with an existing system.
記述、コメントを見ると、Requestオブジェクト生成ロジックを拡張したい時に、上記メソッドによってクラスを差し替えるようである。今回は拡張予定はないので一旦スルー。
さて、createRequestFromFactory()の最後に、重要な記述がある。
return new static($query, $request, $attributes, $cookies, $files, $server, $content);
staticなので、自分自身(ここではSymfony\Component\HttpFoundation\Request
クラス)のインスタンスを新たに生成している。そして、引数として渡されている$requestは、スーパーグローバル変数の$_POSTである。ここでもう一度、Symfony\Component\HttpFoundation\Request
クラスの__construct()に戻ってみよう。
public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); } public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $this->request = new ParameterBag($request); $this->query = new ParameterBag($query); $this->attributes = new ParameterBag($attributes); $this->cookies = new ParameterBag($cookies); $this->files = new FileBag($files); $this->server = new ServerBag($server); $this->headers = new HeaderBag($this->server->getHeaders()); $this->content = $content; $this->languages = null; $this->charsets = null; $this->encodings = null; $this->acceptableContentTypes = null; $this->pathInfo = null; $this->requestUri = null; $this->baseUrl = null; $this->basePath = null; $this->method = null; $this->format = null; }
なるほど。一度見た記述だが、上記の引数として渡される$request・ひいてはSymfony\Component\HttpFoundation\Request
クラスの$requestプロパティの実体は、スーパーグローバル変数の$_POSTであることが分かった。
createFromGlobals()の調査に戻る
少し話が逸れてしまったので、再びcreateFromGlobals()の調査に戻る。
分かったこととしては、下記の$requestには、結局Symfony\Component\HttpFoundation\Request
クラスのインスタンスが返却され、プロパティの$requestにはにはスーパーグローバル変数の$_POSTを保有している、ということだ。
public static function createFromGlobals() { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); $request->request = new ParameterBag($data); } return $request; }
$request以下の記述では、リクエストヘッダ情報を取得し(すでにスーパーグローバル変数として渡されている)、CONTENT_TYPE: application/x-www-form-urlencoded
だった場合にエンコーディング処理を施すなどしている。
parse_str($request->getContent(), $data);
https://www.php.net/manual/ja/function.parse-str.php
ちなみに、リクエストメソッド情報も判定されているが、POSTメソッドは含まれていないようなので一旦スルー。
いずれにせよ、スーパーグローバル変数の情報が含まれた、Symfony\Component\HttpFoundation\Request
クラスのインスタンスが返却されることが分かったところで、Illuminate\Http\Request
のcapture()の記述に戻る。
Illuminate\Http\Request::capture()
を再度調査
public static function capture() { static::enableHttpMethodParameterOverride(); return static::createFromBase(SymfonyRequest::createFromGlobals()); }
さて、次はcreateFromBase()
の調査である。同ファイルに定義されているので、こちらのソースコードを追ってみる。
/** * Create an Illuminate request from a Symfony instance. * * @param \Symfony\Component\HttpFoundation\Request $request * @return static */ public static function createFromBase(SymfonyRequest $request) { if ($request instanceof static) { return $request; } $newRequest = (new static)->duplicate( $request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all() ); $newRequest->headers->replace($request->headers->all()); $newRequest->content = $request->content; $newRequest->request = $newRequest->getInputSource(); return $newRequest; }
着目したいのは、$newRequestが生成される箇所である。引数として渡されている、Symfony\Component\HttpFoundation\Request
のインスタンス$requestが内部で使われている。
そこで、duplicate()を追ってみたところ、どうやらこれも、Symfony\Component\HttpFoundation\Request
に処理の本体が定義されているようである。
/** * Clones a request and overrides some of its parameters. * * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * * @return static */ public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; if (null !== $query) { $dup->query = new ParameterBag($query); } if (null !== $request) { $dup->request = new ParameterBag($request); } if (null !== $attributes) { $dup->attributes = new ParameterBag($attributes); } if (null !== $cookies) { $dup->cookies = new ParameterBag($cookies); } if (null !== $files) { $dup->files = new FileBag($files); } if (null !== $server) { $dup->server = new ServerBag($server); $dup->headers = new HeaderBag($dup->server->getHeaders()); } $dup->languages = null; $dup->charsets = null; $dup->encodings = null; $dup->acceptableContentTypes = null; $dup->pathInfo = null; $dup->requestUri = null; $dup->baseUrl = null; $dup->basePath = null; $dup->method = null; $dup->format = null; if (!$dup->get('_format') && $this->get('_format')) { $dup->attributes->set('_format', $this->get('_format')); } if (!$dup->getRequestFormat(null)) { $dup->setRequestFormat($this->getRequestFormat(null)); } return $dup; }
処理としては、各種情報をnew ParameterBag($request);
などに置き換えているようだ。
ここまで追ってきたが、どうやらnew ParameterBag($request);
が、Requestオブジェクトを生成している本体らしいことが見えてきた。
最後に、上記を調査する。
ParameterBagを追ってみる
調査中
認証機能を実装した際のステータスコードについて
認証機能は色々あるが、ここでは単純なパスワード認証を考える。
Webページのログイン画面から、メールアドレスとパスワードを入力して、該当サイトにログインしたいとする。
この時、認証に失敗した際には、Webページに「パスワードが違います」というメッセージを表示するだけでなく、返却するステータスコードの側でも、ステータスコード401番を返却しなければならない。
また、どのリクエストに対して401番のステータスコードを付与するかだが、これは認証情報を保持し、POSTメソッドにて送信されているリクエストが好ましい。
上記のリクエストに401を付与した上で、再度画面表示を行うGETメソッドのリクエストに対して、ステータスコード200番を付与してやれば良い。
<img src="">属性によるリクエストについて
画像を表示するとき、属性を指定することにより、記述したパスに該当する画像ファイルがページ上に存在する。
この時、画像に関しては別途、HTTPリクエストが送られ、画像が表示される。
HTMLファイルとしては、同一ファイル内に記述されているのだが、リクエストは画像ごとに別々でリクエストが送信される。
リクエストが送信される過程としては、WebブラウザがHTMLを解析する中でimgタグを認識し、srcタグで指定されたパスでリクエストが送信される。
ただし、相対パスで指定する場合、http(s)://〜のホスト部・ドメイン部分は明示的に省略しなければいけない。
リクエストを切り分けることで生じる事象
例えば、画像ファイルの指定パスを誤って記述した場合。
この場合にも、HTML部分についてはブラウザに正常表示され、画像の部分だけ、ステータスコード404が返却され表示されないということになる。
また、alt属性を指定していると、画像の取得に失敗した場合に代替テキストが表示される。(ただしこの場合においても、画像ファイルを取得するリクエストのステータスコードは404である。
画像の送信を含む際のHTTP通信の接続について
HTTP通信では、リクエストのたびに接続を確率し、レスポンスが返却されれば接続を切ることを繰り返している。
たとえば、画像ファイルを多く含むWebページの場合、上記を繰り返すため、非常に冗長に思えるかもしれない。
しかし、HTTP1.1においては、リクエストヘッダに下記が付与されることによって、レスポンスを返した後に接続を維持し、次のリクエストを送るときは、保持している接続をそのまま再利用することができるようになっている。
Connection: keep-alive
これによって、画像ファイルにおけるやりとりが頻発しても、1つの接続内で行うことができるようになっている。
Webの脆弱性についてまとめる
Webの脆弱性について、まとめてみます。 こちらも勉強中のため、随時更新。
クロスサイトスクリプティング(XSS脆弱性)
SQLインジェクション
- SQL命令に不正なパラメータが渡されることによって、意図しないデータ操作が行われてしまうこと。
- データ操作はライブラリ経由で行うこと。またそもそも、外部からの入力値をそのまま使ってSQLを構築しないこと。
OSコマンドインジェクション
- OSコマンドに対して、外部からの入力値などにより、不正な命令などがコマンド上で実行されること。
- そもそも、スクリプトからコマンド操作を行う命令(関数)を使うべきではない
クロスサイトリクエストフォージェリ(CSRF)
- 例えばフォーム送信などで、クライアントユーザーが意図しないうちに、入力値が送信されたりする脆弱性。
- 「ユーザーが意図しない操作」
対策
<input type="hidden" name="token" value="{{ トークン値 }}">
- 一方、上記のトークン値をセッション値としても持たせておく。
- フォーム送信時、送信先において「トークンが送信されているか」「送信されたトークン値は正しいか」を判定し、これらに該当しない場合は不正な送信として、403とかで弾く。
<?php session_start(); $token = $_POST['token']; if (! isset($token)) { http_response_code(403); } if ($token !== $_SESSION['token']) { http_response_code(403); } ?>
パストラバーサル脆弱性
メールヘッダインジェクション
対策
- 特にメールの(送受信先の)アドレス部分に関しては、入力値をそのまま使わないこと
- 必ず、正規表現などによって、意図するアドレスの場合にのみ送信されるようにする。
- その他、inputタグの隠しフィールドとして、アドレス情報を持たせておくのも避けるべき(inputタグは簡単に書き換えられるから)
ファイルアップロード攻撃
- 不正なファイルがアップロードされ、実行されてしまう脆弱性
- 対策としては、ファイルアップロード時に、アップロードできるファイルの種類を限定的にするなど。
全体感
全体として、リクエスト情報全般について、妥当性を検証する必要がある。 リクエスト情報というと、フォームからの入力値などを想像しやすいが、それだけではない。 セッション情報やサーバー情報、HTTPリクエストヘッダなどからも、不正な値が送信されないかチェックする必要がある。
要は「バリデーションを強める」ということなのだが、下記などはおろそかになりやすい。
- 文字エンコーディングは意図したものか(XSS脆弱性の原因となる)
- nullバイトはチェックされているか
- 必須検証は適切か(isset()は空文字列であってもtrueを返すので不適切)
- 数値検証は適切か(is_int()だと、スーパーグローバル変数を適切にバリデートできない)
以下、コード例にて検証してみる(PHP)
「チーズはどこへ消えた?」を読んで、感じたことをまとめる
「チーズはどこへ消えた?」という有名な本がある。 これを先日買って、今読んでいるところだ。 ページ数は少なく、サクサク読めるが、書かれている内容は深く、考えされられる点も多い。 ここでは、感じたこと・考えたことなどを雑多にメモしていく。
まずは変化を知ること
まずは変化を知ること。「変化する」という事象を悟ること。これが重要だと思った。 僕たちは、物事を不変なものだと思いがちだ。 特に、置かれている状況が幸福なものであれば、その傾向は強くなる。
- 上手くいっている現状は当たり前だ
- 今の状況が直線上に続いていく
こうしたことを考える。
だが、周囲は常に変化に晒されている。 世界は常に変わり続けている。 これは事実だし「変わり続ける」ということだけに関しては、この先も不変だろう。 とすれば「変化は起きるもの」と思うことがまずは重要だ。 その上で、今起きている変化を知る努力をすること。 現在起きている変化を正確に知ることは相当難しいだろう。 完璧な予測は、コンピュータでもできない。 だからこそ、まずはできることから始める。 「知ろう」とする。
一番最悪なのは、未来に一切の希望を持てず、何もしないこと
未来に一切の希望がないのが、一番最悪だ。 自分の力ではどうすることもできない、ということだ。 未来に希望がないと、行動する意味もないから、何もできなくなる。 だけど、何もしないのだから、自体が好転するはずもない。
未来には、本当に希望がないのか? 行動するのが怖いから、「未来に希望はない」と思い込んでいるだけではないのだろうか。 その見極めが重要だ。 そして、ほとんどの場合において、未来に希望がないとする原因は後者だ。 行動するのが怖いから、思い込むのだ。
「行動するのが怖い」を乗り越えるには
とにかくやってみることだ。 やってみる。行動してみる。 すると、心に抱いていた恐怖の大半が、幻想だったことに気付かされる。
「何もしない」 < 行動して失敗する
ということが分かる。 分かることで、さらに行動できるようになる。 行動することで、成功する確率を高めることもできる。
未来(=チーズ)を作る必要性
未来に希望がないのが最悪だ、と言った。 そして、そのほとんどは、恐怖による思い込みだと書いた。 だが、本当に未来がないのであれば、どうしようもない。 たとえ恐怖がなくても、想像しうる良い未来が無いのであれば、結局は行動することができない。
だから、思い描くことのできる未来(=チーズ)を作ることが必要だ。 それは、状況を好転させるための「ビジョン」という言葉に置き換えることもできる。 ではどうやって、ビジョン(=チーズ)を持つことができるのだろうか?
物語における、未来(=チーズ)を抱くまでの過程
実は、物語の中では、確証を持って「チーズはある!」という未来を描いていない。 「チームはないのかもしれない」という不安と、「チーズを見つけた時」の期待感とのせめぎ合いの中で、葛藤しながら行動する様子が描かれている。 しかし、次第に行動自体に意味や喜びを見出していく。 「チーズがないままでいるより、迷路に出て探した方が安全だ」 という結論に至り、行動という道に進んでいくのだ。
これは、現実世界にも当てはまるのかもしれない。 もの物語に沿うと、確証性のある未来(=チーズ)を最初から抱く必要性はないように思える。 それよりも、とにかく今いる場所から一歩踏み出すことの方が、重要に思える。 その、ぼんやりとした未来(=チーズ)に向かうまでの過程を、楽しむことができれば最高だ。
まとめ「変化に適応するには」
- 「変化するのは当たり前」と思うこと。その上で、変化に対するアンテナを張っておくこと
- 実際に変化が起きれば、それに合わせて既存の形を変えること。
- その際に生まれる恐怖心を見つめること。「それは実体を伴った恐怖なのか?」
- その上で、行動すること
プログラムが動く仕組み
こちらも、学習備忘録として随時更新していきます。
プログラムが動く仕組み
- プログラムを書く(ここでは、高水準言語を想定)
- プログラムがコンパイラにより、マシン語に変換される。
- OSにより、ハードウェアに保存されたプログラムがメモリーにロードされる。
※ディスクの中にあるままだと、CPUで実行させることはできない(実行自体はできるが、処理が低速になる)
※もし、ディスクをメモリーとして使いたい場合は、仮想記憶(実態はディスクだが、実行時にメモリーと入れ替わりされる)などがある。
※また、ディスクキャッシュという仕組みもある。これについては、実態はメモリーである。(メモリーの一部分が領域として割り当てられ、一度ディスクからロードされたデータに関しては、アクセスが高速化される。 - CPUにより、メモリーに保存されたプログラム(データと命令)が読み出される。
- CPUが、読みだしたデータや命令を解釈し、演算・制御が行われる。
CPUを深掘ると
- CPU内部では、レジスタを使って処理が行われる。
- レジスタとは、記憶領域のことで、演算を行うためのレジスタや、メモリーのアドレスを参照するレジスタなど種類がある。
- プログラムの実行時は、メモリーのアドレスが参照されたレジスタから、メモリーにロードされた各種命令などを読み出す。そしてそれが、演算を行うレジスタ(アキュムレータ)にコピーされることで、演算が行われる。
条件分岐・ループ文・関数
メモリーについて
- 基本的には、アドレス1番地に対して1バイトのデータが格納されていく。
- ただし、型指定などによって、1バイトより大きいデータを格納することも可能。この場合、バイト数の分だけメモリーのアドレス番地が確保される。 (例えば2バイトだったら、アドレス番地が2つ確保される)
- スタック・キューによって、アドレス番地を指定せずとも、メモリーへのデータの格納・書き出しができるので便利。
プログラムにできること
随時更新。イメージです。
- 計算
- 条件による処理の分岐
- 繰り返し
Webアプリケーションとデスクトップアプリケーションの違い
Webアプリケーション
- 処理はサーバーで行われる
- 画面はWebブラウザ上で、HTML表現によって表示される
デスクトップアプリケーション
- 処理はCPUで行われる
- 画面はOS上で表示される
読みやすいソースコードを書くために
読みやすいソースコードを書くためのポイントをまとめていきます。 こちらは、随時更新していきます。 ※リーダブルコードなどの内容です。学習備忘録として
読みやすいコードとは
- 他人が読んで、理解するまでの速度が最短であるソースコード
命名の際の注意点
変数を命名する際には、なるべく文字列に情報を詰め込みたい。 そのために、下記の点などに注意する。
- なるべく汎用的な表現を避ける。ソースコードの動作を適切に表した命名を考える(意味合いのレンジを狭める)
- なるべく具体的な表現を用いる。単位の概念がある場合は、変数にその情報を含めても良い(ミリ秒など)
- 限界値を表す際は
mim・max
を含める。範囲を指定する際にはfirst・last
を含める。
どこまで詳しく命名するか
意図通り読み取ってもらうために
- 複数の意味にとれる命名は避ける(レンジを狭める、という話と同じ)
- 例えば、
get
という表現は暗黙的に単数情報を返す
という意味に受け取られる。このように、暗黙的な意味を持つ表現については、期待通り受け取ってもらえるようにする
コメントする際の注意点
コメントすべきこと
- 定数などが決まった背景
- 関数やクラスなどの全体像・どういう役割を持つか
- コード化されていないが、次に書くべきソースコードが決まっている場合(TODO: などの記法を使う)
- 例えば、処理に時間がかかるリスクがあるなど、注意すべき点があるソースコードについては、あらかじめコメントでその旨を伝えるようにする
コメントするべきではない
- コードを見て一目瞭然の事象については、コメントしない
- 例えば、関数名などの説明は、なるべく関数の命名で工夫したい
- プログラムの動作をただ説明しただけのコメントは避ける。動作ではなく「コードの意図」を書くようにする
条件分岐構文の書き方
- 比較を書くときは、変化する値(result)を左に。不変の値(expect)を右側に配置する。
- if else構文において、下記に該当する条件式を先に記述するようにする。 (肯定式・目立つ条件・単純な条件)
- ネストはなるべく浅くする。そのためには、下記のように、単純な条件を先に返してしまえば良い(ガード節)
// 悪い例 if ($result === A) { if ($result === B) { return 'B'; } return 'A'; } else { return 'C'; } // 良い例 if ($result !== A) { return 'C'; } if ($result === B) { return 'B'; } return 'A';
- 条件分岐式は、巨大になることが多い。そんな時は、説明変数を用いる。
// 例 $result = ($input > 1 && $input < 5 && $input !== 3); if ($result) { return true; }
- その他、概念を伝える際なども、変数を積極的に用いる