- 投稿日:2019-12-22T23:13:41+09:00
BEAR.SundayのチュートリアルでDIが面白いほど理解できた話
はじめに
BEAR.Sunday アドベントカレンダー 23日目の記事です。
長い間、DIに対する理解がふわっとしていたのですが、BEAR.Sundayのチュートリアルをやってみたところ、今まで喉に引っかかっていた小骨な取れたかのように飲み込めたような気がしたので、BEAR.SundayでDIを実践してみたことで理解できたことを記事にしてみようと思った次第です。
DI とは
DI とは、Dependency Injection の略で日本語では依存性の注入という意味です。
依存性をオブジェクトのことだと捉えると、オブジェクトを他のオブジェクトに渡してあげるということになります。
あるクラスの中で他のクラスをNewで生成したりせずに、引数として外部から渡してもらって利用することをDIになります。
DIではない例
<?php namespace src; class Car { private $tire; public function __construct() { $this->tire = new Tire(); } public function viewCar() { settingCar($this->tire); return $this; } }
Car
クラスのコンストラクタ内でTire
オブジェクトを生成しています。DIの例
<?php namespace src; class Car { private $tire; public function __construct(Tire $tire) { $this->tire = $tire; } public function viewCar() { settingCar($this->tire); return $this; } }今度は、コンストラクタの引数に
Tire
オブジェクトを渡してあげるようにしました。このようにクラス内で他のクラスを生成しないように、外部から注入してあげることがDIになります。
DIには、以下のようなパターンがあります。
- コンストラクターインジェクション
- コンストラクタの引数にオブジェクトを渡す
- セッターインジェクション
- セッターにオブジェクトを渡してプロパティを設定する
- メソッドインジェクション
- メソッドにオブジェクトを渡す
- セッターインジェクションと似ていますが、注入されたオブジェクトのメソッドを利用することを目的としている場合がこちらに該当するのかなと認識しています
ちなみに、上記の例は
コンストラクターインジェクション
の例になります。DI すると何が嬉しいのか
DIのメリットとしてよく説明されるのは
- 粗結合になり柔軟性が上がる
- テストしやすくなる
という点だと思います。
例えば、クラスAの中でクラスBを生成している実装の場合、クラスBのオブジェクトを生成する際のパラメータに変更があった場合、クラスA側も修正しなければなりません。
しかも、クラスBを廃止してクラスCに置き換えたくなった場合、クラスBからオブジェクトを生成していた箇所すべてを修正しなければいけません。
現実世界を例に説明してみる
例えば、車工場でタイヤを作っていたら、製造した車に適したタイヤかどうかを組み立てる段階で確認しなければなりません。
ですが、車工場はタイヤ工場が作ったタイヤをただ組み立てるだけにしておけば、車の製造のみに集中することができます。
タイヤ工場も、車工場のことを気にせず、「自分たちの工場でつくるタイヤはこういう規格なんだ!」ということを明確にしておくだけで良くなります。
このように、オブジェクト同士を小さい単位で切り分けられると、他の部品に取り替えることも容易になり、部品交換による影響も小さくすることができます。
BEAR.Sunday(Ray.DI)でDIを試してみる
それでは、具体的な実装の場面でDIを活用するとどうなるかをBEAR.Sundayを利用しながら確認していきたいと思います。
まずは、与えられた正の整数までの正の整数をランダムに生成してレスポンスする処理を作成してみます。
<?php namespace MasaKuVendor\DiSampleApp\Resource\App; use BEAR\Resource\ResourceObject; class Number extends ResourceObject { public function onGet(int $maxNum) : ResourceObject { $this->body = ['rando' => rand(1, $maxNum)]; return $this; } }このコードに指定された値が正の整数かを判定するバリデーションクラスをDIしてみます。
まずはバリデーションクラスのインターフェースを作成します。
<?php namespace MasaKuVendor\DiSampleApp; interface CheckNumberInterface { public function checkPositiveInteger(int $num) : bool; }次は先程の整数を生成するメソッドに上記のバリデーションクラスをDIする処理を追加します。
<?php namespace MasaKuVendor\DiSampleApp\Resource\App; use BEAR\Resource\ResourceObject; use MasaKuVendor\DiSampleApp\CheckNumberInterface; class Number extends ResourceObject { /** * @var CheckNumberInterface */ private $checkNum; public function __construct(CheckNumberInterface $checkNum) { $this->checkNum = $checkNum; } public function onGet(int $maxNum) : ResourceObject { if($this->checkNum->checkPositiveInteger($maxNum)){ $this->body = ['rando' => rand(1, $maxNum)]; }else{ $this->body = ['message' => `正の整数を入力してください`]; } return $this; } }コンストラクタでバリデーションクラスのオブジェクトを注入してクラス内でバリデーションメソッドが利用できるようにしました。
次に具体的なバリデーション処理を記載していきましょう。
<?php namespace MasaKuVendor\DiSampleApp; class CheckNumber implements CheckNumberInterface { public function checkPositiveInteger(int $num) : bool { if($num > 0){ return true; }else{ return false; } } }すごく単純なチェック処理ですが、正の整数かをチェックする処理を追加しました。
最後に、アプリケーション内でDIできるようにバリデーションチェッククラスを束縛します。
<?php namespace MasaKuVendor\DiSampleApp\Module; use BEAR\Package\AbstractAppModule; use BEAR\Package\PackageModule; use MasaKuVendor\DiSampleApp\CheckNumber; use MasaKuVendor\DiSampleApp\CheckNumberInterface; class AppModule extends AbstractAppModule { /** * {@inheritdoc} */ protected function configure() { $appDir = $this->appMeta->appDir; require_once $appDir . '/env.php'; $this->bind(CheckNumberInterface::class)->to(CheckNumber::class); // この行でCheckNumberInterfaceが引数の型に指定された際にCheckNumberが渡されるようになります $this->install(new PackageModule); } }これで、各アプリケーション内でインターフェースを指定してメソッドを呼び出した際に、自動的にオブジェクトを注入してもらえるようになりました。
おわりに
こうしてBEAR.Sundayのチュートリアルを進めながら実際に動かしてみることで、自分として引っかかっていたポイントがするっと理解することができました。
というのも、もともとLaravelアプリケーションで「
他の処理でも利用したい共通の処理をサービスクラスとして切り出して、必要な箇所で呼び出しながら利用できるようにしたい
」と思っていました。そこで、いろいろと調べているうちに、DIやファサードという手段があることを知ったのですが、ある処理にクラスを外部から注入する、ということがどうしても理解できず、何となく見様見真似で利用していた状態でした。
BEAR.Sundayでは、あるリクエストを処理する上で必要となるオブジェクト(依存性)だけを注入して処理するということが一連の処理の中で明確になっていたのでチュートリアルを進めるだけでも「なるほど、こういうことか!」と理解することができました。
ツールや手法を実践の中で覚えていくことも大事ですが、意味を理解しながら覚えていくことで、自分が実装しているコードにも自信がついてくると思うので、自分が書いているコードでは何を実現しようとしているのかを意識してコードを書いていきたいですね。
参考サイト
チュートリアル | BEAR.Sunday
[https://bearsunday.github.io/manuals/1.0/ja/tutorial.html]DI | BEAR.Sunday
https://bearsunday.github.io/manuals/1.0/ja/di.htmlPHPでDIする
https://www.slideshare.net/OhasiYuki/phpdi-53487642「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した
https://qiita.com/mizunowanko/items/53eed059fc044c5aa5dc
- 投稿日:2019-12-22T21:34:26+09:00
PHPで「大石・泉・すき」をランダムに表示して「大石泉すき」が完成したら大石泉すき
この記事について
本記事は、「大石泉すき」 Advent Calendar 2019の23日目です。
プログラム言語「PHP(PHP: Hypertext Preprocessor)」を使い、「大石泉すき」が完成するまで「大石・泉・すき」をランダム表示するプログラムを作成します。
大石泉とは
「大石泉」(おおいし いずみ)とは、Mobageにて配信されているバンダイナムコエンターテインメントのソーシャルゲーム『アイドルマスター シンデレラガールズ』に登場するロジックアイドルである。
(引用:ニコニコ大百科より)
私の担当アイドルである二宮飛鳥とは出身地が同じ(静岡県)で歳が近いということでいつか公式でユニット組んでくれないかなーと思ってるんですが、中々その機会が訪れなくて泣けますね。
PHPで「大石泉すき」を表示するには
まず最初に、PHPで「大石泉すき」と単純に表示するだけのプログラムを作る場合について考えます。
PHPでは以下のように記述することで、「大石泉すき」と表示することができます。大石泉すき
冗談抜きでこれで終わりです。
PHPは、以下のようにHTMLの中に埋め込んで使うことができる言語です。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title><?php echo $title; ?></title> </head> <body> <?php // <?php ~ ?> で囲った部分のみがPHPの記述として解釈される echo $content; ?> </body> </html>この場合、「<?php ~ ?>」で囲われていない部分はHTMLとして普通に表示されます。
なので、一番最初の例のように単純に文字列を表示したい場合、PHPのタグを記述せず表示したい文字列を書けばそれで終わりなのです。
ね、簡単でしょう?勿論、以下のようにプログラムっぽく書くことも可能です。
<?php echo '大石泉すき';これで終わるとあまりにもあっさりしすぎなので、ちょっと捻ったことをやってみましょう。
#「大石泉すき」が完成するまで「大石・泉・すき」をランダム表示する #って何?
「大石泉すき」が完成するまで「大石・泉・すき」をランダム表示します。
何年か前に流行った「「進捗・どう・です・か」をランダムに表示し「進捗どうですか」が完成したら煽ってくる」を「大石泉すき」でやろうという試みです。
これをPHPで書くと、こんな感じです。
<?php // 「大石泉すき」を少しずつ区切って配列に格納する // 「大石泉すき」以外の文字列が紛れ込んでるって? さあ、何の事ですかね………… $ooishiizumisuki = [ '大石', '泉', '二宮', '飛鳥', 'すき', ]; // 探索対象の文字列リスト $search_list = [ '大石泉すき', '二宮飛鳥すき', ]; // count関数は配列の要素数を取得する // そこから1を引くことで当該配列における最大の添字を求めることができる $max_index = count($ooishiizumisuki) - 1; // PHPの配列は添字が0から始まる $min_index = 0; $text = ''; $appeared_text = ''; while (1) { // $ooishiizumisukiの中からランダムに文字列を取得して、文字列結合していく $index = random_int($min_index, $max_index); $text .= $ooishiizumisuki[$index]; foreach ($search_list as $search_text) { // 結合した文字列の中に、探索対象の文字列が含まれていたらループを抜ける if (strpos($text, $search_text) !== false) { $appeared_text = $search_text; break 2; } } } // 探索対象の文字列が出現するまでに何文字を要したかを記録 $text_length = mb_strlen($text); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title><?=$appeared_text;?></title> </head> <body> <div><?=$text;?></div> <div><?=$text_length;?>文字で「<?=$appeared_text;?>」が出現しました。<?=$appeared_text;?></div> </body> </html>実際に実行すると、こんな感じになります。
すき二宮泉すきすき大石すき飛鳥すき飛鳥泉すき泉大石大石飛鳥飛鳥泉飛鳥すきすき二宮泉飛鳥すき飛鳥泉飛鳥二宮大石二宮大石泉すき
61文字で「大石泉すき」が出現しました。大石泉すき運がよければ以下のように最短で「大石泉すき」できます。
大石泉すき
5文字で「大石泉すき」が出現しました。大石泉すきところで「大石飛鳥」、「二宮泉」ってすごくときめく文字列じゃありませんか?
バンナ○ならびにサイゲ○ムスはいずあすを公式化してね! しろよ☆
- 投稿日:2019-12-22T21:18:35+09:00
Visual Studio Code - Remote Development を使用して PHP Xdebug を使ったデバッグを行う
要約
- VS Code の Remote Development で Xdebug する時は、
remote_host
の向き先を ローカルホストにする。- Remote Development の接続先の VS Code に PHP Debug を導入する。
.vscode/launch.json
で Xdebug からの情報を Listen する。はじめに
VS Code の 拡張機能 「 Remote Development 」は、これまでもどかしく感じていたローカル開発環境の構成を改善してくれそうで色々と試しています。
前回の記事 では、Vagrant で構築した仮想環境の上にある Docker コンテナに対して、 Remote Development で接続する方法を記載しました。
今回は、前回構築した環境に追加する形で、 PHP の Xdebug を用いた開発が出来るように設定方法をまとめてみます。
概要
書くこと
- Docker コンテナの起動時の Xdebug の設定方法
- VS Code Server で Xdebug のデータを Listen する方法
書かないこと
- Vagrant で構築する仮想環境に Docker や docker-compose を導入する方法。
想定環境
この記事で実現するシステムの構成は下図の通りです。
Remote Development の有無による違い
Remote Development を使用しない場合
Remote Development を使用せず、 Xdebug のデータを HostOS 上の VS Code で Listen する場合は以下のような構成でした。
この構成を実現するためには、 Xdebug の設定ファイルで remote_host の IP アドレスを指定する必要がありました。
Remote Development を使用する場合
Remote Development を使用する場合の構成は以下の構成になります。
Xdebug の remote_host は localhost を指定すればよく、送信先の IP をいちいち気にする必要がありません。
設定手順
0. 基本設的な設定手順
この記事で実現している構成の基本的な要素は以下の記事で解説しています。
1. XDebug が有効化された Docker コンテナの構築
Docker コンテナの構築に必要なリソースは以下の通りです。
command(bash@GuestOS)## Dockerfile ## authorized_keys : 公開鍵認証に使用する公開鍵。Dockerコンテナ内に配置する。 ## php/20-xdebug.ini : Xdebug の設定ファイル。Dockerコンテナ内に配置する。 $ tree . ├── Dockerfile ├── authorized_keys └── php └── 20-xdebug.ini
Dockerfile
は次のように記載します。Dockerfile# image FROM php:7.0.15-fpm ENV LANG C.UTF-8 RUN apt-get update -qq && \ apt-get install -y \ zlib1g-dev \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ && docker-php-ext-install zip \ && yes "" | pecl install xdebug \ && docker-php-ext-enable xdebug \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j$(nproc) gd RUN apt-get update \ && apt-get install -y libpq-dev \ && docker-php-ext-install pdo_mysql pdo_pgsql RUN apt-get update \ && apt-get install -my wget gnupg WORKDIR /opt/ # ここから最後までがポイント RUN apt-get update && apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile # 手元の公開鍵をコピー COPY authorized_keys /root/authorized_keys EXPOSE 22 # 公開鍵を使えるようにする (パーミッション変更など) CMD mkdir ~/.ssh && \ mv ~/authorized_keys ~/.ssh/authorized_keys && \ chmod 0600 ~/.ssh/authorized_keys && \ # 最後に ssh を起動 /usr/sbin/sshd -D # Xdebug の設定ファイルを Docker コンテナに配置 COPY php/20-xdebug.ini /usr/local/etc/php/conf.d/20-xdebug.iniXDebug の設定を以下のようにします。
php/20-xdebug.inixdebug.remote_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_connect_back = 1 xdebug.remote_host= "localhost" xdebug.remote_port = 9001 xdebug.remote_log = /var/log/xdebug.logDocker コンテナを以下のコマンドで構築します。
command(bash@GuestOS)$ pwd /vagrant/docker-sample $ tree . ├── Dockerfile ├── authorized_keys └── php └── 20-xdebug.ini # build $ docker build ./ -t example # Docker Container を起動 # Port:10000 を Port:22 に転送 $ docker run -d -p 10000:22 example # Docker Container に SSH 接続 $ ssh root@127.0.0.1 -p 10000 -i ~/.ssh/private_key2.
.ssh/config
の設定Host OS から 仮想環境(GuestOS、Dockerコンテナ)に SSH接続するために、以下の設定を行います。
.ssh/config(Windows10)# Vagrant で起動した仮想環境への SSH接続設定 Host vagrant-os HostName 127.0.0.1 User vagrant Port 2222 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key IdentitiesOnly yes LogLevel FATAL # Docker で起動した仮想環境への SSH接続設定 Host docker-os Hostname 127.0.0.1 User root Port 10000 ProxyCommand C:\Windows\System32\OpenSSH\ssh.exe -W %h:%p vagrant-os IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key3. Visual Studio Code の設定
1.) と 2.) の設定をすることで、 VS Code の Remote Development の SSH Target には以下のように接続先候補が表示されるようになります。
上図のうち、
docker-os
の方にアクセスした際の画面が下図です。
ここで、.vscode/launch.json
を作成して、 Xdebug からのデータを Listen 出来るよう設定しています。
.vscode/launch.json
の設定は以下の通りです。
php/20-xdebug.ini
のxdebug.remote_port
で指定した Port と番号を合わせて設定します。
Docker コンテナ上の VSCode に PHPDebug 機能拡張 をインストールすることも忘れず行います。.vscode/launch.json{ // IntelliSense を使用して利用可能な属性を学べます。 // 既存の属性の説明をホバーして表示します。 // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9001 }, { "name": "Launch currently open script", "type": "php", "request": "launch", "program": "${file}", "cwd": "${fileDirname}", "port": 9001 } ] }4. 動作確認
3.) までの手順で設定した内容が正しく設定されているか確認してみます。
Docker コンテナ内で適当な PHP プログラムを作成します。test.php<?php echo "test";PHPDebug で Listen しながら、適当な箇所でブレイクポイントを設定して テストプログラムを実行します。
command(bash@DockerContainer)$ pwd /root $ ls test.php # テストプログラムの実行 $ php test.php test実行結果は以下のようになりました。
おわりに
書いたあとで見返してみると、なんのことは無い、普通の Xdebug の設定でした。
リモートの仮想環境に VS Code Server があり、そこと HostOS の VSCode がやり取りすることによって、リッチな VSCode の UI を使用して仮想環境上のリソースが編集出来るので、ひじょ~~~に便利です。
参考
今回の記事を作成するにあたって参考した記事です。
Remote Development
- 投稿日:2019-12-22T20:31:48+09:00
PHPでpost_max_sizeやupload_max_filesizeの設定がini_set()で反映されない場合
サーバー上でPHPを利用していると、PHPの設定を
php.ini
を書き換えることなく、
特定のファイルや、ディレクトリないだけで変更したい場合がたまにあります。つい先日も、自作のPHPファイルだけで有効になるよう、
post_max_size
とupload_max_filesize
の値をini_set
で値を変更したところ、値が反映されていない・・
php_info()
で設定値を確認しても、やっぱり変わっていなかったので、
ちょっと調べたところPHPマニュアルにがっつり書いていました。PHPの各ディレクティブ(設定値)は、そのディレクティブのモードによって、
何処で設定出来るかが決まっているそうです。
PHPディレクティブとモードの一覧はこちら
post_max_size
やupload_max_filesize
は、
モードがPHP_INI_PERDIRだったので、ini_setでの設定変更が不可だったようです。以下はそのモードについて。
PHP_INI_USER
PHPマニュアルを確認しても該当するディレクティブがないため気にしなくて良さそうなモード
PHP5以前は使われていたらしく、以下の3つのみ設定変更可能なモード
・自作のphpファイルないでini_setコマンドを使って設定変更
・Windowsレジストリから設定変更
・.user.iniから設定変更(PHP5.3以降の場合)PHP_INI_PERDIR
php.ini、.htaccess、httpd.conf、.user.iniでのみ設定変更
PHP_INI_SYSTEM
php.ini、httpd.confでのみ設定変更可能
PHP_INI_ALL
自作PHPファイルでini_set、.user.ini、.htaccess、php.ini、httpd.confのどれでもも設定可能
今回の場合、
post_max_size
やupload_max_filesize
を変更するようの
.htaccessを作成して、自作PHPファイルが格納されているディレクトリに配置したところ、
そのディレクトリだけ設定が反映されてることが確認できました。まとめ
昔、同じ理由で変更出来なかった事をすっかり忘れて、またハマってしまったため、
自分への戒めとして記事に残しときます。
- 投稿日:2019-12-22T19:55:04+09:00
PHP を JVM 言語にするには?
はじめに
みなさんは JVM を知っていますか?そして JVM を実装したことがありますか?さらに、 JVM 言語そのものを実装したことがありますか?
このアドベントカレンダーは PHP アドベントカレンダー並びに言語実装のアドベントカレンダー 25 日目になっており、記事の内容は PHP を JVM 言語そのものにするといった内容になっています。
Java は JDK 9 から構成されるバイナリが大きく変わっており書籍に載せたいくらいの規模になるので、今回については JDK 8 として書かせていただきます。そもそもなんで JVM なのか?
JVM は Java Virtual Machine の略で、 Oracle から公式に Java Virtual Machine Specification という名前のドキュメントが出ています。
そのため、書いてあることがどのような方法でも理解できれば、誰もが作りやすい物といっても過言ではないかと思います。
関連する登壇
過去に PHP と JVM を絡めた登壇をいくつかしているので、興味のある方は見てみてください。
プロジェクトについて
100% PHP で JVM の実装及び PHP そのものを JVM 言語化する実装を php-java というリポジトリで行っています。
こちらも興味がある方は見てみてください。そして、コントリビュートしてくれるとうれしいです⭐PHP そのものを JVM 言語にしてく
さて本題に入り PHP を JVM 言語にしていきます。
PHP のコードを AST (Abstract Syntax Tree, 抽象構文木) に変換する
AST (Abstract Syntax Tree) とは抽象構文木のことを指しますが、正直初見で AST や抽象構文木この単語を見ても何を言っているんだ?となるので、どういうものかを軽く説明します。
Wikipedia によると
抽象構文木(英: abstract syntax tree、AST)は、通常の構文木(具象構文木あるいは解析木とも言う)から、 言語の意味に関係ない情報を取り除き、意味に関係ある情報のみを取り出した(抽象した)木構造の木である。と書いてありますが、よくわからない。これをとてつもなく簡単に言い換えると、PHP 限定で言えば スペース だとか ブレース ({, }) とかを取り除いて、ノード上、つまりそのステートメントを階層とオブジェクトで表現したものです。
例えば
<?php namespace A; class SomethingClass { }というコードがあったときに、これを下記のようにツリー状にしたものを AST とだと思えば大丈夫です。
+ Namespace + Name + A + Statements + Class + Name + SomethingClass + Statements + ...上記のような状態にしたもので、PHP 言語をパースする方法としては
nikic/php-parser
を使います。ちなみに余談ですが
nikic/php-parser
は PHP そのものの静的解析などに有用です。
nikic/php-parser
を composer でインストールします。$ composer require nikic/php-parserそして、
Hello World!
を AST にしてみましょうHelloWorld.php<?php echo "Hello World!";ast.php<?php require __DIR__ . '/vendor/autoload.php'; use PhpParser\ParserFactory; var_dump( (new ParserFactory()) ->create( ParserFactory::PREFER_PHP7 ) ->parse( file_get_contents( __DIR__ . '/HelloWorld.php' ) ) );
ast.php
を実行すると下記のように出力されます。array(1) { [0]=> object(PhpParser\Node\Stmt\Echo_)#1100 (2) { ["exprs"]=> array(1) { [0]=> object(PhpParser\Node\Scalar\String_)#1099 (2) { ["value"]=> string(12) "Hello World!" ["attributes":protected]=> array(3) { ["startLine"]=> int(2) ["endLine"]=> int(2) ["kind"]=> int(2) } } } ["attributes":protected]=> array(2) { ["startLine"]=> int(2) ["endLine"]=> int(2) } } }これが AST になります。 exprs は expressions, つまり式の複数形です。式を 1 つしか受け取れないものは expr になります。
そして、またここでも余談ですが echo が exprs である理由は echo はカンマ区切りで複数の式を受け取ることができるのです。そのため複数の式を受け取る形になっています。 print は echo とは違い複数の式ではなく一つの式のみしか受け取れず、また常に 1 を返す仕様です。
なお、どちらも言語構造ですが AST に分解した際に echo はステートメントとして扱われ、 print は式として扱われます。構成するクラスファイルのバイナリを理解する
バイナリファイルにコンパイルする際に AST と同様で重要なのが JVM のクラスファイルの仕様そのものです。
JVM におけるクラスファイルの仕様は Java Virtual Machine Specification のChapter 4. The class File Format
にかかれています。さて、上記に書かれているクラスファイルの仕様は下記のとおりです。
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }これらの意味そのものについては PHP または JavaScript での実装方法を Qiita にまとめているので詳しく知りたい方は読んでみてください。
- PHP で Java の中間コードの class ファイルを生成して、 java コマンドで Hello World を出力する
- JavaScript で JVM (Java Virtual Machine) を実装する (Hello World 出力編)
- JavaのバイトコードをPHPで見よう(とりあえずメソッドだけ拾ってみる)
AST を上記のフォーマットに則りバイナリファイルに書き出していく必要があります。
構成される Constant Pool を理解する
Constant Pool は文字列やメソッドなどのシンボルテーブルのような役割を担っているとても重要なものです。この Constant Pool からメソッド名やクラス名、出力する文字列などを探し出す必要があります。
今回の出力は HelloWorld を中心にしていきたいと思います。クラスファイルの仕様も踏まえて、Hello World で必要な Constant Pool のエントリは下記になります。
親のクラス名
- java.lang.Object
現在のクラス名
- ここでは HelloWorld としておきます。
System.out.print
- echo を Java に置き換えると
System.out.print
が適任であるため。java.io.PrintStream
- System.out は java.io.PrintStream であるため。
Hello World!
- 出力される文字列
main
- エントリーポイントになるため
<init>
,<clinit>
- あってもなくてもどっちでも動く。動的、静的コンストラクタの役割を担っている。今回は使わない。
Code
- Code Attribute を識別するための文字列
これ以外にも Constant Pool はそのメソッドと対になる引数の情報や返り値の情報が入っているディスクリプタ(Descriptor)と呼ばれるエントリも必要です。これによりメソッド・フィールドの引数の型を理解することができます。
そしてそれを紐付けるために NameAndType と呼ばれるエントリも必要になります。Code Attribute において生成されるべきオペコードとそれに付随するオペランドを理解する
そもそも、 Code Attribute とは何か?というところですが、Code Attribute はメソッドに付随する属性の一つで、そのメソッドのオペレーションコードが書かれています。
Hello World を出力するにあたり、オペレーションコードの理解をする必要があります。オペレーションコードとは何か?というと 命令 のことを指します。略されてオペコードとも呼ばれたりします。一般的なオペレーションコードはアセンブリでもそうですが、1 バイトの数字で表されることが多いです。命令はそれぞれ異なる動きをし、引数を持つものもあります。その引数のことを オペランド といいます。
そして、これらを人間に見やすい形にしたものを総じて ニーモニック と呼びます。例えば、JVM の命令の一つとして、 invokevirtual などがありますが、これはニーモニックです。そして、 invokevirtual は indexbyte1, indexbyte2 の 2 つのオペランドを必要とします。
また、invokevirtual のオペレーションコードは 0xB6 です。そして、 JVM にはもう一つ重要な単語としてオペランドスタックというものがあります。これは何かというと、オペレーションコードを実行したにあたって、オペレーションコードにより返された値を保持する場所と認識していただければ大丈夫です。一般的にはプッシュと言います。また、返り値は積まれていく一方でそれを使用することもあります。それをポップといいます。
さて、 本題ですが Hello World を出力するにあたって、必要なオペレーションコードは何か?というと、JVM においては概ね下記のようになります。
- System.out をロードする(オペランドスタックに積む)
- static なフィールドから値をロードするには
getstatic
を使用する- Hello World をロードする(オペランドスタックに積む)
- 定数文字列を Constant Pool からロードするには
ldc
やldc_w
を使用する- print をコールする (PHP の echo は終端に改行コードが入らないため)
- 通常のメソッドの呼び出しには
invokevirtual
を使用する。- return (オペレーションの終了として)
これをオペコードで表すと下記です
getstatic #indexbyte1 #indexbyte2 // System.out の Constant Pool の位置 ldc #index // Hello World! の Constant Pool の位置 invokevirtual #indexbyte1 #indexbyte2 // NameAndType のもの returnバイナリファイルを構成する
前菜は終わり、ここからが本題です。とても長くなります。
PHP でバイナリを書くには?
PHP でバイナリを書くには、
pack
を使用するのがベターです。特に JVM だと符号付き、符号なしの場合や、 2 byte 整数値、 4 byte 整数値などを使用することが多々あります。
pack
関数を使用することにより、 PHP の数値リテラルから整数値を割り出すひと手間を省く事が可能です。例えば
1234
という数値リテラルがあった場合、 ビッグエンディアンの 2 バイトになおす場合、<?php $integerLiteral = 1234; echo chr(($integerLiteral >> 8) & 0xff) . chr($integerLiteral & 0xff)
($integerLiteral >> 8) & 0xff
は何をしているのかというと、数値リテラルを2進数に直したときに8ビット分右にシフトさせていて、 0xff, つまり 255 の範囲内で数値が収まるようにマスクをしています。
1234 を 2 進数に治すと下記です。0100 1101 0010これを右に 8bit シフトさせます。
01000100, つまり
4
となります。そして、これに 0xff をマスクさせています。0000 0000 0000 0100 & 1111 1111 1111 1111 ---------------------- 0000 0000 0000 0100なぜマスクをする必要があるのかというと 4 という数字が 255 を超えていた場合、値がオーバーフローすることになります。
それを防ぐためです。なぜ防げるのかというと、&
オペレータは左右のオペランド(両辺)のビットが立っていない場合は 0 となるからです。つまり、 255 以上の値は必然的に 0 になるので、その値を超えることはないのです。話に戻りますが、4バイトである場合は下記のようになるでしょう。
<?php $integerLiteral = 1234; echo chr(($integerLiteral >> 32) & 0xff) . chr(($integerLiteral >> 16) & 0xff) . chr(($integerLiteral >> 8) & 0xff) . chr(($integerLiteral) & 0xff)そして、こんな面倒くさいことを関数一つ叩けばしてくれるのが pack です。
例えば 2 バイトのビッグエンディアンである場合は下記のようになります。<?php echo pack('n', 1234);4 バイトの場合は
n
がN
になります。メタ情報を書き込む
PHP でバイナリを書く方法を説明しました。では、実際にどういう値を書いていけばいいのでしょうか。
ずばり ClassFile Structure の通りに書いていきます。ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }まず、 magic, minor_version, major_version のメタデータを書き込んでいきます。
magic はファイルを識別するためのマジックバイトのことで、 Java のクラスファイルの場合は0xCAFEBABE
となります。minor_version および major_version は JDK 8 である場合、下記の表より、 minor_version は 0, major_version は 52 となります。
Java SE class file format version range 1.0.2 45.0 ≤ v ≤ 45.3 1.1 45.0 ≤ v ≤ 45.65535 1.2 45.0 ≤ v ≤ 46.0 1.3 45.0 ≤ v ≤ 47.0 1.4 45.0 ≤ v ≤ 48.0 5.0 45.0 ≤ v ≤ 49.0 6 45.0 ≤ v ≤ 50.0 7 45.0 ≤ v ≤ 51.0 8 45.0 ≤ v ≤ 52.0 9 45.0 ≤ v ≤ 53.0 10 45.0 ≤ v ≤ 54.0 11 45.0 ≤ v ≤ 55.0 この対応表、 SE 8 のページではなく SE 11 のページに載っています。
<?php $className = 'HelloWorld'; $handle = fopen($className . '.class', 'w+'); // unsigned short, unsigned int で書き込むための関数を用意する function writeUnsignedByte(int $number) { global $handle; fwrite($handle, pack('C', $number)); } function writeUnsignedShort(int $number) { global $handle; fwrite($handle, pack('n', $number)); } function writeUnsignedInt(int $number) { global $handle; fwrite($handle, pack('N', $number)); } // magic writeUnsignedInt(0xCAFEBABE); // minor_version writeUnsignedShort(0); // major_version writeUnsignedShort(52);AST にした PHP を解釈して Constant Pool を書き込む
次に constant_pool_count, constant_pool の書き込みを行います。
Constant Pool にはエントリを識別するためのタグがあり、JDK 8 である場合、下記のとおりとなります。
Constant Type Value CONSTANT_Class 7 CONSTANT_Fieldref 9 CONSTANT_Methodref 10 CONSTANT_InterfaceMethodref 11 CONSTANT_String 8 CONSTANT_Integer 3 CONSTANT_Float 4 CONSTANT_Long 5 CONSTANT_Double 6 CONSTANT_NameAndType 12 CONSTANT_Utf8 1 CONSTANT_MethodHandle 15 CONSTANT_MethodType 16 CONSTANT_InvokeDynamic 18 今回使うのは Class, Fieldref, Methodref, String, NameAndType, Utf8 の 6 つとなります。
ConstantPool のエントリ番号は 0 からではなく 1 からスタートする仕様であるため、基本的に 0 から採番されないように 0 番目の配列の要素には null を入れておくと良いです。これらを書きやすくするため予めルーチン化しておきます。
function writeConstantPoolEntry(int $tag, $value): int { global $constantPool; $constantPool[] = [$tag, $value]; return count($constantPool); } function writeConstantPoolClass($value): int { return writeConstantPoolEntry(7, $value); } function writeConstantPoolFieldref($value): int { return writeConstantPoolEntry(9, $value); } function writeConstantPoolMethodref($value): int { return writeConstantPoolEntry(10, $value); } function writeConstantPoolString($value): int { return writeConstantPoolEntry(8, $value); } function writeConstantPoolNameAndType($value): int { return writeConstantPoolEntry(12, $value); } function writeConstantPoolUtf8($value): int { return writeConstantPoolEntry(1, $value); }また、 Constant Pool は以後、検索してエントリの番号を知る必要があるため、検索を簡易に行えるようこれについても予めルーチン化しておきます。
function findEntryIndex(int $tag, $value) { global $constantPool; foreach ($constantPool as $index => $element) { if ($element === null) { continue; } [$entryTag, $entryValue] = $element; if ($tag === $entryTag && $value === $entryValue) { return $index; } } throw new RuntimeException( 'Could not found on the ConstantPool' ); }そして、上の方で説明した Constant Pool の値を書き込んでいきます。
親のクラス名
- java.lang.Object
現在のクラス名
- ここでは HelloWorld としておきます。
System.out.print
- echo を Java に置き換えると
System.out.print
が適任であるため。java.io.PrintStream
- System.out は java.io.PrintStream であるため。
Hello World!
- 出力される文字列
main
- エントリーポイントになるため
<init>
,<clinit>
- あってもなくてもどっちでも動く。動的、静的コンストラクタの役割を担っている。今回は使わない。
Code
- Code Attribute を識別するための文字列
上記から、書き込む文字列は下記のとおりになります。
// 親クラス名を書き込む writeConstantPoolUtf8('java/lang/Object'); // クラス名を書き込む writeConstantPoolUtf8($className); // java/lang/System を書き込む writeConstantPoolUtf8('java/lang/System'); // out を書き込む writeConstantPoolUtf8('out'); // print を書き込む writeConstantPoolUtf8('print'); // java/io/PrintStream を書き込む writeConstantPoolUtf8('java/io/PrintStream'); // print の引数 writeConstantPoolUtf8('(Ljava/lang/String;)V'); // main メソッドの名称を書き込む writeConstantPoolUtf8('main'); // main の引数 writeConstantPoolUtf8('([Ljava/lang/String;)V'); // Code Attribute の識別子である Code を書き込む writeConstantPoolUtf8('Code');Java における名前空間はコード上では
.
で表現されますが、 Constant Pool では/
になります。これはディレクトリであることを示しているからなのだと勝手に推測しています(要出典)そして、メソッドに対する引数の情報を書き込む必要があるため、少し解説をします。
JVM の引数は基本的には 1 文字の識別子からその種類が何であるかを示します。例えばI
であれば Integer です。また、オブジェクトは少し少々特殊でL
のあとに;
が現れるまでの間の文字列をオブジェクト名として認識します。例えばLjava/lang/String;
です。そして、配列は[
の文字数で次元数を表します。これらの組み合わせも Constant Pool に書く必要があります。最終的な書式については
([Ljava/lang/String;)V
のようになります。(
から)
の間が引数であり、その後ろのシグネチャが返り値となります。したがって、下記を書き込む必要があります。
// main における引数を書き込む writeConstantPoolUtf8('([Ljava/lang/String;)V');さらに、これらをエントリの採番から紐付ける必要があります。例えばクラスであることを示すために Class を使用したり、メソッド名と引数を紐付けるために NameAndType を使用したりします。
今回のケースで必要なのは親クラス, カレントクラス, java.lang.System, main, System.out.print です。
まず、親クラスとカレントクラスを書き込みます。// カレントクラス $currentClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, $className)); // 親クラス $parentClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, 'java/lang/Object')); // java.lang.System $javaLangSystemClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, 'java/lang/System')); // print を包括するクラス $printStreamClassEntryIndex = writeConstantPoolClass(writeConstantPoolUtf8('java/io/PrintStream'));そして、 main と引数の情報が対になったものを書き込みます。対であることを示すのは NameAndTypeです。さらに、そのメソッドがどこのクラスのものであるかを示す必要があり、 Methodref を使用する必要があります。
コードで表すと下記のようになります。
// main と引数を対にする $mainAndArgumentsEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'main'), findEntryIndex(1, '([Ljava/lang/String;)V'), ]); // Methodref を書き込む $mainMethodrefEntryIndex = writeConstantPoolMethodref([ $currentClassEntryIndex, $mainAndArgumentsEntryIndex ]);次に System.out フィールドの参照を書き込みます。フィールドは引数はなく返り値のみなので、
()
を省略して、書き込みます。writeConstantPoolUtf8('Ljava/io/PrintStream;');そして、これを out に紐付け、更にその値を java.lang.System に紐付けます。
// System.out を書き込む $outAndPrintStreamEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'out'), findEntryIndex(1, 'Ljava/io/PrintStream;'), ]); // java.lang.System に紐付ける $systemFieldrefEntryIndex = writeConstantPoolFieldref([ $javaLangSystemClassEntryIndex, $outAndPrintStreamEntryIndex, ]);そして、 print 自体の情報を ConstantPool に登録する必要があるため、下記を書き込みます。
// print を紐付ける $printmethodNameAndTypeEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'print'), findEntryIndex(1, '(Ljava/lang/String;)V'), ]); $printMethodrefEntryIndex = writeConstantPoolMethodref([ $currentClassEntryIndex, $printmethodNameAndTypeEntryIndex ]);print を使えるようにクラスに ConstantPool を登録しておきます。
$printMethodrefEntryIndex = writeConstantPoolMethodref([ $printStreamClassEntryIndex, $printmethodNameAndTypeEntryIndex, ]);さらに予め PHP の AST から文字列を抜き出し Constant Pool に追加する処理を行います。
私が行っている php-java というプロジェクトでは動的に追加 (Constant Pool に入れる必要のある文字列が出現したタイミングで追加)をするようにしていますが、ここでは話の流れとして、追加するようにしています。function registerConstantsRecursive(\PhpParser\Node ...$nodes): void { foreach ($nodes as $node) { if (property_exists($node, 'exprs')) { registerConstantsRecursive(...$node->exprs); continue; } if (property_exists($node, 'expr')) { registerConstantsRecursive(...[$node->expr]); continue; } if (property_exists($node, 'value')) { $utf8Index = writeConstantPoolUtf8($node->value); writeConstantPoolString($utf8Index); } } } registerConstantsRecursive(...$ast);(アドベントカレンダーということもあり、だいぶ雑実装ですが、 php-java だともっとまともに書いています。)
最後に、
$constantPool
へ追加された値をバイナリへ書き込んでいきます。foreach ($constantPool as $element) { if ($element === null) { continue; } [$tag, $value] = $element; writeUnsignedByte($tag); switch ($tag) { case 7: // Class case 8: // String writeUnsignedShort($value); break; case 9: // Fieldref case 10: // Methodref case 12: // NameAndType writeUnsignedShort($value[0]); writeUnsignedShort($value[1]); break; case 1: // Utf8 fwrite($handle, $value); break; default: throw new RuntimeException( 'The kind tag is not implemented.' ); } }access_flags を書き込む
access_flags は、
final
であったりpublic
のようなアクセス権限を設定できます。
今回の例だと、コード上においては継承していない(が、実態は java.lang.Object を継承している)ため、public
とsuper
を設定します。access_flags は下記に従って設定します。
Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_FINAL 0x0010 Declared final; no subclasses allowed. ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. ACC_INTERFACE 0x0200 Is an interface, not a class. ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. ACC_ANNOTATION 0x2000 Declared as an annotation type. ACC_ENUM 0x4000 Declared as an enum type.
ACC_PUBLIC
及びACC_SUPER
が該当します。この値を unsigned short で書き込みます。writeUnsignedShort(0x0001 | 0x0020);this_class, super_class を書き込む
次に this_class と super_class を書き込みます。
これは予め ConstantPool に書き込んだである Class を使用し、それらを unsigned short で書き込みます。writeUnsignedShort($currentClassEntryIndex); writeUnsignedShort($parentClassEntryIndex);field_count, interface_count を書き込む
HelloWorld
クラスにはフィールド及びインタフェースは存在しないため、 2 つとも0
と書き込みます。writeUnsignedShort(0); writeUnsignedShort(0);AST にした PHP を解釈してメソッドを書き込む
今回のアドベントカレンダーの趣旨の話をします。 しかし残念ながら、AST にふれるのはここだけで、あっという間に終わってしまいます。まるで、作るのは手間暇がかかるけど、食べるのは一瞬である北京ダックのようです。ちなみに北京ダックは好きです。
まず、メソッドの数を書き込みます。本来は
<init>
も必要ですが、省略しても動くので省略します。
つまり必要なメソッド数は 1 つです。ということで 1 を書き込みます。writeUnsignedShort(1);次に、PHP で AST された値を再度確認してみます。
array(1) { [0]=> object(PhpParser\Node\Stmt\Echo_)#1100 (2) { ["exprs"]=> array(1) { [0]=> object(PhpParser\Node\Scalar\String_)#1099 (2) { ["value"]=> string(12) "Hello World!" ["attributes":protected]=> array(3) { ["startLine"]=> int(2) ["endLine"]=> int(2) ["kind"]=> int(2) } } } ["attributes":protected]=> array(2) { ["startLine"]=> int(2) ["endLine"]=> int(2) } } }通常トップレベルなものは配列であるので、配列を回しつつ処理を分けていく形になります。
余談ですが、php-java は下記のように分けています。今回の場合下記のようなコードになるでしょう。
$operations = ''; foreach ($ast as $stmt) { switch ($type = get_class($stmt)) { case \PhpParser\Node\Stmt\Echo_::class: // echo は PHP だとカンマで区切って複数出力できるため複数形の exprs になっているが、 // 出力は一つのみとして考慮する。 $expr = $stmt->exprs[0]; $reference = null; switch ($expressionType = get_class($expr)) { case \PhpParser\Node\Scalar\String_::class: /** * @var \PhpParser\Node\Scalar\String_ $expr */ $reference = findEntryIndex(8, $expr->value); break; // ... default: throw new RuntimeException( $expressionType . 'is not implemented yet.' ); // ここの解説をします。 } break; // ... default: throw new RuntimeException( $type . 'is not implemented yet.' ); } }このように取得した Node ごとに処理をの役割を分離していく必要があります。
// ...
と書かれている箇所に分岐処理が大量に入ってきます。楽しいね!さて、
// ここの解説をします。
の部分について解説をします。ここにはずばり PHP における echo そのものの実装が入り、それらをすべてオペレーションコードで表す必要があります。では、一体必要なオペレーションコードは何かを考えたときに、上記でも説明した通りのものになります。
- System.out をロードする(オペランドスタックに積む)
- static なフィールドから値をロードするには
getstatic
を使用する- Hello World をロードする(オペランドスタックに積む)
- 定数文字列を Constant Pool からロードするには
ldc
やldc_w
を使用する- print をコールする (PHP の echo は終端に改行コードが入らないため)
- 通常のメソッドの呼び出しには
invokevirtual
を使用する。- return (オペレーションの終了として)
上記はニーモニックですから、オペレーションコードが必要になります。それぞれのペアは JVM Specification にも書かれていますが、下記のとおりです。
ニーモニック オペコード getstatic 0xb2 ldc 0x12 invokevirtual 0xb6 return 0xb1 これらを
$operations
に書いていきます。// getstatic $operations .= pack('Cn', 0xb2, $systemFieldrefEntryIndex); // ldc $operations .= pack('CC', 0x12, $reference); // invokevirtual $operations .= pack('Cn', 0xb6, $printMethodrefEntryIndex); // return $operations .= pack('C', 0xb1);上記が実行するオペレーションコードになります。そしてこれは Code Attribute という属性に分類され、これを紐付ける先であるメソッドのエントリを構築します。
メソッドのエントリは JVM Specification より下記です。
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }書き込むエントリはずばり main メソッドです。まず
access_flags
を書き込みます。
access_flags
は クラスとちょっと異なります。
Flag Name Value Interpretation ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class. ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses. ACC_STATIC 0x0008 Declared static. ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5). ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use. ACC_BRIDGE 0x0040 A bridge method, generated by the compiler. ACC_VARARGS 0x0080 Declared with variable number of arguments. ACC_NATIVE 0x0100 Declared native; implemented in a language other than Java. ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided. ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict. ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. 上記になります。そして main メソッドは
public
とstatic
を使用します。writeUnsignedShort(0x0001 | 0x0008);次に、
name_index
およびdescriptor_index
ですが、これはメソッド名と引数・返り値情報の文字列のエントリー番号になります。writeUnsignedShort(findEntryIndex(1, 'main')); writeUnsignedShort(findEntryIndex(1, '([Ljava/lang/String;)V'));上記のようになります。次に
attribute_count
ですが、 CodeAttribute が入ってくるため1
となります。writeUnsignedShort(1);次に Code Attribute を書き込んでいきます。 Code Attribute は JVM Specification により下記です。
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }まず、
attribute_name_index
は何の属性であるかを示したものが入ります。今回の場合はCode
になります。次にattribute_length
は属性自体の長さです。この値は予め、それ以外の値及び長さを知っている必要があります。
max_stack
及びmax_locals
はオペランドスタックに積まれる最大のスタック数及び、パラメータの数を含めたローカル変数の最大値となります。ぶっちゃけ、足りなくならなければ何でもいいので、両方とも 10 とか雑な値で大丈夫です。php-java はオペレーションコードを見て、サイズを見るようにしています。これには実際にエミュレート的な仕様が必要で、オペレーションコードが走って初めてオペランドスタックの最大の大きさがわかったりするのですが、だいぶ面倒くさいということもあり、説明を省略します。
ちなみに、話が脱線しますが、 if 文の実装にも実はこのエミュレートっぽい処理は必要(もしかしたら別手段はあるかもしれない)、 StackMap テーブルと呼ばれる if 文のブランチ先を定義するものが必要になります。そして、
code_length
はすべてのオペレーションコード及びオペランドのサイズが入ります。
次に、code
にはそのまま code が入ります。
exception_table_length
及びexception_table
は例外処理を適用するオペレーションコードの範囲を定義するものですが、今回に至っては例外処理はないのでexception_table_length
は 0 になります。最後に
attributes_count
及びattributes
は存在しないため、attribute_length
は 0 になります。それを踏まえると下記のようなコードになります。$attribute = ''; // max_stack $attribute .= pack('n', 10); // max_locals $attribute .= pack('n', 10); // code_length $attribute .= pack('N', strlen($operations)); // code $attribute .= $operations; // exception_table_length $attribute .= pack('n', 0); // attributes_count $attribute .= pack('n', 0); // 書き込む writeUnsignedShort(findEntryIndex(1, 'Code')); writeUnsignedInt(strlen($attribute)); fwrite($handle, $attribute);あと処理
そして、最後にクラスファイル自体の
attributes_count
を書き込みます。特に付ける必要はないので0
です。writeUnsignedShort(0);少し長くなってしまいましたが、以上で JVM 言語の開発ができるようになりました。めでたい?
実際に動かしてみる
java HelloWorld上記を実行し
Hello World!
が出れば成功です。コードのまとめ
バイナリはどうなっているかというと、下記みたいな形になります。
00000000: CA FE BA BE 00 00 00 34 00 19 01 00 10 6A 61 76 .......4.....jav 00000010: 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 0A a/lang/Object... 00000020: 48 65 6C 6C 6F 57 6F 72 6C 64 01 00 10 6A 61 76 HelloWorld...jav 00000030: 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 01 00 04 a/lang/System... 00000040: 6D 61 69 6E 01 00 04 43 6F 64 65 01 00 16 28 5B main...Code...([ 00000050: 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E Ljava/lang/Strin 00000060: 67 3B 29 56 01 00 15 28 4C 6A 61 76 61 2F 6C 61 g;)V...(Ljava/la 00000070: 6E 67 2F 53 74 72 69 6E 67 3B 29 56 01 00 15 4C ng/String;)V...L 00000080: 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 java/io/PrintStr 00000090: 65 61 6D 3B 01 00 03 6F 75 74 01 00 05 70 72 69 eam;...out...pri 000000a0: 6E 74 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 nt...java/io/Pri 000000b0: 6E 74 53 74 72 65 61 6D 07 00 02 07 00 01 07 00 ntStream........ 000000c0: 03 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E ....java/io/Prin 000000d0: 74 53 74 72 65 61 6D 07 00 0F 0C 00 04 00 06 0A tStream......... 000000e0: 00 0C 00 11 0C 00 09 00 08 0C 00 0A 00 07 0A 00 ................ 000000f0: 10 00 14 09 00 0E 00 13 01 00 0C 48 65 6C 6C 6F ...........Hello 00000100: 20 57 6F 72 6C 64 21 08 00 17 00 21 00 0C 00 0D World!....!.... 00000110: 00 00 00 00 00 01 00 09 00 04 00 06 00 01 00 05 ................ 00000120: 00 00 00 15 00 0A 00 0A 00 00 00 09 B2 00 16 12 ................ 00000130: 18 B6 00 15 B1 00 00 00 00 00 00 ...........このバイナリは普通に Hello World! と書かれた Java ファイルを javac でコンパイルしたクラスファイルより余分な情報がないので、短い形で済み、かつ
java
コマンドを使用しても動きます。<?php require __DIR__ . '/../../vendor/autoload.php'; use PhpParser\ParserFactory; $className = 'HelloWorld3'; $handle = fopen($className . '.class', 'w+'); // unsigned short, unsigned int で書き込むための関数を用意する function writeUnsignedByte(int $number) { global $handle; fwrite($handle, pack('C', $number)); } function writeUnsignedShort(int $number) { global $handle; fwrite($handle, pack('n', $number)); } function writeUnsignedInt(int $number) { global $handle; fwrite($handle, pack('N', $number)); } // magic writeUnsignedInt(0xCAFEBABE); // minor_version writeUnsignedShort(0); // major_version writeUnsignedShort(52); $constantPool = [null]; function writeConstantPoolEntry(int $tag, $value): int { global $constantPool; $constantPool[] = [$tag, $value]; return count($constantPool) - 1; } function writeConstantPoolClass($value): int { return writeConstantPoolEntry(7, $value); } function writeConstantPoolFieldref($value): int { return writeConstantPoolEntry(9, $value); } function writeConstantPoolMethodref($value): int { return writeConstantPoolEntry(10, $value); } function writeConstantPoolString($value): int { return writeConstantPoolEntry(8, $value); } function writeConstantPoolNameAndType($value): int { return writeConstantPoolEntry(12, $value); } function writeConstantPoolUtf8($value): int { return writeConstantPoolEntry(1, $value); } function findEntryIndex(int $tag, $value) { global $constantPool; foreach ($constantPool as $index => $element) { if ($element === null) { continue; } [$entryTag, $entryValue] = $element; if ($tag === $entryTag && $value === $entryValue) { return $index; } } throw new RuntimeException( 'Could not found on the ConstantPool' ); } // 親クラス名を書き込む writeConstantPoolUtf8('java/lang/Object'); // クラス名を書き込む writeConstantPoolUtf8($className); // java/lang/System を書き込む writeConstantPoolUtf8('java/lang/System'); // main メソッドの名称を書き込む writeConstantPoolUtf8('main'); // Code Attribute の識別子である Code を書き込む writeConstantPoolUtf8('Code'); // main における引数を書き込む writeConstantPoolUtf8('([Ljava/lang/String;)V'); writeConstantPoolUtf8('(Ljava/lang/String;)V'); writeConstantPoolUtf8('Ljava/io/PrintStream;'); writeConstantPoolUtf8('out'); writeConstantPoolUtf8('print'); // java/io/PrintStream を書き込む writeConstantPoolUtf8('java/io/PrintStream'); // 親クラス $currentClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, $className)); // カレントクラス $parentClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, 'java/lang/Object')); $javaLangSystemClassEntryIndex = writeConstantPoolClass(findEntryIndex(1, 'java/lang/System')); $printStreamClassEntryIndex = writeConstantPoolClass(writeConstantPoolUtf8('java/io/PrintStream')); // main と引数を対にする $mainAndArgumentsEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'main'), findEntryIndex(1, '([Ljava/lang/String;)V'), ]); // Methodref を書き込む $mainMethodrefEntryIndex = writeConstantPoolMethodref([ $currentClassEntryIndex, $mainAndArgumentsEntryIndex, ]); // System.out を書き込む $outAndPrintStreamEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'out'), findEntryIndex(1, 'Ljava/io/PrintStream;'), ]); // print を紐付ける $printmethodNameAndTypeEntryIndex = writeConstantPoolNameAndType([ findEntryIndex(1, 'print'), findEntryIndex(1, '(Ljava/lang/String;)V'), ]); $printMethodrefEntryIndex = writeConstantPoolMethodref([ $printStreamClassEntryIndex, $printmethodNameAndTypeEntryIndex, ]); $systemFieldrefEntryIndex = writeConstantPoolFieldref([ $javaLangSystemClassEntryIndex, $outAndPrintStreamEntryIndex, ]); $ast = (new ParserFactory()) ->create( ParserFactory::PREFER_PHP7 ) ->parse( file_get_contents( __DIR__ . '/test.php' ) ); function registerConstantsRecursive(\PhpParser\Node ...$nodes): void { foreach ($nodes as $node) { if (property_exists($node, 'exprs')) { registerConstantsRecursive(...$node->exprs); continue; } if (property_exists($node, 'expr')) { registerConstantsRecursive(...[$node->expr]); continue; } if (property_exists($node, 'value')) { $utf8Index = writeConstantPoolUtf8($node->value); writeConstantPoolString($utf8Index); } } } registerConstantsRecursive(...$ast); writeUnsignedShort(count($constantPool)); foreach ($constantPool as $element) { if ($element === null) { continue; } [$tag, $value] = $element; writeUnsignedByte($tag); switch ($tag) { case 7: // Class case 8: // String writeUnsignedShort($value); break; case 9: // Fieldref case 10: // Methodref case 12: // NameAndType writeUnsignedShort($value[0]); writeUnsignedShort($value[1]); break; case 1: // Utf8 writeUnsignedShort(strlen($value)); fwrite($handle, $value); break; default: throw new RuntimeException( 'The kind tag is not implemented.' ); } } writeUnsignedShort(0x0001 | 0x0020); writeUnsignedShort($currentClassEntryIndex); writeUnsignedShort($parentClassEntryIndex); writeUnsignedShort(0); writeUnsignedShort(0); $operations = ''; foreach ($ast as $stmt) { switch ($type = get_class($stmt)) { case \PhpParser\Node\Stmt\Echo_::class: // echo は PHP だとカンマで区切って複数出力できるため複数形の exprs になっているが、 // 出力は一つのみとして考慮する。 $expr = $stmt->exprs[0]; $reference = null; switch ($expressionType = get_class($expr)) { case \PhpParser\Node\Scalar\String_::class: /** * @var \PhpParser\Node\Scalar\String_ $expr */ $utf8pos = findEntryIndex(1, $expr->value); $reference = findEntryIndex(8, $utf8pos); break; // ... default: throw new RuntimeException( $expressionType . 'is not implemented yet.' ); } // getstatic $operations .= pack('Cn', 0xb2, $systemFieldrefEntryIndex); // ldc $operations .= pack('CC', 0x12, $reference); // invokevirtual $operations .= pack('Cn', 0xb6, $printMethodrefEntryIndex); // return $operations .= pack('C', 0xb1); break; // ... default: throw new RuntimeException( $type . 'is not implemented yet.' ); } } writeUnsignedShort(1); writeUnsignedShort(0x0001 | 0x0008); writeUnsignedShort(findEntryIndex(1, 'main')); writeUnsignedShort(findEntryIndex(1, '([Ljava/lang/String;)V')); writeUnsignedShort(1); $attribute = ''; // max_stack $attribute .= pack('n', 10); // max_locals $attribute .= pack('n', 10); // code_length $attribute .= pack('N', strlen($operations)); // code $attribute .= $operations; // exception_table_length $attribute .= pack('n', 0); // attributes_count $attribute .= pack('n', 0); writeUnsignedShort(findEntryIndex(1, 'Code')); writeUnsignedInt(strlen($attribute)); fwrite($handle, $attribute); // attributes_count writeUnsignedShort(0);※ 上記は本アドベントカレンダーを解説するために作った雑コードであるため、一部説明している手順と異なっている可能性があります?
おわりに
PHP で JVM を作るのは、 JVM Specification に則れば作ることが可能であることがわかりました。
本アドベントカレンダーでは JVM を題材にしていますが、 AST にすることにより別言語を作ることも可能であり、アセンブリに直し、実行ファイルを生成することもぶっちゃけ可能です。
さらに PHP で説明をしていますが、こういった処理ができるのであればどんな言語でも実装可能なのです。夢が広がりますよね。日頃のプロダクションコードとは少し離れて、プログラムと戯れるのもまたありだと思います。ぜひお試しください。そして、 JVM 言語の実装ではなく、 PHP で JVM を実装する方法を以前何回か登壇しています。 JVM の実装そのものに興味があるかたはぜひ見てみてください。
そして、最後になりますが、本を書いていたりするので買ってね。
- 投稿日:2019-12-22T18:14:13+09:00
【Laravel】boolean型へのキャスト
boolean型(理論型)とは
phpのマニュアルによれば、「boolean は、真偽の値を表します。 この値は、TRUE または FALSE のどちらかになります。」とのこと。
つまりboolean型には「TRUE」と「FALSE」の2種類の値にしかならない。
型をキャスト(変換)すると
boolean型へのキャストを行うと次の値は「FALSE」と判定される。
・boolean の FALSE
・integerの0
・floatの0.0
・空の文字列、および文字列の "0"
・要素の数がゼロである配列
・NULL
・空のタグから作成された SimpleXML オブジェクトvar_dump((bool) "false"); // bool(false) var_dump((bool) 0); // bool(false) var_dump((bool) 0.0); // bool(false) var_dump((bool) ""); // bool(false) var_dump((bool) "0"); // bool(false) var_dump((bool) []); // bool(false) var_dump((bool) NULL); // bool(false)これら以外の値はTRUEとなる
判定処理への利用
DBテーブル内にその値があるかどうかの判定
例)指定したIDのユーザが存在するかどうかUser.phpreturn (boolean) $this->where("id",$user->id)->first();first()で取り出したデータはオブジェクト型のデータとなるが、存在しない場合NULL値となるためFALSEを返す。
これだったらis_null()やempty()でいいかも。
- 投稿日:2019-12-22T17:05:09+09:00
自分が調べたPHPのマジックメソッドの機能についてまとめてみた
はじめに
Laravelの仕組みを調べているときに調べたマジックメソッドの機能について、自分の備忘録を兼ねてここにまとめました。
この投稿について疑問や指摘があれば、ぜひコメントお願いします。本題
property_exists メソッド
PHPマニュアル(https://www.php.net/manual/ja/function.property-exists.php)
機能 オブジェクトもしくはクラスにプロパティが存在するかどうかを調べる。
説明property_exists ( mixed $class , string $property ) : bool
パラメータ class:確認するクラス、もしくはクラスのオブジェクトを指定します。 property:プロパティ名を指定します。試し書き
PropertyExists.php//基本型 class Pikotaro { public $pen; protected $pineapple; private $apple; } var_dump(property_exists('Pikotaro', 'pen')); //bool(true) var_dump(property_exists(new Pikotaro, 'pineapple')); //bool(true) var_dump(property_exists(new Pikotaro, 'apple')); //bool(true) var_dump(property_exists('Pikotaro', 'applepen')); //bool(false)PulsPropertyExists.php<?php //同じ名前空間で使うとき namespace Kosakadaimao; class Pikotaro { public $pen; protected $pineapple; private $apple; } var_dump(property_exists('\\Kosakadaimao\\Pikotaro', 'pen')); //bool(true) var_dump(property_exists(new Pikotaro, 'pineapple')); //bool(true) //異なる名前空間で使うとき namespace Kosaka; use Kosakadaimao; var_dump(property_exists('\\Kosakadaimao\\Pikotaro', 'pen')); //bool(true) var_dump(property_exists(new Kosakadaimao\Pikotaro, 'apple')); //bool(true) ?>追記)名前空間の特定クラスのプロパティーを調べたい場合は上記のようにすればできます。
end メソッド
PHPマニュアル(https://www.php.net/manual/ja/function.end.php)
機能 配列の内部ポインタを最終要素にセットする。
説明end ( array &$array ) : mixed
パラメータ array:配列。この配列は参照渡しとなります。関数内で配列の中身を変更するからです。 つまり、ここには配列そのものを渡さなければならず、 配列を返す関数を指定することはできません。 参照渡しできるのは、実際の変数だけだからです。試し書き
End.php//基本型 $kosaka = ['Pikotaro', 'Kosakadaimao', 'Kazuhito']; var_dump($kosaka); //array(3) { [0]=> string(8) "Pikotaro" [1]=> string(12) "Kosakadaimao" [2]=> string(8) "Kazuhito" } echo end($kosaka); //KazuhiroPlusEnd.php//User Contributed Notes から $kosaka[2] = 'Pikotaro'; $kosaka[1] = 'Kosakadaimao'; $kosaka[0] = 'Kazuhito'; var_dump($kosaka); //array(3) { [2]=> string(8) "Pikotaro" [1]=> string(12) "Kosakadaimao" [0]=> string(8) "Kazuhito" } echo end($kosaka); //Kazuhitovar_dump()の結果からわかりますが、連想配列のキーを数字にしても、デフォルトのキーのように昇順で並んでくれないようです。
PlusPlusEnd.php$kosaka = ['Pikotaro', 'Kosakadaimao', 'Kazuhito']; $kosaka[2] = 'Pikotaro'; $kosaka[1] = 'Kosakadaimao'; $kosaka[0] = 'Kazuhito'; var_dump($kosaka); //array(3) { [0]=> string(8) "Kazuhito" [1]=> string(12) "Kosakadaimao" [2]=> string(8) "Pikotaro" } echo end($kosaka); //Pikotaroしかし、デフォルトのキーの値を上書きする形であれば、デフォルトのキーの順番通りに並ぶようです。
終わりに
もっといろんなマジックメソッドの機能を上げようと思ったのですが、疲れたのでここで終わります。
いつかまたこの続きをやりたいなと思います。
- 投稿日:2019-12-22T16:28:17+09:00
Web開発勉強のためWordpressでポートフォリオサイトを開設するまで③〜XAMPPローカル環境構築失敗編〜
XAMPPでローカル環境を構築できたといったな。あれは嘘だ。
いや、嘘ではないんですけど、うまくいったのはインストールまででした。
講座の動画に従って投稿や固定ページの更新をしたところ、
「更新に失敗しました。 エラーメッセージ: 返答が正しい JSON レスポンスではありません。」
というエラーメッセージが表示され、なにもできない状態でした。いまだにデータ交換をテキストやらCSVでやっている仕事ですので、XMLはおろかJSONとは、みたいな状態で、ググってもいまいち解決方法がピンとこない・・・
UdemyのQ&Aを覗いても同様の質問がなかったため、思いきって自ら質問してみました。
すると、すぐに先生自ら回答をくださいました!php.iniのmax_execution_timeをデフォルト設定から300に変更して、Apacheを再起動してみてください。
とのこと。ちなみにこの日は有給予定だったので平日朝の7時ぐらいに書きこみました。
先生ヒマなのかしら。
というわけで、php.iniを探してパラメーターを上書き。
念のためPCも再起動しましたが・・・残念ながら改善せず。
どうするかなあと悩みましたが、この時点でもうローカル環境は諦めるつもりでした。
Railsチュートリアルやろうとした時のAWSアカウント、まだ無料枠残ってるからそれ使って環境構築すればええやん、と。
程度にもよりますが、初心者向けの講座もろもろいじって無料枠超えても1000円は取られないらしいので、今後のAWSの勉強も兼ねてAWSで環境構築することにしました。というわけで、先生にはうまくいかなかったことと、AWSで環境構築する旨をお伝えしました。
先生からも、XAMPP とWordPressのいずれが原因かどうか切り分けるために、AWSやVPSなどでApacheやNginxで試してみるのがよいと思います。
とのご回答が。
めんどくさくなったんですね、わかります。ちなみに先生からlogsフォルダにエラーログ出てない?と聞かれたんですが、残念ながらエラーログは見当たりませんでした。
これでAWSの環境構築を試してうまくいかなかったら、いよいよMAC買うしかないな、と思いました。そんな背水の覚悟で臨んだAWSのWordpress環境構築ですが、下記のサイトを参考にしました。
・https://skillhub.jp/blogs/197結論から言うと、ようやくうまくいきました。
ターミナルの開き方とかAWSの操作面で不安がありましたが、それほど難しくないと思います。
投稿や固定ページの更新も問題なくオーケー。
ようやくスタートラインに立てたかなという感じです。
次回からは、サイトを作っていく中でつまづいた点などを書いていきたいと思います。
- 投稿日:2019-12-22T15:53:56+09:00
Laravel-Excelでセル結合した複数シートを出力する
この記事は、Fusic その2 Advent Calendar 2019 22日目の記事です。
はじめに
LaravelでExcel出力するなら Laravel-Excel を使っている方が多いかと思います。
ただこのライブラリ、PHPSpreadSheet の完全なラッパーではありません。
それ故に少々詰まったので、書き残しておきます。環境
PHP 7.4.1
Laravel 6.9.0
Laravel-Excel 3.1クラス構成
Userモデルを介して取得した全データを、
UsersExportクラスにDIし、各ユーザごとにUsersSheetクラスを作成します。├── app │ ├── Exports │ │ ├── UsersExport.php │ │ └── UsersSheet.php │ └── User.php最終的には各ユーザごとにレイアウトが変動する複雑なExcelファイルの出力が必要だったのですが、
今回はその前段階として、結合セルのある複数シートの出力を目指します。
セル結合
Laravel-Excel では、PHPSpreadsheet の
mergeCells()
がラッパーされていません。
即ちセル結合をする際は、ライフサイクルの中でPHPOffice\PHPSpreadSheet\WorkSheet\WorkSheet
インスタンスを取得する必要があります。Laravel-Excelのライフサイクル
以下4つのイベントが順に発火します。
- BeforeExport
- BeforeSheet
- AfterSheet
- BeforeWriting
公式ドキュメントの記述によると、
PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheet
が生成されるのはBeforeSheetイベントの直前です。
しかし、肝心の取得方法は記載されていません。
PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheet
の取得以下のようにして、BeforeSheetイベント内で取得できました。
あとはPHPSpreadSheetのドキュメントに従うだけです。
$event->getSheet()で取得されるのがMaatwebsite\Excel\Sheet
なのはややこしいですね。namespace App\Exports; use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Events\BeforeSheet; class UsersExport implements WithEvents { /** * @return array */ public function registerEvents(): array { return [ BeforeSheet::class => function (BeforeSheet $event) { $sheet = $event->getSheet()->getDelegate(); // セル結合 $sheet->mergeCells('A2:D2'); }, ]; } }複数シートの出力
続いて、複数シートの出力を行います。
失敗例
Laravel-Excel # Multiple Sheets を参考に、
先ほどのコードにWithMultipleSheets
とsheets()
メソッドを追加してみました。namespace App\Exports; use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Events\BeforeSheet; class UsersExport implements WithEvents, WithMultipleSheets { protected $users; public function __construct($users) { $this->users = $users; } /** * @return array */ public function registerEvents(): array { return [ BeforeSheet::class => function (BeforeSheet $event) { $user = $event->getSheet()->user; $sheet = $event->getSheet()->getDelegate(); // セル結合 $sheet->mergeCells('A2:D2'); $sheet->setCellValue('A2', $user->name); }, ]; } /** * @return array */ public function sheets(): array { $sheets = []; foreach ($this->users as $user) { $sheets[] = new UsersSheet($user); } return $sheets; } }<?php namespace App\Exports; use App\User; class UsersSheet { public $user; public function __construct(User $user) { $this->user = $user; } }これを実行すると…
まっさら!!!
ライフサイクルの確認
各イベントが発火しているのか確認します。
<?php namespace App\Exports; use App\User; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Concerns\WithMultipleSheets; use Maatwebsite\Excel\Events\BeforeExport; use Maatwebsite\Excel\Events\BeforeWriting; use Maatwebsite\Excel\Events\BeforeSheet; use Maatwebsite\Excel\Events\AfterSheet; class UsersExport implements WithEvents, WithMultipleSheets { protected $users; public function __construct($users) { $this->users = $users; } /** * @return array */ public function registerEvents(): array { return [ BeforeExport::class => function (BeforeExport $event) { \Log::debug('==========================================='); \Log::debug('BeforeExport'); \Log::debug('==========================================='); }, BeforeSheet::class => function (BeforeSheet $event) { \Log::debug('==========================================='); \Log::debug('BeforeSheet'); \Log::debug('==========================================='); }, AfterSheet::class => function (AfterSheet $event) { \Log::debug('==========================================='); \Log::debug('AfterSheet'); \Log::debug('==========================================='); }, BeforeWriting::class => function (BeforeWriting $event) { \Log::debug('==========================================='); \Log::debug('BeforeWriting'); \Log::debug('==========================================='); }, ]; } /** * @return array */ public function sheets(): array { $sheets = []; foreach ($this->users as $user) { $sheets[] = new UsersSheet($user); } return $sheets; } }[2019-12-22 03:42:15] local.DEBUG: =========================================== [2019-12-22 03:42:15] local.DEBUG: BeforeExport [2019-12-22 03:42:15] local.DEBUG: =========================================== [2019-12-22 03:42:15] local.DEBUG: =========================================== [2019-12-22 03:42:15] local.DEBUG: BeforeWriting [2019-12-22 03:42:15] local.DEBUG: ===========================================
BeforeSheet
,AfterSheet
イベントが発火していませんね。
UsersSheet
のほうにもデバッグコードを追加してみます。class UsersSheet implements WithEvents { /** * @return array */ public function registerEvents(): array { return [ BeforeExport::class => function (BeforeExport $event) { \Log::debug('==========================================='); \Log::debug('BeforeExport -- Sheet'); \Log::debug('==========================================='); }, BeforeSheet::class => function (BeforeSheet $event) { \Log::debug('==========================================='); \Log::debug('BeforeSheet -- Sheet'); \Log::debug('==========================================='); }, AfterSheet::class => function (AfterSheet $event) { \Log::debug('==========================================='); \Log::debug('AfterSheet -- Sheet'); \Log::debug('==========================================='); }, BeforeWriting::class => function (BeforeWriting $event) { \Log::debug('==========================================='); \Log::debug('BeforeWriting -- Sheet'); \Log::debug('==========================================='); }, ]; } }[2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeExport [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: AfterSheet -- Sheet [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: =========================================== [2019-12-22 04:03:45] local.DEBUG: BeforeWriting [2019-12-22 04:03:45] local.DEBUG: ===========================================
BeforeSheet
,AfterSheet
はどちらもUsersSheet
内での発火になっています。
Sheetイベントなので、確かに各Sheetクラスに委任するのが自然ですね。最終コード
以下のコードで、望んでいたExcelが出力できました。
<?php namespace App\Exports; use App\User; use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\WithMultipleSheets; class UsersExport implements WithMultipleSheets { protected $users; public function __construct($users) { $this->users = $users; } /** * @return array */ public function sheets(): array { $sheets = []; foreach ($this->users as $user) { $sheets[] = new UsersSheet($user); } return $sheets; } }<?php namespace App\Exports; use App\User; use Maatwebsite\Excel\Concerns\WithTitle; use Maatwebsite\Excel\Concerns\WithEvents; use Maatwebsite\Excel\Concerns\WithMultipleSheets; use Maatwebsite\Excel\Events\BeforeSheet; class UsersSheet implements WithEvents, WithTitle { private $user; /** * @param User $user */ public function __construct(User $user) { $this->user = $user; } /** * @return array */ public function registerEvents(): array { return [ BeforeSheet::class => function (BeforeSheet $event) { $sheet = $event->getSheet()->getDelegate(); $sheet->mergeCells('A2:D2'); $sheet->setCellValue('A2', $this->user->name); }, ]; } public function title(): string { return $this->user->name; } }まとめ
今回はセル結合のみを行いましたが、
PHPOffice\PHPSpreadSheet\WorkSheet\WorkSheet
インスタンスの取得方法がわかったので、ほかにもいろいろできそうです。
BeforeSheet以外のイベントも上手く使っていきたいと思います。
- 投稿日:2019-12-22T15:51:49+09:00
データクラスを活用しよう!
はじめに
この記事は、僕が最近同期(めちゃ仕事できるマン)と仕事をしていてコードレビューで指摘をされて「なるほど!」と感動したので
その感動を忘れないようにするための備忘録みたいなものです。奇数と偶数に分ける処理を例に考えてみる
今回は例として、奇数と偶数を分ける処理を書いてみます。
やり方としては…
- 奇数と偶数を分けた結果を同じ配列の異なるキーに格納する
呼び出し側はこんな感じです。
DivideTask.php<?php require_once "./divider.php"; class DivideTask { public function execDivide() { $number = array(1, 2, 3, 4, 5); $divider = $this->getDivider(); $divided_array = $divider->divide($number); if (!empty($divided_array["even"])) { echo "-------偶数--------\n"; foreach ($divided_array["even"] as $even) { echo $even . "\n"; } } if (!empty($divided_array["odd"])) { echo "-------奇数--------\n"; foreach ($divided_array["odd"] as $odd) { echo $odd . "\n"; } } } private function getDivider() { return new Divider(); } } $divideTask = new DivideTask(); $divideTask->execDivide();実際に偶数と奇数に分ける処理はこんな感じです。
Divider.php<?php class Divider { /** * 受け取った数字を偶数と奇数に分ける処理 * @param array $numbers 偶数と奇数の数字が混合している配列 * @return array $result 振り分けが完了した配列 * ex) ["even"=>2, "odd"=>3] */ public function divide(array $numbers) { $result = array(); foreach($numbers as $number) { if($number % 2 === 0) { $result["even"][] = $number; } else { $result["odd"][] = $number; } } return $result; } }-------偶数-------- 2 4 -------奇数-------- 1 3 5動作としてはこれで全然問題は無いのですが、以下の点において問題があります。
divide()
メソッドを呼び出す側は、返ってくる配列の中にeven
とodd
の2つのキーがある前提で呼んでいる- 「素数も追加してよ」と言われた時は新しく配列にキーを追加する必要があり、呼び出し元も呼び出し側も変えなければならない
- 「奇数いらなくなったし消していいよ」と言われた瞬間に
odd
キーが一瞬でレガシーになるまあともかく、今のままだと仕様の変更に脆くレガシーになりやすいということです。
そこで今回はデータクラスを活用してみたいと思います。
リファクタリング
データクラスを作成します。
偶数と奇数をそれぞれクラスプロパティとして定義をし、メソッドはセッターとゲッターだけです。Number.php<?php class Number { public evenNumber = array(); public oddNumber = array(); public function getEvenNumber() { return $this->evenNumber; } public function getOddNumber() { return $this->oddNumber; } public function setEvenNumber($number) { $this->evenNumber[] = $number; } public function setOddNumber($number) { $this->oddNumber[] = $number; } }
Divider
クラスでこのNumber
クラスのインスタンスを作成し、偶数と奇数をそれぞれプロパティに追加をします。
追加が終わった段階で、オブジェクトをそのままDivideTask
クラスにreturnをしてあげます。Dividerクラスをリファクタします。
主な変更点としては以下の通りです。
- 結果を配列に追加するのではなく、オブジェクトに持たせる
- 返却する結果もそのままオブジェクトで返す
Divider.php<?php require_once "./Number.php"; class Divider { /** * 受け取った数字を偶数と奇数に分ける処理 * @param array $numbers 偶数と奇数の数字が混合している配列 * @return Object Number */ public function divideOddOrEven(array $numbers) { /* ①結果を配列ではなくオブジェクトに持たせる */ //$result = array(); $numberObject = $this->getNumberObject(); foreach($numbers as $number) { if($number % 2 === 0) { /* ②数字をSetterを使ってプロパティに格納していく */ //$result["even"][] = $number; $numberObject->setEvenNumber($number); } else { /* ②数字をSetterを使ってプロパティに格納していく */ //$result["odd"][] = $number; $numberObject->setOddNumber($number); } } /* ③返却する結果もオブジェクトをそのまま返す */ //return $result; return $numberObject; } /** * @return Object Number */ private function getNumberObject() { return new Number(); } }処理を呼ぶ側もリファクタしましょう。
DivideTask.php<?php require_once "./divider.php"; class DivideTask { public function execDivide() { $number = array(1, 2, 3, 4, 5); $divider = $this->getDivider(); /* ①返ってくる結果はObjectなので、それをメソッド内で活用する */ //$divided_array = $divider->divideOddOrEven($number); $numberObject = $divider->divideOddOrEven($number); /* ②Getterメソッドを使って現在のNumberクラスのプロパティを取得する */ $evenNumber = $numberObject->getEvenNumber(); $oddNumber = $numberObject->getOddNumber(); if (!empty($evenNumber)) { echo "-------偶数--------\n"; foreach ($evenNumber as $even) { echo $even . "\n"; } } if (!empty($oddNumber)) { echo "-------奇数--------\n"; foreach ($oddNumber as $odd) { echo $odd . "\n"; } } } private function getDivider() { return new Divider(); } } $divideTask = new DivideTask(); $divideTask->execDivide();おわりに
イマサラタウンな話題ですが、ふとした時に忘れがちで油断するとリファクタ前みたいなコード(なんでも配列のキーに追加マン)になってしまうので、改めてこのタイミングでアウトプットをすることで初めて自分のものに出来た感があります。
最後まで読んでくださり、ありがとうございました(`・ω・´)ゞビシッ!!
- 投稿日:2019-12-22T15:51:49+09:00
[PHP]データクラスを活用しよう!
はじめに
この記事は、僕が最近同期(めちゃ仕事できるマン)と仕事をしていてコードレビューで指摘をされて「なるほど!」と感動したので
その感動を忘れないようにするための備忘録みたいなものです。奇数と偶数に分ける処理を例に考えてみる
今回は例として、奇数と偶数を分ける処理を書いてみます。
やり方としては…
- 奇数と偶数を分けた結果を同じ配列の異なるキーに格納する
呼び出し側はこんな感じです。
DivideTask.php<?php require_once "./divider.php"; class DivideTask { public function execDivide() { $number = array(1, 2, 3, 4, 5); $divider = $this->getDivider(); $divided_array = $divider->divide($number); if (!empty($divided_array["even"])) { echo "-------偶数--------\n"; foreach ($divided_array["even"] as $even) { echo $even . "\n"; } } if (!empty($divided_array["odd"])) { echo "-------奇数--------\n"; foreach ($divided_array["odd"] as $odd) { echo $odd . "\n"; } } } private function getDivider() { return new Divider(); } } $divideTask = new DivideTask(); $divideTask->execDivide();実際に偶数と奇数に分ける処理はこんな感じです。
Divider.php<?php class Divider { /** * 受け取った数字を偶数と奇数に分ける処理 * @param array $numbers 偶数と奇数の数字が混合している配列 * @return array $result 振り分けが完了した配列 * ex) ["even"=>2, "odd"=>3] */ public function divide(array $numbers) { $result = array(); foreach($numbers as $number) { if($number % 2 === 0) { $result["even"][] = $number; } else { $result["odd"][] = $number; } } return $result; } }-------偶数-------- 2 4 -------奇数-------- 1 3 5動作としてはこれで全然問題は無いのですが、以下の点において問題があります。
divide()
メソッドを呼び出す側は、返ってくる配列の中にeven
とodd
の2つのキーがある前提で呼んでいる- 「素数も追加してよ」と言われた時は新しく配列にキーを追加する必要があり、呼び出し元も呼び出し側も変えなければならない
- 「奇数いらなくなったし消していいよ」と言われた瞬間に
odd
キーが一瞬でレガシーになるまあともかく、今のままだと仕様の変更に脆くレガシーになりやすいということです。
そこで今回はデータクラスを活用してみたいと思います。
リファクタリング
データクラスを作成します。
偶数と奇数をそれぞれクラスプロパティとして定義をし、メソッドはセッターとゲッターだけです。Number.php<?php class Number { public evenNumber = array(); public oddNumber = array(); public function getEvenNumber() { return $this->evenNumber; } public function getOddNumber() { return $this->oddNumber; } public function setEvenNumber($number) { $this->evenNumber[] = $number; } public function setOddNumber($number) { $this->oddNumber[] = $number; } }
Divider
クラスでこのNumber
クラスのインスタンスを作成し、偶数と奇数をそれぞれプロパティに追加をします。
追加が終わった段階で、オブジェクトをそのままDivideTask
クラスにreturnをしてあげます。Dividerクラスをリファクタします。
主な変更点としては以下の通りです。
- 結果を配列に追加するのではなく、オブジェクトに持たせる
- 返却する結果もそのままオブジェクトで返す
Divider.php<?php require_once "./Number.php"; class Divider { /** * 受け取った数字を偶数と奇数に分ける処理 * @param array $numbers 偶数と奇数の数字が混合している配列 * @return Object Number */ public function divideOddOrEven(array $numbers) { /* ①結果を配列ではなくオブジェクトに持たせる */ //$result = array(); $numberObject = $this->getNumberObject(); foreach($numbers as $number) { if($number % 2 === 0) { /* ②数字をSetterを使ってプロパティに格納していく */ //$result["even"][] = $number; $numberObject->setEvenNumber($number); } else { /* ②数字をSetterを使ってプロパティに格納していく */ //$result["odd"][] = $number; $numberObject->setOddNumber($number); } } /* ③返却する結果もオブジェクトをそのまま返す */ //return $result; return $numberObject; } /** * @return Object Number */ private function getNumberObject() { return new Number(); } }処理を呼ぶ側もリファクタしましょう。
DivideTask.php<?php require_once "./divider.php"; class DivideTask { public function execDivide() { $number = array(1, 2, 3, 4, 5); $divider = $this->getDivider(); /* ①返ってくる結果はObjectなので、それをメソッド内で活用する */ //$divided_array = $divider->divideOddOrEven($number); $numberObject = $divider->divideOddOrEven($number); /* ②Getterメソッドを使って現在のNumberクラスのプロパティを取得する */ $evenNumber = $numberObject->getEvenNumber(); $oddNumber = $numberObject->getOddNumber(); if (!empty($evenNumber)) { echo "-------偶数--------\n"; foreach ($evenNumber as $even) { echo $even . "\n"; } } if (!empty($oddNumber)) { echo "-------奇数--------\n"; foreach ($oddNumber as $odd) { echo $odd . "\n"; } } } private function getDivider() { return new Divider(); } } $divideTask = new DivideTask(); $divideTask->execDivide();おわりに
イマサラタウンな話題ですが、ふとした時に忘れがちで油断するとリファクタ前みたいなコード(なんでも配列のキーに追加マン)になってしまうので、改めてこのタイミングでアウトプットをすることで初めて自分のものに出来た感があります。
最後まで読んでくださり、ありがとうございました(`・ω・´)ゞビシッ!!
- 投稿日:2019-12-22T15:10:21+09:00
Web開発勉強のためWordpressでポートフォリオサイトを開設するまで②〜MAMPローカル環境構築失敗編〜
Wordpressの講座で、ローカルで使用環境を構築するための案内をしていたので、動画に沿って環境構築をしていきました。
PCは動画ではMacを使って案内していましたが、当方Windowsでの環境構築となります。
ちなみに使用PCのスペックは、
・Windows10 HOME バージョン1909
・CPU Cerelon(R) N4000CPU 1.10GHz 1.10GHz
・メモリ 4GB
・ストレージ HDD1TB
です。(ムダにHDD1TBなのがツッコミどころですw)まずMAMPをインストールするよう案内されました。
・https://www.mamp.info/
LAMPをもじったオープンソースで、Webサーバーの環境構築が簡単にできるソフトです。
WindowsだからWAMPだよなーと思いつつ無事にインストールまで完了。動画の案内通り、MAMPのフォルダ内にあるhtdocs内にダウンロードしたWordpress一式を移動。
MAMPを起動させ、ブラウザが起動したのでパスにWordpressのフォルダ名を指定。
この時点でインストール画面が表示されるはずなのですが・・・うまくいかない。
ネットを調べていたところ、wp-config.phpをパスに指定すればオーケーとのことだったのでその通りに操作。
(参考サイト:MAMP・Wordpressセットアップの詳細な手順はこちらのサイトで丁寧に解説されています。)
・https://bazubu.com/how-to-insall-wp-in-mamp-23798.html
無事にセットアップ画面が表示されたので、データベースを作成。
Wordpressのセットアップ手順に従い、データベース名やサイト名を入力。
無事にダッシュボード画面が開きました!わーぱちぱちぱち!え、ローカル環境構築失敗してないじゃないかって?
そう、タイトル詐欺です。ここまでは。
とりあえずその日はそこまでで操作を終え、翌日にMAMPを起動したところ・・・あれ?Apacheに緑のランプがつかないぞ?
まあいいかと気にせずWordpressを開こうとするもエラーに。そりゃそうだ。
ネットで調べていたらポート番号が別のアプリケーションと被ってダメなことがあるらしい。
ならばとポート番号をいろいろ変更するも・・・やはりApacheのグリーンライトは点灯せず。
うーむ、困ったなあ・・・と思っていたところ、そういえばUdemyには講座ごとにQ&Aが存在していたことを思い出す。
Q&Aを眺めていたところ、同様の質問が何件か。やはりけっこうある現象なのね。
講師の先生が自ら原因を探っていくも、最終的にはXAMPPを使っては?という回答に大半が落ち着いている。めんどくさくなったんですね、わかります。ということで、自分もXAMPPをインストールしました。
・https://www.apachefriends.org/jp/index.html
インストール手順は下記のサイトを参考にしました。
・https://bazubu.com/xampp-wordpress-23795.htmlMAMPよりボタンとか項目が多いので戸惑うかもしれませんが、基本の流れは一緒なのですんなりインストールできました。
念のためPCを再起動しても問題ないか確認したところ、無事にWordpressのダッシュボード画面を開くことができました!というわけで、MAMPでうまくいかなかった皆様、その時はXAMPPをインストールしましょう!
- 投稿日:2019-12-22T14:46:41+09:00
【入門】Laravel×Vue.js②〜実装編〜
はじめに
PHPのフレームワークであるLaravelで作成したアプリケーションに
JavaScriptのフレームワークであるVue.jsを連携させる方法について説明します。
簡単な機能を実装します。自分のコンポーネントを登録する方法について説明します
実装する機能
メッセージを表示して、ボタンを押すと、メッセージを反転するコンポーネントの作成
まず、
resources/js/components/
内にHelloComponent.vue
を作成してください
そして、内容を以下のようにしてくださいresources/js/components/HelloComponent.vue<template> <div id="app"> <p>{{ message }}</p> <button v-on:click="reverseMessage">Reverse Message</button> </div> </template> <script> export default { data:function(){ return { message: 'Hello Vue!', }; }, methods: { reverseMessage: function () { this.message = this.message.split('').reverse().join('') } } } </script>コンポーネントの登録
上で作成したコンポーネントを登録しましょう
resource/js/app.jsrequire('./bootstrap'); window.Vue = require('vue'); Vue.component('example-component', require('./components/ExampleComponent.vue').default); Vue.component('hello-component', require('./components/HelloComponent.vue').default);ビューファイルへの組み込み
ビューファイルの追加したい時に下記を記述してください
hello/index.blade.php<div id="app"> <hello-component></hello-component> </div>以上で実装完了です。
疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!
- 投稿日:2019-12-22T14:27:52+09:00
phpでcookieやurlを使わずにsession_start()でセッション管理する
phpでcookieを使わずに、session idを手動で設定し、session_start()する方法。
こんな方向けの話
- アクセス元からsessionを表す or 作ることができる文字列がpostされる(alexaスキルや、google assistatアプリ、line botなどの受け側をphpで作る場合など)
- セッション管理だけの為にRDBMS、nosql系を使いたくない → Webサーバのsession機能を使いたい。
制限
セッションIDとして使えるのは「英数字」「カンマ」「ハイフン」のみです。
コード
<?php ini_set( 'session.use_cookies', 0 ); ini_set( 'session.use_only_cookies', 0 ); $session_id = "session-id-text"; //この文字列がセッションidとなる session_id($session_id); session_start(); ?>サンプル
test1.php
<?php ini_set( 'session.use_cookies', 0 ); ini_set( 'session.use_only_cookies', 0 ); $session_id = "session-id-text"; //この文字列がセッションidとなる session_id($session_id); session_start(); $_SESSION['test'] = "yamada"; //セッションに保存してみる ?>test2.php
<?php ini_set( 'session.use_cookies', 0 ); ini_set( 'session.use_only_cookies', 0 ); $session_id = "session-id-text"; //この文字列がセッションidとなる session_id($session_id); session_start(); var_dump($_SESSION); ?>
- 投稿日:2019-12-22T13:15:43+09:00
foreachをlistを使ってかっこよく取得する
よく使う使い方
$fruitList = [ '1' => 'リンゴ', '2' => 'レモン', '3' => 'バナナ' ]; foreach($fruitList as $index => $name){ dd($index. ':'. $name); }1:リンゴと出力されました。
一般的な使い方として、$key => $valueの形で使用します。
これが例えば、$valueのほうが配列になった場合を考えてみましょう。
valueが配列の場合
添字配列の場合
$fruitInfoList = [ '1' => ['リンゴ', '100円'], '2' => ['レモン', '200円'], '3' => ['バナナ', '300円'] ]; foreach($fruitInfoList as $index => $fruitInfo){ dd($index. ':'. $fruitInfo[0]. ':'. $fruitInfo[1]); }これでは、0と1という添字を書いて、取得するのはちょっとみにくいですね。
連想配列にしてみます。連想配列の場合
$fruitInfoList = [ '1' => [ 'name' => 'リンゴ', 'price' => '100円' ], '2' => [ 'name' => 'レモン', 'price' => '200円' ], '3' => [ 'name' => 'バナナ', 'price' => '300円' ], ]; foreach($fruitInfoList as $index => $fruitInfo){ dd($index. ':'. $fruitInfo['name']. ':'. $fruitInfo['price']); }取得の際は、nameとpriceと意味付けができて見やすくなりましたが、宣言する際が少し大変で、長いし冗長ですね。
list関数を使ってみましょう。listを使用した際
listは、定義された変数の順番に配列の要素が代入されます。さっそく見てみましょう
https://www.sejuku.net/blog/24406$fruitList = [ '1' => ['リンゴ', '100円'], '2' => ['レモン', '200円'], '3' => ['バナナ', '300円'], ]; foreach($fruitList as $index => list($name, $price)){ dd($index. ':'. $name. ':'. $price); }
だいぶすっきりしました。
foreachの中で、$fruitInfoと命名していたものが消えました。(変数を命名するのは難しいので、避けられるなら避けたほうが良いときもある)
- 投稿日:2019-12-22T11:55:18+09:00
ランサーズ創業時から今まで12年分のコミットをビジュアライズしてみた
はじめに
ランサーズ AdventCalendar 21日目担当の @sayanet です。
ふだんはtoBの LancersEnterprise プロダクトの開発チームリーダーとして、プロジェクトマネジメントを行っています。最近プロダクトマネジメントもはじめました。
ハイ、さっそく本題にはいります!
私がランサーズに入社したのは2014年4月で、その頃はまだメンバーは50名に満たない規模でした。
今回、上場という節目においてここまでの軌跡をなにかの形で残しておきたいなと思い、主に Lancers の開発を行っているリポジトリで創業から今までの約12年(2008-2019)にわたるすべてのコミットをビジュアライズしてみました。
今回は公開用として、創業者兄弟である秋好社長&聡さんと著者以外はイニシャルにしています。主にランサーズhttps://t.co/ROuI6QmWGBのプロダクト開発を行っているリポジトリで創業から今まで約12年(2008-2019)にわたるすべてのコミットをビジュアライズしてみました
— ぶなっしー(おかんエンジニア)? (@sayanet) December 22, 2019
創業者兄弟である秋好社長・聡さん以外イニシャルにしていますが過去の膨大なコミットが今に繋がっていることを再認識し感謝? pic.twitter.com/llnQfUsoNPつくりかた
1. コミットログの取得
git shortlog
を利用してユーザーごとのコミット数を取得します。例えば今月のコミットをユーザーごとにリストアップしたい場合はこんなかんじです。
git shortlog -sn --no-merges --since='date +%Y/%m/01' --until='date +%Y/%m/%d'オプション説明
-s
: ユーザー毎にサマライズ
-n
: コミット数多い順にソート
--no-merges
: マージコミットは除外
--since
: 対象期間いつから
--until
: 対象期間いつまでさてさて、今回は年ごとにサマった述べコミット数がほしいので、創業から今年まで年単位でループをまわします。
それぞれの年に過去合計値をセットしたいのでsinceオプションは2008年固定にしておくのがミソ。for ($year=2008; $year<=2019; $year++) { echo $year . "\n"; $command = sprintf("git shortlog -sn --no-merges --since='2008/01/01' --until='%d/12/31'", $year, $year); system($command); }2. データを整形する
1で取得したユーザーごとのコミット数をスプレッドシートに貼り付けて整形。年とユーザー名をマトリクス表で表現していきます。
こちらがベースとなるデータ。年とユーザー名を結合させて下ごしらえ。
※ yosuke->秋好社長、satoshi->聡さん です。他の方はマスキング
年とユーザー名をもとにVLOOKUP使ってマトリクス表のシートにプロット。
ここで予期せぬ事態。同一人物でもコミット時のユーザー名が異なることが多々あり、名寄せするのが大変でした。仕組み化するならメアドを使って名寄せするとかうまいことやりたい。さて、ここまでできればデータ整形完了!
このタイミングでユーザー名をイニシャル化にしています。3. ビジュアライズする
ビジュアライズにはFlourishのBar chart raceを使いました。
https://app.flourish.studio/@flourish/bar-chart-race/9Dataにスプレッドシートの内容をそのまま貼ればOKというお手軽さ。職種で色分けしたかったのでカテゴリー追加しています。
Previewモードで件数や速さなど表示の設定ができます。
動きを確認しながら調整できてトテモ便利![]()
デキアガリ\(^o^)/
APIも公開されていて捗りそうです
https://app.flourish.studio/@flourish/bar-chart-race/9#apiまとめ
後半のデザイナーT.Tさんの伸びがすごかったです。コミット数が多い方は総じて仕事のスピードが早いなという印象。ちなみにT.Tさんからはプログラマーはプログラムを書くな! 有能なプログラマーはプログラムを書かないリンクをそっと渡されましたがそれもまたひとつの謙虚スタイル。リスペクト。
エンジニア出身の社長が今でもTOP17に残っているのを見て、ほんとうに創業時はめちゃくちゃエンジニアフルコミットだったんだなと思ったり、私が入社した2015年頃にメンバーが一気に増えてベンチャーの勢いを感じたり、最近入社したメンバーがガンガンコミット伸ばしていることに気づけたりとなかなかの感慨深さがありました。
もちろん、コミットの単位は時期や実装者・チームによって異なるし、fix:typoなどもあるため多いからよいとは一概にいえませんが、純粋にこれだけコミットしたのすごいな〜という意味合いで見ています。
ソースコードは日々刷新されますがコミットに込めた気持ちとビジョンが受け継がれて今のランサーズがあるんだなと再認識。日々の積み重ねって偉大ですね。
年末なので、一年の振り返りとして月ごとに出してみても面白そうだし、毎日眺めてがんばったなーとムフフするのも楽しそう。
それではみなさん、良いエンジニアライフを!
- 投稿日:2019-12-22T10:36:17+09:00
【IT専門学生の就活】僕がこれからやること
自己紹介
僕はIT系の専門学生1年で、現在はWeb系の企業を目指して学習を進めています。今回この記事を書こうと思った理由は、就活が迫ってきたのですが学校にはWeb系企業の求人がなく、ポートフォリオを作成し、技術を証明する必要があると考えたからです。
今までやったこと
僕がWeb開発を始めたのは大学受験に落ちた日からです。もちろん最初は Progateさんから始めました:)
そしていつの間にかIT系の専門学校に
独学
- ポートフォリオページ作成(HTML&CSS)
- Ruby
- Ruby on Rails
- AWS(EC2)
- adobeXD
- PaizaスキルチェックBランク(Java,Ruby)
学校
- MySQL
- CentOS
- GUI電卓作成(JavaFX)
- GUIポーカー作成(JavaFX)
- シューティングゲーム作成(JavaFX)
- 基本情報技術者試験合格
- 成績全てA(1年前期)
書き途中に気付いたのですが、自主学習時間の割に形になっているものが少ないなと思ったので、作成物や資格合格を目標として取り組むことにします。
これからやること
自分でやることで何をすればよいかを整理するために書くので、学校のみで勉強するものは省いています。
就活までにやること(専門1年)
先頭の番号は優先順位です
- Webアプリ作成(Rails&HTML&CSS&BT) [独学]
- PaizaスキルチェックAランク(Python) [独学]
- ポートフォリオサイト作成(HTML&CSS&BS) [独学]
- 成績全てA [自宅+学校]
- 応用情報技術者試験合格 [自宅+学校]
プラスしてやること(専門2年)
まだ具体的に定まっていないため優先順位はつけられないのでつけません。
- Webアプリ作成(Laravel)
- Webアプリ作成(Java Spring Framework)
- TOEIC600点?(受けたことないからわからない)
ここからは趣味でやりたいことかもしれません。余裕があればやります
- 高校数学復習+大学数学
- 機械学習・人工知能
- Pythonでいろんな分野で遊ぶ
タスクに分解
やることを明確にするため、目標をタスクに分解していきます。
1.Webアプリ作成(Rails)
- Ruby
- Rails
- HTML・CSS
- Git
主役はRailsですがもちろん基礎文法としてRuby、デザインとしてHTML・CSS、バージョン管理としてGitをやる必要があります。デプロイはHerokuで行います(デプロイについてはRailsチュートリアルで学習できます↓)
タスク
【Ruby】
・[完了]Progate Rubyコース
・[完了]Paiza Rubyコース
・[完了]プロを目指す人のためのRuby【Rails】
・[完了]Progate Railsコース
・Railsチュートリアル【HTML・CSS】
・[完了]Progate HTML&CSS【Git】
・Udemy Git:はじめてのGitとGitHub2.PaizaスキルチェックAランク
- Python
- アルゴリズム
Pythonは応用情報と組み合わさった良い講座があったのでそれを見ることにします。アルゴリズムは演習もやりながらできるものがQiitaにたくさんあったのでそれをやることにします。
タスク
【Python】
・基本情報技術者試験+応用情報技術者試験+Python+SQL【アルゴリズム】
・AtCoder に登録したら次にやること 過去問精選10問
・AtCoder 版!蟻本 (初級編)3.ポートフォリオサイト作成(HTML&CSS&BS)
サーバーサイドを見せるためのポートフォリオ程度のHTML・CSSで新たに学習することはないので適当に調べながらやることにします。
4.成績全てA
テスト範囲が発表されてから考えます
5.応用情報技術者試験合格
基本情報で基本的なことは覚えたのでやるべきことは過去問演習だけです
タスク
・過去問演習
おわりに
応用情報合格したいな...?
- 投稿日:2019-12-22T01:32:03+09:00
Moodle 3.8 マニュアル - Ubuntu のための step-by-step インストールガイド
Ubuntu のための step-by-step インストールガイド
注:このドキュメントは、PHP 7.2 をもつ Ubuntu 18.04 に Moodle 3.7 をインストールすることについてのものです。
内容
1 始める前に
2 ステップ 1:Ubuntu をインストールする
2.1 なぜ我々は Ubuntu desktop よりも Ubuntu を好むのか
2.2 なぜ我々は Ubuntu 14.04 よりも Ubuntu 16.04 よりも Ubuntu 18.04 を好む(あるいは好まない)のか
2.3 手順
3 ステップ 2:Apache/MySQL/PHP をインストールする
4 ステップ 3:追加のソフトウェアをインストールする
5 ステップ 4:Moodle をダウンロードする
6 ステップ 5:ローカルリポジトリを /var/www/html/ にコピーする
7 ステップ 6:MySQL サーバをセットアップする
8 ステップ 7:完全なセットアップ
8.1 moodledata のパスを変更する
8.2 データベースのタイプ
8.3 データベースの設定
8.4 環境のチェック
8.5 次 次 次
8.6 サイト管理者のアカウントを作成する
8.7 インストールの完了
8.8 忘れないで
9 インストール後のシステムのパス
10 提案:Zend OpCache/Change Document Root を有効にする
11 moodle のために config.php を編集する
12 一台の Ubuntu サーバにおいて複数の Moodle ブランチをホストする
12.1 例 1
12.2 例 2
13 関連項目1 始める前に
パスワード(とユーザ名)を書き留めておくのはいい考えです。Ubuntu と Moodle に使うために必要となるでしょう。
- Ubuntu の root パスワード
- Moodle が使用するであろう MySQL のユーザ名とパスワード
- Moodle のメインのユーザ名とパスワード
- 追加の管理者のユーザ名とパスワード
2 ステップ 1:Ubuntu をインストールする
2.1 なぜ我々は Ubuntu desktop よりも Ubuntu を好むのか
- ほとんどの IT プロフェッショナルは、安全でありハッキングされにくいためにコマンドラインインターフェース(CLI)サーバを使用することを好みます。
- アマチュアユーザはグラフィカルインターフェース(デスクトップ)を使用するのを簡単だと思うでしょう。
- Moodle サーバを実験のためにローカルで使用したいだけならば、Ubuntu のデスクトップバージョン(64 ビットが好まれます)をインストールするのを好むかもしれません。
- CLI だけのサーバをインストールしてあとで後悔するならば、単にグラフィカルデスクトップを簡単に追加するだけです。
注:ほとんどのエキスパートには推奨されませんが、'Ubuntu desktop' をインストールするために、'sudo tasksel' または 'sudo apt install ubuntu-desktop' コマンドを発行してグラフィカルユーザインターフェース(デスクトップ)をインストールできるでしょう。しかし注意して使用してください:GUI は予想するようには現れず、CLI すら使えなくなるかもしれません。まずテストマシンで試してください。
2.2 なぜ我々は Ubuntu 14.04 よりも Ubuntu 16.04 よりも Ubuntu 18.04 を好む(あるいは好まない)のか
- All three are LTS (Long Term Service) releases. amd64 edition preferred. http://www.ubuntu.com/download
- Ubuntu Server 18.04 has all the required packages (comes default with Apache2, PHP 7.2, MySQL 5.7). Ubuntu Server 18.04 has some minor differences from 16.04 and 14.04, mainly PHP. This document has been updated to reflect those changes.
- Moodle 3.0.1 onwards can use PHP 7.0. Moodle 3.7 requries PHP 7.1.
- But beware that if you are using external authentication or enrollment plug-ins, please note that at the moment of Moodle 3.0.1 release (December 2015) neither MSSQL nor SQLSRV are available under php7. So, anybody using SQL*Server as primary database or remote auth/enrol plugin should not move to PHP 7 at all ! Also, depending of your configuration, some extensions (memcached can be installed, redis, mongodb, xmlrpc...) maybe missing or work in progress for your distribution, triple check the exact availability for your OS.
2.3 手順
Ubuntu has a well known issue with its automatic updates filling up the /boot directory until automated updates start to fail and automated removal of old kernel files from /boot is impossible. Because of this you should consider installing Ubuntu with a /boot directory of around 5Gb and putting some automated clean up in place. More info can be found here - [1]
You can use either VI (lightweight editor) or VIM (heavyweight editor), however, if you wish to use VIM you will need to install it
sudo apt-get install vim
- VI or VIM Commands
To edit a file press "Insert" Key
To finish editing press "Esc" Key
To write the file press ":w"
To Exit the editor press ":q"
You can also write and quit ":wq"
- In Ubuntu, the standard user, the account you created during the install, does not have rights to install/write to many of the directories. In the below tutorial we will be using the term "sudo" which stands for "super user do" before most of the commands.
3 ステップ 2:Apache/MySQL/PHP をインストールする
Note: Moodle 3.0.1 introduced support for PHP 7.0 and we will be using PHP 7.2 in this tutorial
Open up Terminal and install the following;
sudo apt install apache2 mysql-client mysql-server php libapache2-mod-php
Run 'sudo mysql_secure_installation' to set the root password for mysql - please, please my dear friends, WRITE IT DOWN and spare yourself some grief, you will need it in step 6.
4 ステップ 3:追加のソフトウェアをインストールする
sudo apt install graphviz aspell ghostscript clamav php7.2-pspell php7.2-curl php7.2-gd php7.2-intl php7.2-mysql php7.2-xml php7.2-xmlrpc php7.2-ldap php7.2-zip php7.2-soap php7.2-mbstring
Restart Apache so that the modules are loaded correctly
sudo service apache2 restart
We will be using Git to install/update the Moodle Core Application
sudo apt install git
5 ステップ 4:Moodle をダウンロードする
Setup your local repository and download Moodle, We will use /opt for this installation.
- Git is what is called a "version control system". By using git it will much easier down the road to update the moodle core application. Within Step 5 there is a little more detail on why we put the moodle core application code in the /opt directory.
cd /opt
Download the Moodle Code and Index
sudo git clone git://git.moodle.org/moodle.git
Change directory into the downloaded Moodle folder
cd moodle
Retrieve a list of each branch available
sudo git branch -a
Tell git which branch to track or use
sudo git branch --track MOODLE_37_STABLE origin/MOODLE_37_STABLE
Finally, Check out the Moodle version specified
sudo git checkout MOODLE_37_STABLE
6 ステップ 5:ローカルリポジトリを /var/www/html/ にコピーする
sudo cp -R /opt/moodle /var/www/html/
sudo mkdir /var/moodledata
sudo chown -R www-data /var/moodledata
sudo chmod -R 777 /var/moodledata
sudo chmod -R 0755 /var/www/html/moodle
- Explanation:
Since we setup a local repository in the previous step, you will copy it to your webroot after any updates and making changes. Having your local repository outside of the webroot, like we have in /opt, you will be able to prepare and stage your upgrades in a more efficient manner. For example, you want to make some changes or add some plug-ins, you would download the plugin and copy it to your local moodle repository. After you have added the plug-in and any other changes you might have made you will need to edit the file located in /opt/moodle/.git/info/exclude. Within that file you want to tell git which files/folders to exclude when it pulls down the updates when you run your next "sudo git pull". An example entry would be the certificate mod located in /opt/moodle/mod/certificate so within the exclude file you want to add "/mod/certificate" below the last comments. You would add additional entries, 1 per line, for each plug-in or file you might have changed. If I were to change the favicon.ico file you would just add "favicon.ico" to the exclude file. Now when you run "sudo git pull" to update moodle to the latest version it will ignore those files and directories and just update the core moodle code. Before copying to your webroot to upgrade you want to make sure and download and copy over the latest versions of the plug-ins you might have added.
7 ステップ 6:MySQL サーバをセットアップする
First we need to change the default storage engine to innodb and change the default file format to Barracuda, this is a new setting compared to previous versions. You also need to set innodb_file_per_table in order for Barracuda to work properly. Ref: https://dev.mysql.com/doc/refman/5.6/en/innodb-compression-usage.html
You should not need to make innodb the default storage engine anymore, the latest version of Moodle will select it automatically during install. It is always a good idea to make it default anyway. You do however need to set the default file format!
If you chose to use VIM instead please substitute vi for vim
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
Scroll down to the [mysqld] section and under Basic Settings add the following line under the last statement. if you want to add you have to press the "insert" button on your keyboard. this is usually above the "delete" button. this allows you to add some text.
default_storage_engine = innodb
innodb_file_per_table = 1
innodb_file_format = Barracuda
In order to save my.cnf using the editor, press the Esc (Escape) key, type the following in sequence which will save :w then close the editor :q
:w
:q
Restart MySQL Server for changes to take affect
sudo service mysql restart
Now we need to create the Moodle database and the Moodle MySQL User with the correct permissions
Use the password you created in step 1
sudo mysql -u root -p
mysql>
CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Note: Use 'utf8mb4' for full range (4-byte) support of UTF-8, including Emoji ('utf8' only supports 3-byte). You will be compliant by Moodle admin page if you don't use 'utf8mb4' here.
Where it says "moodledude" and "passwordformoodledude" you should change to the username and password of your choosing.
mysql>create user 'moodledude'@'localhost' IDENTIFIED BY 'passwordformoodledude';
mysql>
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,CREATE TEMPORARY TABLES,DROP,INDEX,ALTER ON moodle.* TO moodledude@localhost IDENTIFIED BY 'passwordformoodledude';
mysql>
quit;
Note - If you are using MySQL 5.6+ and when you issue the create user and get an error about the password hash you need to adjust the password to use the hash value
You can get this by following the below
mysql>SELECT password('passwordformoodledude');
This will print the hash of the password like *AD51BAFB2GD003D3480BCED0DH81AB0BG1712535, you will want to use this in the IDENTIFIED BY ' part
8 ステップ 7:完全なセットアップ
- Note - If you are not comfortable using terminal to create the config.php file that needs to be created when going through the installer, you should temporarily make the webroot writable by doing the following:
sudo chmod -R 777 /var/www/html/moodle
After you have ran the installer and you have moodle setup, you NEED to revert permissions so that it is no longer writable using the below command.
sudo chmod -R 0755 /var/www/html/moodle
Open your browser and go to
http://IP.ADDRESS.OF.SERVER/moodle
Follow the prompts:
8.1 moodledata のパスを変更する
/var/moodledata
8.2 データベースのタイプ
Choose: mysqli
8.3 データベースの設定
Host server: localhost
Database: moodle
User: moodledude (the user you created when setting up the database)
Password: passwordformoodledude (the password for the user you created)
Tables Prefix: mdl_
8.4 環境のチェック
This will indicate if any elements required to run moodle haven't been installed.
8.5 次 次 次
follow prompts and confirm installation
8.6 サイト管理者のアカウントを作成する
Create your moodle user account which will have site administrator permissions.
The password you select has to meet certain security requirements.
8.7 インストールの完了
Congrats! You can now start using Moodle!
8.8 忘れないで
If you made the webroot writable, revert permissions
sudo chmod -R 0755 /var/www/html/moodle
9 インストール後のシステムのパス
After installing Moodle you should set the system paths, this will provide better performance VS not setting them. Each entry in Moodle will have it's explanation.
Navigate, on the moodle webpage, to Site Administration > Server > System Paths
Input the following;
Path to du: /usr/bin/du
Path to apsell: /usr/bin/aspell
Path to dot: /usr/bin/dot
Save Changes
- Optional if you do not already have an AntiVirus Solution
We also installed ClamAV in Step 3 so we need to set the path in Moodle
1st Create the Quarantine Directory
sudo mkdir /var/quarantine
Change Ownership
sudo chown -R www-data /var/quarantine
Navigate to Site Administration > Plugins > Antivirus plugins > Manage antivirus plugins
Enable ClamAV antivirus
Click on Settings
Set the proper settings
Save changes
In previous Moodle branches: Check "Use ClamAV on uploaded files" ClamAV Path : /usr/bin/clamscan Quarantine Directory : /var/quarantine
Save Changes
10 提案:Zend OpCache/Change Document Root を有効にする
- Since we have installed Ubuntu Server 14.04LTS, we can use the built-in PHP OPcache, https://docs.moodle.org/26/en/OPcache
Within the link above, https://docs.moodle.org/26/en/OPcache add the recommended settings to your 05-opcache.ini file. Again, substitute vi with vim and remember to use the correct key squences from the introduction.
sudo vi /etc/php5/apache2/conf.d/05-opcache.ini
NOTE: In Ubuntu 16.04 opcache.ini is located in:
/etc/php/7.0/mods-available/opcache.ini
Restart Apache for changes to take affect.
sudo service apache2 restart
That's it for the Zend OpCache!
You can also install a GUI to view the status of your Zend OpCache, not recommended on production servers.
cd /var/www/html/moodle/
Download the PHP Script to your Moodle directory, you should also add this file to /opt/moodle/.git/info/exclude file so it does not get removed when upgrading your installation.
sudo wget https://github.com/rlerdorf/opcache-status/blob/master/opcache.php
Visit http://ip.address.of.server/moodle/opcache.php
If you do not want your end users to type http://yourserver/moodle and just want them to navigate to http://youserver you will need to edit the site configuration for Apache which will tell Apache to use the /var/www/html/moodle as the root directory and not /var/www/html
Open up the Apache sites config and change the document root
sudo vi /etc/apache2/sites-available/000-default.conf
On the line where DocumentRoot is;
Change From: DocumentRoot /var/www/html
Change To: DocumentRoot /var/www/html/moodle
:w
:q
Restart Apache for changes to take affect.
sudo service apache2 restart
Important note!
If you have already installed Moodle then you should make the below changes.
11 moodle のために config.php を編集する
In the installation instructions, one of the suggested settings for 'webroot' is 'localhost'. This is fine if all you want to do is some local testing of your new Moodle installation. If, however, you want to view your new installation from another machine on the same local area network, or view your site on the Internet, you will have to change this setting:
For local testing, 'localhost' is fine for the webroot ($CFG->wwwroot in config.php). If you want to test your site from other machines on the same local area network (LAN), then you will have to use the private ip address of the serving machine, (e.g. 192.168.1.2/moodle) or the network name of the serving computer (e.g. network_name_of_serving_machine/moodle) as the web root. Depending on your LAN setup, it may be better to use the network name of the computer rather than its (private) ip address, because the ip address can and will change from time to time. If you don't want to use the network name, then you will have to speak to your network administrator and have them assign a permanent ip address to the serving machine. Finally, if you want to test your new installation across the internet, you will have to use either a domain name or a permanent (public) ip address/moodle as your web root. To handle both types of access, see masquerading.
Edit config.php for Moodle
cd /var/www/html/moodle sudo vim config.php
Hit the "insert" button on your keyboard, make then changes you need to make. Then press "escape" and type the following in to quit and to save changes (excluding quotation marks): ":wq"
Under $CFG->wwwroot change to http://ip.address.of.server instead of http://ip.address.of.server/moodle
12 一台の Ubuntu サーバにおいて複数の Moodle ブランチをホストする
- This is very useful for the language pack maintainers to test translations in several Moodle branches.
- It is also very useful for developers to test their plugins in different Moodle branches.
- Just create a folder for each instance inside the web folder and that would be enough.
- To access the sites you only need to add the folder to localhost URL: http://localhost/moodle31
You can have an instance for each version from 1.9 to 3.1
You do need a separate data folder for each instance and a separate database (You can use phpmyadmin to set your database, but that's not necessary), add each instance in its own folder, and carry on as above. You can also host another service (eg, Mahara) in it's separate folder.
12.1 例 1
- So, one example folder tree on one Linux laptop (an actual server would be more) may look something like:
var
--www
----maharadata
----moodlecleandata
----moodlestabledata
----moodlemasterdata
----moodletestingdata
----uswmoodledata
----html
------mahara
------moodleclean
------moodlestable
------moodlemaster
------moodletesting
------uswmoodle12.2 例 2
- Have several sandboxed Moodles on a single (CentOS X) server all of different versions .. only the ones that are supported for security fixes and above - 2.7,2.8,2.9,3.0, and now a 3.1. Pretty much 'stock' Moodles with only occasional addons, etc. for testing.
All have their separate code and data directories as well as their separate DB's.
Hint: install and maintain them all with git ... even if you don't prefer/like command line, that is by far the most efficient way to update and/or upgrade a site.
/var/www/html/moodle27/version.php:$release = '2.7.14 (Build: 20160509)'
/var/www/html/moodle28/version.php:$release = '2.8.12 (Build: 20160509)'
/var/www/html/moodle29/version.php:$release = '2.9.6+ (Build: 20160520)'
/var/www/html/moodle30/version.php:$release = '3.0.4+ (Build: 20160603)'
/var/www/html/moodle31/version.php:$release = '3.1+ (Build: 20160603)'
- The git -b command locks a site into the version provided with the rest of the git command ... for example, installing the 3.1, which is a long term support version, installed with git -b option. Don't plan on upgrading nor testing upgrades with that one.
git clone -b MOODLE_31_STABLE git://git.moodle.org/moodle.git moodle31
- All the other moodles I have on that server have been installed via git
git clone git://git.moodle.org/moodle.git [nameofdir]
- then from nameofdir
git branch --track MOODLE_2#STABLE origin/MOODLE_2#_STABLE
git checkout MOODLE2#_STABLE
2# is the version number.
That allows one to march that moodle upwards ... higher branch(es). So one can test an upgrade (as opposed to an 'update').
This second method 'gits' more code and backups will range in the 5+ Meg range due to all the older version git stuff The 3.1 much less (restricted to 3.1 branch):
545M ./moodle296-code-20160604145012.tar
193M ./moodle31+-code-2016060883737.tar
13 関連項目
Installation on Ubuntu using Git
カテゴリ:インストール