20191214のPHPに関する記事は12件です。

業務システム屋が初めてWebアプリを作成しました。

この記事について

業務システム3年の若輩エンジニアが、忘年会の余興支援Webアプリを作成しました。
初めてのWebアプリであったので、その時勉強したことをまとめています。

作成したWebアプリについて

参加者にポイントを配布し、カジノのようにポイントを多くするゲームの支援アプリ。

余興の内容

  • アナログでミニゲームを行う。(ジェスチャーゲームなど)
  • 参加者はミニゲームの結果を予想する。(何人目まで正しく伝わるかなど)
  • 予想には、自身のポイントをベッドする。
  • 予想的中者はベッドしたポイントが2倍になり返却される。逆に予想を外した人はポイントが没収される。
  • ミニゲームを数回行い、ポイントの高い順から豪華景品をプレゼント!

実装した機能

参加者のログイン、ポイント、ランキング管理

IMG_6523.png

ミニゲームの予想投票

IMG_6530.png

投票率、投票内容の内訳表示

IMG_6526.png   IMG_6527.png

ミニゲーム結果の登録とポイント集計

image1.png   IMG_6528.PNG

自身のポイントと平均ポイント推移のグラフ表示

IMG_6529.png

デプロイ環境

レンタルサーバ-はロリポップ!を使用しました。
アプリの規模もとても小さく、フレームワークも使用していないので、上記で十分だと思いました。
そんなに技術力に自信がないよって人が、初めてWebアプリを公開するにはよかったです。
一点だけ確認不足だったのは、DBのバージョンです。ウィンドウ関数に対応する直前のバージョンでした。

(記事を書いている頃には、Paasについても少しづつ理解できるようになりました。
 イケているエンジニアであれば、最初にプラットフォームを選択し、その上にアプリを作るのでしょう。
 今回は、知見がなかったということもあり、プラットフォームの選択に重きを置けませんでした。
 次回に期待。)

使用言語

ロリポップ!ではPHPが一番メインぽかったので、PHPを使用しました。
業務ではC#オンリーで、スクリプト言語自体が初めてでしたので、こだわりはなかったです。

(OOPについてがっつりと勉強してから、OOP言語の差はとても小さくなりました。
 それぞれの言語でモダンな書き方は勉強する必要がありますが、本質は同じってやつでしょうか。)

使用DB

ロリポップ!ではMySqlが扱えます。
業務ではSQL Serverオンリーなので、こちらもお初です。
PHPからDBへのアクセスは、定石であろうPDOを使用しました。

(接続するたびにPDOインスタンスを生成しているので、オーバーヘッドが気になってました。
 上記の疑問をイケているエンジニアに尋ねると、コネクションプーリングというワードを引き出せました。
 そこから、MySqlの接続コストが低いことや、MySqlの最大同時接続数について調査することができ、
 コネクションプーリングは不要と判断しました。ありがとう、イケているエンジニア。)

実装した機能の詳細について

参加者のログイン、ポイント、ランキング管理

ログイン

ログイン状態を維持するために、セッションやCookieについて学習しました。
セッションは有効期限を設けなければ、ブラウザを閉じるまで有効です。
しかし、ロリポップ!ではサーバー側の設定で、有効期限が設けられていました。
ロリポップのライトプランではphp.iniファイルは見られないのでしょうか。

(ちゃんとしたシステムでは上記を調べないなんて言語道断ですが、
 実際にアプリを使用する場では、30分以上の放置は起こりえないため、
 1時間弱の放置テストをして、不問にしました。)

ポイント、ランキング管理

ランキング管理では、デプロイ環境で確認不足だったMySqlのバージョンがアダとなります。
本来であれば、ウィンドウ関数のRankを使用することでSQLのみで完結します。
しかし、ウィンドウ関数に対応していないバージョンのため、プログラムで付与する羽目になりました。
プログラム内でSQLを動的生成する場合、複雑に記述されていると発狂するので、注意したい部分です。

ミニゲームの予想投票

ラジオボタンから選択し、投票しているだけの機能です。
単純な機能ですが、CSSを使用するだけで、モダンな感じ見えるのはWebのいい所です。
参考図書で紹介している本のなかで、ブラウザバックやダブルクリックによる二重登録の対応策について、
最も確実なワンタイムトークンを含め、複数の対応策を学習することができました。

投票率、投票内容の内訳表示

Webアプリの構想時から、この機能についてはこだわりたいと思っていました。
初めは、CSSによる円グラフの表示から調べ、最終的にChart.jsに行きつきました。
高機能なライブラリをすぐに実装できるというは、気持ちがいいものです。

(素晴らしいフレームワークやライブラリを使えるようになると、自分がイケてるエンジニアになったと
 錯覚しがちです。しかし、その恩恵を授かっているだけであることを、忘れてはいけません。)

ミニゲーム結果の登録とポイント集計

ミニゲームの実施後に、ゲームマスターが答えを登録します。
答えを登録時に参加者のポイントも計算します。
次回以降の投票戦略の判断材料になるかと思い、変動値も載せています。

自身のポイントと平均ポイント推移のグラフ表示

Chart.jsがあまりにも簡単実装だったため、調子にのって折れ線グラフでポイントの推移を実装しました。

感想

アプリ作成は勉強になりました。特にWeb周りの知識は実装を行うことで、より着実に身に着けられました。
また、記事作成も勉強になりました。今度はOOPを身に着けた過程について記事を作成してみたいと思います。
プログラムの面ではより高度な実装に励みたいです。ただ、作りたいものが簡単に浮かばないところが壁です。

参考図書

「プロになるためのWeb技術入門」 ――なぜ、あなたはWebシステムを開発できないのか

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOS に phpenv で PHP 7.4 をインストールする

PHP 7.4 が2019年11月28日にリリースされてphpenv + php-build では2019年12月10日に PHP 7.4 が追加されたようなので、早速 macOS にインストールしてみたけど、結構てこずったので、私がインストールできた方法をまとめておきます。

環境

  • macOS: 10.15.1
  • phpenv: v0.9.0-rc.1
  • php-build: v0.11.0dev

はじめに実行したコマンド

phpenv を最新の状態にして

$ phpenv update

PHP 7.4 をインストールしようとしたところ、いくつかのエラーが発生したので、順番にエラーを潰していった。

$ phpenv install 7.4

krb5 をインストールする

krb5 を入れろと言われるので、 brewkrb5 をインストールする。

$ brew install krb5

あと、 PKG_CONFIG_PATH にパスを設定しろとも言われるので、設定する。私は fish shell を使っているので、 .config/fish/conf.d/krb5.fish に下記のように設定した。

set -gx PKG_CONFIG_PATH "/usr/local/opt/krb5/lib/pkgconfig" $PKG_CONFIG_PATH

そのときのエラーメッセージ

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Package requirements (krb5-gssapi krb5) were not met:

No package 'krb5-gssapi' found
No package 'krb5' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables KERBEROS_CFLAGS
and KERBEROS_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

openssl をインストールする

同じく openssl を入れろと言われるので、 brewopenssl をインストールする。

brew install openssl@1.1

同じく PKG_CONFIG_PATH にパスを設定しろとも言われるので、設定する。 .config/fish/conf.d/openssl.fish に下記のように設定した。

set -gx PKG_CONFIG_PATH "/usr/local/opt/openssl@1.1/lib/pkgconfig" $PKG_CONFIG_PATH

そのときのエラーメッセージ

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Package requirements (openssl >= 1.0.1) were not met:

No package 'openssl' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables OPENSSL_CFLAGS
and OPENSSL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

icu4c をインストールする

同じく icu を入れろと言われるので、 brewicu4c をインストールする。

brew install icu4c

同じく PKG_CONFIG_PATH にパスを設定しろとも言われるので、設定する。 .config/fish/conf.d/icu4c.fish に下記のように設定した。

set -gx PKG_CONFIG_PATH "/usr/local/opt/icu4c/lib/pkgconfig" $PKG_CONFIG_PATH

そのときのエラーメッセージ

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Package requirements (icu-uc >= 50.1 icu-io icu-i18n) were not met:

No package 'icu-uc' found
No package 'icu-io' found
No package 'icu-i18n' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables ICU_CFLAGS
and ICU_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

libedit をインストールする

同じく libedit を入れろと言われるので、 brewlibedit をインストールする。

brew install libedit

同じく PKG_CONFIG_PATH にパスを設定しろとも言われるので、設定する。 .config/fish/conf.d/libedit.fish に下記のように設定した。

set -gx PKG_CONFIG_PATH "/usr/local/opt/libedit/lib/pkgconfig" $PKG_CONFIG_PATH

そのときのエラーメッセージ

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: WARNING: libedit directory ignored, rely on pkg-config
configure: error: Package requirements (libedit) were not met:

No package 'libedit' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables EDIT_CFLAGS
and EDIT_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

libxml2 のパスを設定する

libxml2 は brew でインストールされていたけど、パスが通っていなかったので、ビルド時にエラーになった。

.config/fish/conf.d/libzml2.fish に下記のように設定した。

set -gx PKG_CONFIG_PATH "/usr/local/opt/libxml2/lib/pkgconfig" $PKG_CONFIG_PATH

そのときのエラーメッセージ

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
warning: unsupported relocation in debug_info section.
note: while processing /private/var/tmp/php-build/source/7.4.0/ext/opcache/.libs/zend_accelerator_util_funcs.o
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ext/opcache/.libs/opcache.a(shared_alloc_shm.o) has no symbols
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ext/opcache/.libs/opcache.a(shared_alloc_shm.o) has no symbols
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: /var/tmp/php-build/source/7.4.0/modules/opcache.a(shared_alloc_shm.o) has no symbols
/var/tmp/php-build/source/7.4.0/ext/libxml/libxml.c:34:10: fatal error: 'libxml/parser.h' file not found
#include <libxml/parser.h>
         ^~~~~~~~~~~~~~~~~
1 error generated.
make: *** [ext/libxml/libxml.lo] Error 1

bzip2iconv のパスを通す

bzip2iconv は fish shell の設定ではうまくパスが通らなかったので、 phpenv で install するときにパスを渡すことにした。

エラーメッセージ

bzip2

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Please reinstall the BZip2 distribution

iconv

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Please specify the install prefix of iconv with --with-iconv=<DIR>

最終的なコマンド

私は fish shell を使っているので、最終的に下記のようなコマンドになった。

$ env PHP_BUILD_CONFIGURE_OPTS="--with-bz2=/usr/local/opt/bzip2 --with-iconv=/usr/local/opt/libiconv"  phpenv install 7.4.0

bash であれば下記のようになると思う。

$ PHP_BUILD_CONFIGURE_OPTS="--with-bz2=/usr/local/opt/bzip2 --with-iconv=/usr/local/opt/libiconv"  phpenv install 7.4.0

下記のような感じで、問題なくインストールできていることを確認した。

$ phpenv versions
  system
  7.2.25
  7.3.9
  7.4.0
$ phpenv shell 7.4
7.4
$ php -v
PHP 7.4.0 (cli) (built: Dec 13 2019 22:17:20) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.0, Copyright (c), by Zend Technologies
    with Xdebug v2.9.0, Copyright (c) 2002-2019, by Derick Rethans
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでDBから取得したデータの型情報が欲しいのでプログラムを生成するコードを書いた

phpstan 0.12 の level:max に対応させようとしてプログラムがかなり変わった。

自分で思った以上にDBから取得したデータをstdClassで持ち回していたみたい。
そうすると phpstan がまったく使えないので、DBのテーブル情報をPHPのクラスに変換するコードを書いた。

以前は自作ORMを使っていてDBのテーブルを解析してevalしてクラスを作ってたんだけど、最近は素のPDOでいいやってなってた。
でもORMのようなものが必要になって、結局同じようなことになってしまうんだなあ、などと思ったり。

#!/usr/bin/php
<?php
define('NS', 'App\\Generated\\Db');
define('TARGET', 'src/Generated/Db/');
define('TRAIT_SUFFIX', '_trait');
define('NULLABLE_SUFFIX', '_join');
define('DSN', 'host=db-host dbname=db user=dbuser password=secret');


$db = pg_connect($dsn);

$res = pg_query($db, 'select relname from pg_stat_user_tables order by relname');

foreach (pg_fetch_all($res) as $row){
    echo 'parse ' . $row['relname'], "\n";
    $data = parse($db, $row['relname']);
}
echo "done.\n";

function parse($db, $table)
{
    $cols = pg_meta_data($db, $table);

    $ns = "<?php\n\nnamespace " . NS . ";\n\n";

    $class = $table;
    $trait = $table . TRAIT_SUFFIX;
    $join = $table . NULLABLE_SUFFIX . TRAIT_SUFFIX;

    $data = $ns;
    $data .= "trait $trait\n{\n";
    foreach ($cols as $col => $meta)
        $data .= parseCol($col, $meta);
    $data .= "}\n";
    file_put_contents(TARGET . $trait . '.php', $data);

    $data = $ns;
    $data .= "trait $join\n{\n";
    foreach ($cols as $col => $meta){
        $meta['not null'] = false;
        $data .= parseCol($col, $meta);
    }
    $data .= "}\n";
    file_put_contents(TARGET . $join . '.php', $data);

    $data = $ns;
    $data .= "class $class\n{\n";
    $data .= "    use $trait;\n\n";
    $data .= "    public function __construct()\n";
    $data .= "    {\n";
    foreach ($cols as $col => $meta){
        $data .= parseConstruct($col, $meta);
    }
    $data .= "    }\n";
    $data .= "}\n";
    file_put_contents(TARGET . $class . '.php', $data);
}
function parseCol($col, $meta)
{
    $data = '    /** @var ';
    $data .= getColType($meta) . " */\n";
    $data .= '    public $' . $col . ";\n";

    return $data;
}
function getColType($meta)
{
    $type = $meta['type'];
    $type = ltrim($type, '_'); // array
    switch ($type){
    case 'int4':
    case 'int4':
    case 'int8':
    case 'numeric':
        $type = 'int';
        break;

    case 'bool':
        $type = 'bool';
        break;

    default:
        $type = 'string';
        break;
    }

    if ($meta['array dims']){
        $type = 'array<int,' . $type . '>';
    }

    if (!$meta['not null'])
        $type = '?' . $type;

    return $type;
}
function parseConstruct($col, $meta)
{
    $data = '';
    $t = '        ';

    if ($meta['array dims']){
        $data .= "$t/** @var ";
        if (!$meta['not null'])
            $data .= '?';
        $data .= "string */\n";
        $data .= "$t\$a = \$this->$col;\n";

        if (!$meta['not null'])
            $data .= "${t}if (\$a)\n    ";

        $data .= "$t\$this->$col = fromDbArr(\$a);\n";
    }

    return $data;
}

ちょっと長いけど全部貼る。

実行すると下記のようなクラスとtraitを作る。

member_trait.php
<?php
namespace App\Generated\Db;

trait member_trait
{
    /** @var int */
    public $member_id;
    /** @var string */
    public $email;
    /** @var ?string */
    public $email_mobile;
    /** @var string */
    public $name;
}
member_join_trait.php
<?php

namespace App\Generated\Db;

trait member_join_trait
{
    /** @var ?int */
    public $member_id;
    /** @var ?string */
    public $email;
    /** @var ?string */
    public $email_mobile;
    /** @var ?string */
    public $name;
}
member.php
<?php
namespace App\Generated\Db;

class member
{
    use member_trait;

    public function __construct()
    {
    }
}

結構大量に作る。

配列型を使っているなら

functions.php
/** @return int[] */
function fromDbArr(?string $dbArr): array
{
    if ($dbArr === null)
        return [];

    $dbArr = trim($dbArr, '{}');
    if (strlen($dbArr) == 0)
        return [];

    $arr = explode(',', $dbArr);
    $arr = array_map(function($a){ return trim($a, '"'); }, $arr);
    return $arr;
}

このような変換の関数を用意すると、コンストラクタで配列文字列をPHPの配列に変換するコードも生成する。

何が嬉しいかというと、

use \App\Generated\Db\member;

/** @return PDOStatement<member> */
function getMembers(PDO $pdo)
{
    $stmt = $pdo->prepare('select * from member order by member_id');
    $stmt->execute();
    $stmt->setFetchMode(PDO::FETCH_CLASS, member::class, []);
    return $stmt;
}

foreach (getMembers($pdo) as $member){
   echo $member->name; // ok
   echo $member->phone; // phpstan error
}

型チェックが効くようになる。
Generics良い。

実際には

/**
 * @template T
 * @param array<mixed> $params
 * @param class-string<T> $cls
 * @return PDOStatement<T>
 */
function select(PDO $pdo, string $sql, array $params, string $cls)
{
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $stmt->setFetchMode(PDO::FETCH_CLASS, $cls, []);
    return $stmt;
}

foreach (select($pdo, $sql, [], member::class) as $member){
   echo $member->name; // ok
   echo $member->phone; // phpstan error
}

このように汎用的にも作れるので、固定で型を書かなくても大丈夫。

traitがあるのでleft joinにも対応できる。

select *
from member
join member_type using(member_type_id)
left outer join last_logged_in_log using(member_id)
join lateral (
  select json_agg(member_address) as member_address_json
  from member_address
  where member.member_id = member_address.member_id
) as member_address on true
where email = ?

このようなSQLに対応するクラスは

namespace App\Dto;

use App\Generated\Db as Gen;

class MemberDetail
{
    use Gen\member_trait;
    use Gen\member_type_trait;
    use Gen\last_logged_in_log_join_trait;

    /** @var ?string */
    public $member_address_json;
    /** @var Gen\member_address[] type hint of \stdClass */
    public $member_addresses = [];

    public function __construct()
    {
        if ($this->member_address_json){
            // type hint cast
            /** @var Gen\member_address[] */
            $this->member_addresses = json_decode($this->member_address_json);
        }
    }
}
$members = select($pdo, $sql, ['user@example,com'], App\Dto\MemberDetail::class);

このように書ける。
$members はphpstanによって型チェックが効く。

これで各画面ごとに微妙に異なるleft outer joinや一対多をまとめたデータに対しても、全ての型がほぼ正確になる。
安心して手書きSQLが書ける。

いやORM使えよっていうのは置いといて。
むしろORMより型が正確なんじゃないかと思ったり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10でApache+PHP+FuelPHP(+MySQL)

茶番(興味無かったら飛ばしてください。)

今学期の授業でApache+PHPを使うためにUbuntuを使っているのですが、周囲の知り合いから「Ubuntuの動作が重すぎてブチ○したい」だの「なんで学校のPCにはメモリ8GBしか載ってないんじゃそんなんでVirtualBoxが快適に動くかアホ○ね」といった悲鳴が聞こえてくるので僕は思ったんですよ。
「…じゃあUbuntuじゃなくてWindowsで動かせば良くない...?」
しかし、これを言うと「やり方がわからん」やら「terminal触りたくない」と言った人が出てくるんです。terminal触りたくないは君よくここまで生きてきたなこの学科でと言いたくなったけど。

前置きがクッソ長くなりましたが、まあ要するにそういう人のために向けた設定の手順をここで教えるので、やりたくなった人は参考にして下さいってことです。

ちなみにFuelPHPを導入する理由は授業で使うからです。Google検索の予測候補に「FuelPHP オワコン」って出るようなやつ導入してええんかっていうのは気にしちゃダメです。

導入

PHP→Apache→FuelPHP→MySQLの手順で説明します。
バージョン違い等は適宜読み替えてください。(1.8.2等のバージョンの数字は全部X.X.Xと書き換えています。)

PHP

  1. PHPのダウンロードページに行く。
  2. 「Windows downloads」と書いてあるところがあるので押す。(この段階でphpのバージョンを覚えておくと後で楽)
  3. VC15 x64 Thread Safe」と書いてあるとこの下のZipを押してダウンロード。
  4. Zipファイルを解凍したら解凍したフォルダの名前を「php」にする。
  5. phpフォルダを丸ごとCドライブ直下(C:\)に置く。
  6. phpフォルダ直下にあるphp.ini-productionをコピーしてphp.iniにリネーム。
  7. 環境変数の設定画面に移って、C:\phpを環境変数Pathに登録する。
  8. コマンドプロンプトを開いて、php -vと入力。

こんな感じの文字が出ればOK。

PHP 7.4.0 (cli) (built: Nov 27 2019 10:14:18) ( ZTS Visual C++ 2017 x64 )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

Apache

Apacheをインストールする前に、Microsoft Visual C++ 2015 再頒布可能パッケージをインストールする必要がある。
1. ダウンロードページに行く。
2. ダウンロードを押す。
3. vc_redist.x64.exeにチェックを入れてダウンロード。
4. vc_redist.x64.exeを実行。
5. あとは規約に同意してインストール押せば大丈夫です。
セットアップ失敗と出て、別のバージョンの製品が〜〜〜とかなんとか言われたらそれは放置で何とかなるはず

Apacheのダウンロードに戻ります。
1. Apache Loungeのページに行く。
2. 左側の「Download」を押す。
3. httpd-X.X.XX-win64-VS16.zipと書いてあるところを押してダウンロード。
4. 解凍したフォルダの中のApache24フォルダを丸ごとCドライブ直下(C:\)に置く。
5. コマンドプロンプトを管理者権限で起動する。
6. 以下のコマンドを入力する。

cd C:\Apache24\bin
httpd.exe -k install

Errors reported here must be corrected before the service can be started.みたいな表示が出たら成功です。

動作確認

  1. エクスプローラでC:\Apache24\binに移動してApacheMonitor.exeを起動する。
  2. タスクバーに赤い羽根みたいなのが出るのでクリックしてStartを押す。
  3. localhostにアクセスする。
  4. It works!と表示されればOK。

FuelPHP

  1. FuelPHPのページからDownload vX.X.X now!を押してダウンロードしてくる。
  2. 解凍したフォルダを丸ごとCドライブ直下(C:\)に置く。

Apacheの設定をいじる

このままではApacheでPHPが使えないので、設定をいじります。
C:\Apache24\confに移動して、httpd.confを開きます。
以下のことをやる。

  • #LoadModule rewrite_module modules/mod_rewrite.soのコメントアウトを解除。
  • LoadModule ~~~~って書いてあるところの一番下にLoadModule php7_module C:/php/php7apache2_4.dllと追加。
  • DirectoryIndex index.htmlDirectoryIndex index.html index.phpに書き換える
  • DocumentRootを以下のように変更する。
DocumentRoot "C:/fuelphp-X.X.X/public"
<Directory "C:/fuelphp-X.X.X/public">
  • DocumentRootDirectory内にあるAllowOverride NoneAllowOverride Allにする。
  • AddType ~~~~って書いてあるところの一番下にAddType application/x-httpd-php .phpを追加
  • 最後にPHPIniDir "c:/php"を追加。

再度localhostにアクセスする。
一番上にFuelPHP、その下に大きくWelocome!と書かれていたらOKです。

MySQL

MySQLのダウンロードページ](https://dev.mysql.com/downloads/mysql/)からmsi版をダウンロード。
あとは指示に従ってMySQL serverをインストールすればOK。

だと思っていたんですが、以前MySQLを入れたときの残骸(MySQL Connector/NET)が残っていてどうにもうまく行かない。
正直この段階で僕は某VTuberよろしく「あ"あ"ゴミカス!!!!○ねぇぇぇぇぇぇぇぇぇぇ!!!!!!!」ってなってる。
見た感じファイル自体は完全に消えてるはずなんだけどなあ...わかる人いたら教えて下さい...

解決しました。
Windowsサポートのプログラムのインストールまたは削除がブロックされる問題を解決するっていうページにあるトラブルシューティングツールをダウンロードしてみたら抹消することができました。

InstallationでErrorがめっちゃ出ましたがTryagain連打でどうにかなった。ほんまか?

適当にサーバ設定を済ませてC:\php\php.iniに以下の設定を入れれば多分動きます。

extension_dir = "C:\PHP\ext"
extension=php_mbstring.dll
extension=php_mysql.dll
extension=php_mysqli.dll

あとがき

このままではApache構築芸人になってしまう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新卒1年目がCircleCIに振り回される話

こんにちは!新卒エンジニア1年目の@k_mattunです。
この記事は、All About Group(株式会社オールアバウト) Advent Calendar 2019 15日目の記事となっております。

はじめに

つい先日入社したように感じますが、気づいたらクリスマスが迫っているんですね・・・。
社会人の方々が言っていた、あっという間に時間が経っている現象を身を以て体験して焦りを感じています。

この記事では、新卒エンジニアとして仕事を始めてハマった事を書いていこうかなーって思います。
※Advent Calendarに書く内容がなかったわけではありませんので、誤解なきようお願いします

基本的な開発環境

私が日頃仕事を行う上での基本的な開発環境としては、ざっくり以下の通りです。

使用言語

  • PHP5系 or PHP7系
  • JavaScript

使用フレームワーク

  • Laravel5系

インフラ周り・CIツール

  • GCP
  • k8s
  • Docker
  • CircleCI

ハマった事その1

まずこのエラーについて説明する前に、やっていた作業について簡単に説明します。

CIツールの変更について

みなさん改修作業など行なった際に改修内容をリリースしますよね?
そんな時大体の方がCIツールを利用されていると思います。
もちろん、うちの会社でもCIツールは使用していて。

私が入社したタイミングではJenkinsおじさんことJenkinsと、Werckerを利用していました。
※入社当初CIツール?Wercker??って感じでした。自分が参考にしたサイトはこちらです。https://deeeet.com/writing/2014/10/16/wercker/

しかし、入社後CIツールをJenkins + WerckerからCircleCIに変更する事となりました。
Werckerを撤廃した後、あるアプリの改修を行なったのですが。
そのアプリはCircleCIでデプロイした事がなかったため、改修内容を反映させるためにもCircleCIでリリースを行えるように対応を行う事となりました。
※CircleCIについても無知だったので、自分が参考にした記事を載せておきます(大変助かりました、ありがとうございます)https://qiita.com/gold-kou/items/4c7e62434af455e977c2

ハマった事その1については、こちらのCircleCI移行中に起こった出来事になります。

起きたエラー

CircleCIってなんぞやって状況ながらも、社内のCircleCI移行に関するドキュメントであったりネット上の記事であったり。
手当たり次第探しては読み、探しては読みを繰り返しながらなんとかconfig.yamlを記述していきます。

ふんふん、大体ステージング用とプロダクション用にjobを作ってそれぞれどんなタイミングで実行させるかworkflowsで指定していくのね・・・って感じで順調に設定ファイルを書き上げていく。
wercker.ymlを参考にしたのもあって意外とすんなり書けたので、よっしゃ案外CircleCI移行も簡単か?って思っていました。

そして、いざ!CircleCIでデプロイできるかテストや!!ってやってみると。

php artisan clear-compiled

[ErrorException]
~ エラーメッセージ ~

出ました出ました、画面に表示される無慈悲な文言。
きっと今の自分ならこのエラーが出ると、あっ多分この辺りで怒られているんだなって察しがつきますが。
※きっとそうであると信じたい
業務を始めて1ヶ月ぐらいだった自分は頭のが???でいっぱいでした笑

エラーの調査

何はともあれ調べないとわからないので、このエラーをコピーして調べまくりました。
どうやらphp artisan clear-compiledはLaravelのコマンドっぽいぞ?ってことがわかり。
エラーメッセージを読み解くと、なんか使おうとしてるけど該当のモジュール無いよ的な意味でした。

しかしWerckerと同じ設定にしているため、Werckerでは動いていてCircleCIで動かないって現象が理解できず。
何故うまく動作しないのか、考えました。

composer installするタイミングのそれぞれのImageの違い

Wercker時代のビルドフローを全て見直してみて気づきました。
Werckerで行なっていた時はcompsoer installするタイミングのImageの状態がCircleCIの時と違ったのです。

Wercker時代は、ベースイメージを元にDockerfileでアプリ用のイメージを生成します。Wercker.ymlで扱うイメージはアプリ用に作成した色々モジュールがインストールされた状態のイメージを使用しています。
その為Werckerでcomposer installするタイミングでは該当のモジュールが入っており、正しく動作していたのだと思います。

逆にCircleCIの場合該当のモジュールが入っていないイメージのタイミングでcompsoer installを行い、その後デプロイするタイミングでDockerのビルドを行ないアプリ用のイメージを作成していました。
その為、Laravelの環境が立ち上がるタイミングで先ほどのエラーが発生していました。

config.ymlで該当モジュールをinstallしてみる

compsoer installするタイミングでモジュールが入ってればいいのなら、config.yamlで入れたらこのエラー解決するんじゃね?って考え、実行してみることに。

command: |
    ~ 他のいろんな処理 ~
    install ~ 該当モジュール ~
    ~ 他のいろんな処理 ~

これで問題なく次のステップに進める!って思っていましたが、またしても画面に表示される無慈悲な文言。

php artisan clear-compiled

[ErrorException]
~ また別のエラーメッセージ ~

・・・何やねん!!って心境ですね。

再びエラー調査

今度はphp artisan clear-compiledについて集中的に調べることに。
ここで触れておきますが、私は入社して始めてフレームワークを触りました。
なので当然Laravelについても無知です。

一生懸命ネットの海を渡りながら調べた結果、どうやらcompsoer.jsonに記述されているscriptの
php artisan clear-compiledが動作。
それによってvendor配下にあるHogeServiceProvider.php(composerでautoloadしている)のメソッドが動作し、該当のエラーが発生していることがわかった。

エラーの原因

エラーの事象として、CircleCIでデプロイの最適化を行うscript(php artisan clear-compiledやphp artisan optimize)が走った際に、該当のエラーが発生することがわかりました。

これは最適化のタイミングでLaravelのServiceProviderが動作し、とあるメソッドが動作していました。

メソッドの中の処理として他のサーバと通信を行う処理が書いてあったのですが、この際に通信できていないのが実際の原因。

というのも、今回このエラーが発生するタイミングは
環境を構築している段階(ステージングのビルド時)でCircleCI上のコンテナで動いています。
コンテナはまだGKEにデプロイされていない状態です。

ビルドが実行されているCircleCI上コンテナはIPアドレスもポートも毎回異なります。
セキュリティの観点から接続できるIPアドレスは絞っており、該当のサーバへ通信が通らなかった為上記エラーが発生していました。

対処方法

envの環境変数に登録してあるIPに通信を行うので、envが無い場合はその通信が発生しません。
なのでconfig.yamlのコマンドの順番を変更しました。

steps:
    - envを配置する処理
    - composer_installする処理

この処理の流れから

steps:
    - composer_install
    - envを配置する処理する処理

こう変更した。

この事によりcompsoer install実行して該当のscriptが走っても、サーバに通信する事もなく、ビルドしてしまった後にenvを配置するのでデプロイしても無事に動作するようになりました。

実はWerckerでのデプロイも同様の処理フローでした。
(あんなに見比べていたのに一体何をみていたんだ??)

このおかげで、無事CircleCIでのデプロイができるようになり、本来の改修内容もリリースすることが可能となりました。

ハマった事その2

はい、今回のハマった事ですがまたもやCircleCI移行でハマりました。
CircleCI移行って、言ってしまえばデプロイに関する設定を行うので、ちょっとした間違いで直ぐエラーが起こります。
今回も結論としてはしょうもないところでした。

CircleCI移行を済ませ、ステージング環境をデプロイ。
今度は念入りに動作の確認を行って、問題ないことを確認しました。
よっし、これでプロダクションリリースできる!!
そう、こんな時にいつもあの無慈悲な文言が現れるんです・・・

バグの内容

内容としてはCircleCIでデプロイして、ステージング環境プロダクション環境共にリリースできたがプロダクション環境だけアクセスするとステータスコードの500が帰って来るといった問題です。
※社内ツールだった為、今回もビジネス影響はなく九死に一生を得ました

500が帰ってきた際GCPにあるStackdriverのLoggingで該当アプリのログを確認したのですが、アクセスログしか吐かれておらずエラーログがわからない状態でした。

エラーの調査のためにバグの再現

500で繋がらなくなってからすぐにKubernetesで切り戻しを行なったので調べようにも、バグが発生する環境がない状態でした。なぜかステージングは正常に動作しているため調査には使えず。

そこでCircleCIがImageのビルド時にGCPのContainer Registryにpushしているのを思い出したので、そのイメージを手元に落としてきてDockerでコンテナを建てて再現することにしました。
簡単に設定などをローカル用に合わせて、無事立ち上げることができ環境も現象も再現することができました。

apacheのconfファイルを書き換えてaccess_log、error_log共にStackdriverではなく手元に吐くようにして、確認できなかったエラーログを確認しました。

エラーの原因

再現した環境で取得したaccess_logとerror_logです
※パスは架空です

access_log

hoge - - [03/Sep/2019:10:02:53 +0900] "GET / HTTP/1.1" 500 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"

error_log

[Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP Fatal error:  Cannot declare class Illuminate\\Support\\Facades\\Facade, because the name is already in use in /home/hogehoge/bootstrap/cache/compiled.php on line 6005
[Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP Stack trace:
[Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP   1. {main}() /home/hogehoge/public/index.php:0
[Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP   2. require() /home/hogehoge/public/index.php:21

アクセスログは現象の500が帰ってきています。
エラーログではIlluminate\Support\Facades\Facadeを宣言しようとしてるけど、すでに/bootstrap/cache/compiled.phpで使われてるよって怒られてます。

これが原因で処理が中断されステータス500が返されているようでした。

エラーが発生する原因として、compsoer installもしくはupdateが実行された際にcomposer.jsonで記述されているscript(php artisa optimize)が実行され、Laravelの最適化が走っていたのがエラーの発生箇所。(またお前たちか)

compsoer.json

"scripts": {
        "post-root-package-install": [
            "php -r \"copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "php artisan key:generate"
        ],
        "post-install-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postInstall",
            "php artisan optimize"
        ],
        "post-update-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postUpdate",
            "php artisan optimize"
        ]

php artisan optimizeについて

php artisan optimizeコマンドで、Laravelの最適化を行います。
フレームワークのコードを結合して1つのファイルに纏める
該当のクラスなどを利用する際はこのキャッシュファイルを参照するため読み込み速度が上がってパフォーマンスが向上する


実際に手元で動かしているpodからcompiled.phpを取得し、error_logで指摘されていたコードが記述されていることを確認できました。

対応

結論から言って、compiled.phpを生成しないことにしました。

compiled.phpの必要性について

あくまでLaravelの最適化(処理速度向上)のため必要なファイル
生成するコマンドphp artisan clear-compiledについて、Laravel5.5辺りから非推奨となり、公式からも削除されている(5.4まで存在)
https://github.com/laravel-shift/laravel-5.5/blob/master/composer.json#L40

非推奨の理由
77d2a776-371f-3bcd-4045-dec7ebd63007.png

該当のシステムで使用されているphpは7系。
かつ、OPcacheがインストールされいることも確認できパフォーマンスにも影響がないと判断しました。

実際に、この対応を行うと無事プロダクションも動作するようになりました。

ステージングとプロダクションの動作が違った件について

APP_DEBUG

STGとPROのenvに記述してあるAPP_DEBUGが原因
今までSTG環境をAPP_DEBUG=trueでデプロイしていたため今回の現象が発生しなかった。

※APP_DEBUG=trueの場合、optimizeコマンドを実行してもcompiled.phpは生成されない

教訓

開発中はデバッグモードで作業してもいいと思うが、ステージングやプロダクションではデバッグモードを解除してリリースするべき。今回の件のように今までなんとなく動いていた、なんて事になり兼ねないし本番とテスト環境が異なる状況も避けるべき。

終わりに

本当は他にも色々ハマった事あったんですけど、文量が多くなりそうなので、またいつかの機会にでも・・・。

わかった事としてLaravel全般的な知識、特にLaravelが建ち上がるまでの動きとか知らなすぎ問題。
あとはサーバーサイドの事知らなすぎ問題。
今まで全く触れてこなかったので、やっぱり勉強あるのみだなって感じです。
今回のこれらの失敗は様々な事を知るいい機会でした。

少しでも早く一端のエンジニアになれるよう精進あるのみです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

clound9にPHP、Laravelの環境構築をする

はじめに

PHP、Laravelを学習するために環境構築の備忘録として記載

環境

AWS Cloud9

インストール

PHP 7.2
Laravel 5.5

1.PHPのセットアップ

リポジトリのインストール
$ sudo yum -y install http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

<中略>
Installed:
  remi-release.noarch 0:6.10-1.el6.remi                                                                                                                                                                                                        

Complete!
PHP 7.2と必要なパッケージをインストールする
$ sudo yum -y install php72 php72-cli php72-common php72-devel php72-gd php72-intl php72-mbstring php72-mysqlnd php72-pdo php72-pecl-mcrypt php72-opcache php72-pecl-apcu php72-pecl-imagick php72-pecl-memcached php72-php-pecl-redis php72-php-pecl-xdebug php72-xml

<中略>
  policycoreutils-python.x86_64 0:2.1.12-5.25.amzn1        python27-IPy.noarch 0:0.75-1.6.6.amzn1                   scl-utils.x86_64 0:20120229-1.el6                                        selinux-policy.noarch 0:3.10.0-98.26.amzn1        
  setools-libs.x86_64 0:3.3.7-34.23.amzn1                  setools-libs-python.x86_64 0:3.3.7-34.23.amzn1           tcl.x86_64 1:8.5.7-6.9.amzn1                                            

Complete!
デフォルトでPHP 7.2を使えるように設定する

cloud9には最初から古いバージョンのPHPがあるため先ほどインストールしたPHP 7.2をデフォルトで使えるようにする

$ sudo alternatives --set php /usr/bin/php-7.2
バージョンを確認し、7.2.*がインストールされているのかを確認する
$ php -v
PHP 7.2.24 (cli) (built: Oct 31 2019 18:03:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.24, Copyright (c) 1999-2018, by Zend Technologies

2.composerのインストール

Laravelをインストールするためには、composerというパッケージ管理ツールを利用する必要がある
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
composerの確認
$ composer about
Composer - Dependency Manager for PHP
Composer is a dependency manager tracking local dependencies of your projects and libraries.
See https://getcomposer.org/ for more information.
※Your environment is running out of quota. Please make some free space.という警告

もし上記のような警告がCloud9の右上に出てきたら下記コマンドでメモリを開放すると解決するかもしれない

$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"

3.Laravelプロジェクトを作成する

home(environment)で

$ composer create-project laravel/laravel ./プロジェクト名(任意) "5.5.*" --prefer-dist

"5.5.*" と指定することで5.5の中での最大のバージョンがインストールされる。

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

法令APIを利用したリサーチツールを自作してみた【SmartRoppo】

 1. はじめに
 2. リーガルテックっぽいプロダクトを作ってみた
 3. SmartRoppoのコンセプト
 4. SmartRoppoの主な機能・特長
 5. なぜ自分で作ろうと思ったのか?
 6. 今後の課題
 7. おわりに

1. はじめに

この記事は、じゃんく(@jank_2525)さんからバトンを受け継ぎ、「法務系 Advent Calendar 20191」の14日目エントリーとして執筆しています。

皆さんのエントリー、どれも個性あふれる素敵な内容で、毎日大変興味深く拝見しています。

2. リーガルテックっぽいプロダクトを作ってみた

さて、突然ですが、リーガルテック的なプロダクトを作ってみたので、このエントリーをもってβ版を公開させていただきます。【SmartRoppo】といいます。

SmartRoppo -法令データベースを、もっと賢く-
https://smartroppo.com/SmartRoppo/index.html
movie2.gif

ユーザー登録など面倒なことは一切不要で、どなたでも利用できます。
※スマホ・タブレットには対応していないので、PC(ブラウザはできればChrome)からご利用ください。

3. SmartRoppoのコンセプト

SmartRoppoのコンセプトは、「法令のUI/UXをアップデートする」というものです(壮大)。

私自身は金融系の案件を扱うことが多いのですが、金商法や銀行法をはじめとした金融系の法令は、極めて複雑で難解なものが多いです。

それゆえ、恥ずかしながら、弁護士になって5年が経とうとしている今でも、「こんな条文があったのか!」と気づいたり、危うく読み方を間違えそうになることがあります(私だけではないはず…)。

ただ、こうした複雑・難解な法令は、裏を返せば、様々なケースを想定して具体的・詳細に書かれている(解釈の幅が狭い)ということでもあります。実際、業界の法規制に精通したクライアント様からの相談であっても、条文の内容だけでズバリ回答できてしまうケースもそれなりにあったりします。

つまり、法令の内容を「正確に」読み解くことができれば、それだけで求めている情報にたどり着けることも少なくないのです。というか、まずそれができないと解釈も何もないですよね。実務書等の文献に当たることももちろん大事ですが、最終的には原典である条文を確認することが不可欠でしょう。

あるイベントでお話させていただいた際のスライドを抜粋します。
こうした課題をテクノロジーの力で解決することがSmartRoppoのコンセプトです。
GGAプレゼン資料 2_アップ用_ページ_2.png
GGAプレゼン資料 2_アップ用_ページ_3.png
GGAプレゼン資料 2_アップ用_ページ_4-min.png
GGAプレゼン資料 2_アップ用_ページ_6.png
GGAプレゼン資料 2_アップ用_ページ_7.png
GGAプレゼン資料 2_アップ用_ページ_8.png
GGAプレゼン資料 2_アップ用_ページ_9.png

4. SmartRoppoの主な機能・特長

SmartRoppoの機能・特長は、現時点では主に3つです。
(たぶん、文章で説明するよりも、実際に使っていただくか、上記のデモ動画を見ていただいた方が早いです。)

 ① 法令APIからのデータ取得機能(+法令のリアルタイム検索機能)
 ② 下位規則の自動レファレンス機能
 ③ かっこ書きのハイライト機能

① 法令APIからのデータ取得機能(+法令のリアルタイム検索機能)

これまでの電子六法アプリは、自前でデータを保有するデータベース方式が多かったように思います。

これに対し、SmartRoppoでは、総務省が公開している「法令API2」を活用する方式にしました。
つまり、基本的にはアプリ側でデータを保有せず、アクセスの都度、最新のXMLデータをe-govから取得するという設計にしています。

API方式には、(e-govが適時に更新される限り)法令データが常に最新の状態に保たれ、アプリ側のメンテナンス(法改正等の反映作業)が基本的に不要である3という点にメリットがあります。
ただ、都度データを取得してくるという仕組みゆえ、表示速度はデータベース方式に比べて劣っているかもしれません。

② 下位規則の自動レファレンス機能

  ・ 下位規則の重要性

複雑な法令の内容を正確に理解するには、各条文で参照されている「政令」や「内閣府令」といった下位規則を併せて読むことが不可欠です。
ただ、こうした各条文に紐付く下位規則をいちいち特定・確認するのは骨が折れる作業です。紙の分厚い法令集を、行ったり来たりしながら(栞代わりにペンを挟んだりして)確認したことがある方も多いのではないでしょうか。

そこで、SmartRoppoでは、こうした下位規則を自動で取得し、参照元の法令と一覧表示する機能(自動レファレンス機能)を実装しました。

  ・ これまでになかった?

これまでの電子六法アプリでも、手作業で(ゆえに限られた法令・条文に対して)レファレンスを付けていると思われるものは少数ながらありました。
一方、少なくとも私の知る限り、「自動で」(ゆえに全法令に対して)レファレンスを付ける機能を備えたものは、これまで見られなかったように思います(違っていたらすみません)。

とはいえ、SmartRoppoもまだ精度はイマイチですし、現状全ての法令には対応できていないので4、このあたりは順次改善していきたいと思います。

  ・ なぜ難しいのか? ー「逆参照」の壁

なぜ下位規則の自動レファレンス機能が実装されないのか、私は以前から疑問に思っていました。なんとなく、割と簡単に実装できそうな気もします。

しかし、実際に作ってみてよく分かりました。実はこれ、法令の構造的な問題ゆえに、技術的にはそれなりにチャレンジング(というか超面倒くさい)なのです。

特に、API方式と両立させようとすると難しく、いろんな方法を試した結果、それなりの精度でワークしそうなものは、今の自分には一つしか見つけられませんでした。

難しい理由は、端的にいうと、下位規則のレファレンスは、「参照元には参照先を特定する情報がなく、参照先にのみ参照元を特定する情報がある5。そうすると、まずは参照先を特定する必要があるが、その参照先をどうやって特定するのかが問題。」という「タマゴとニワトリ」のような構造になっているからです。

私はこれを「逆参照」と呼んでいます。

GGAプレゼン資料 2のコピー.png

人間が作業する際は、「ここでの『内閣府令』は『○○府令』のことを指していて、だいたいこの辺に書いてあるはず」といったアタリを付け、その周辺の条文をチェックするといったやり方を採ることが多いように思います。

しかし、こういった「肌感覚」的なものをプログラムに書こうとすると、なかなか難しいわけです。

③ かっこ書きのハイライト機能

これは見たまんまですが、かっこ書きをその階層ごとに色分けする機能です。かっこ書きが長かったり、かっこが多重になっている条文が読みやすくなります。

ただ、この多重かっこ(ネスト)の処理がなかなかくせ者で、現状バグが出てしまっています
原因は分かっているのですが6、時間が足りずまだ対応できていません。すみません。。

5. なぜ自分で作ろうと思ったのか?

SmartRoppoの開発にあたっては、コーディングはもちろん、設計や(イケてない)デザインも含め、とりあえず自分一人で手を動かしてやってみました7

私は自他ともに認める「超ド文系」の人間です。技術的なバックグラウンドは何一つありません。プログラミング言語も全く触ったことがなく、「HTML…?:thinking:」というレベルからのスタートでした。

そんな状態から、どうして自分で作ろう/作れると思い至ったのか。自分でもよく分からない(というか忘れた)のですが、以下のような想いがあったように思います。

  • リーガルテックという言葉が広まりはじめ、弁護士等の専門家が自ら起業するケース8も出てきた。
  • 興味はあるので海外の事例とか調べてみたりアイデア(DDとか自動化できたらいいよね!)とかぼんやり考えてみるものの、具体的に何かするわけでもなく空想で終わる。
  • なんかモヤモヤ。せっかく法律の専門性を生かせるテック分野なのに、何も手を動かさずに見てるだけなのはもったいない9よなあ。
  • どこまでできるか分からないけど、技術的にも興味があるし10、とりあえず自分が欲しいものを作ってみようか。プログラミングやったことないけどまあ頑張れば何とかなるやろ(適当)。

こうした経緯で開発を始めたわけですが、弁護士業務の合間を縫って、あーでもないこーでもないと考えながらコードを書いては消し、無限エラー地獄と戦うのは、思っていた以上にハードでした。

特に前述の「逆参照」機能については、正直、めちゃくちゃ悩みました11。いろんな技術を試しては壊してを繰り返す中で、頭がハゲそうになりました12

でも、エンジニア志望でもない自分がプログラミングを学ぶのであれば、何か形にしないとやる意味がない(Deploy or Die13)。少なくとも自分自身が実務で使いたいと思えるものでなければ作る意味もない。そう思っていたので、気合いで乗り切りました。
基本は怠惰なダメ人間ですが、やると決めたことは何だかんだやるんです。

結果、なんとか形になって少しほっとしています(まだまだ課題は山積していますが)。

開発の詳しい経緯(どうやってプログラミングを勉強したか)や技術面の詳細などについては、(もし興味を持ってくださる方がいれば)別の機会に書きたいと思っています。技術面の話は、扱っているデータが「法令そのもの」であるだけに、法務パーソンの皆様にも興味を持っていただけるのではないかと思います。

6. 今後の課題

とりあえず公開はしてみたものの、時間や技術力の制約もあり、まだまだ十分な性能・機能を備えているとはいえません。
今後の課題や追加予定の機能をざっと書き出してみると、こんな感じでしょうか(順不同)。

  • 処理速度の向上(API方式と自動レファレンス機能のせいですがそれにしてもちょっと遅い)
  • 自動レファレンス機能(逆参照)の精度向上・対象拡大
  • 条数検索・用語検索機能(UIはありますがこれも時間が足りず)
  • 順参照の自動レファレンス機能(逆参照の前にこっちをやるべきでした)
  • 法令外国語訳DBの自動参照機能14
  • 判例・パブコメ等の関連資料のレファレンス機能
  • メモ・ブックマーク等のパーソナライズ機能
  • デザインの改修(絶望的にセンスがないので誰か助けてください…)
  • XMLデータ化されていない法令(裁判所規則、告示等)のカバー
  • 正規表現による検索機能
  • その他細かいバグの修正

7. おわりに

まだまだ未熟なプロダクトですが、是非一度触ってみていただければと思います。
そして、どんな内容でも結構ですので、ご意見ご要望などいただけるととても嬉しいです(@lawyer_alpaca)。

長々とお付き合いいただきありがとうございました!
次は10ru(@oga10ru)さんです!よろしくお願いします!


  1. 」もあります。 

  2. https://www.e-gov.go.jp/elaws/interface_api/index.html 

  3. 法令APIに対応していない法令もあります(データ容量が大きい等)。とはいえ、それほど数は多くないので、定期的に改正の有無をチェックし、手動で最新のXMLデータに差し替えることで対応可能です。 

  4. レファレンスの処理自体はプログラムで自動的に行っているのですが、プログラムにデータを渡す際の前処理的な作業を一部手作業でで行っているためです。公開までに時間が足りませんでした。。 

  5. 例外として、参照元にも参照先にも両者を「明確に」紐付ける情報がないケースもあります。 

  6. いわゆる「読み替え規定」がかっこの対応関係を崩しているためです。例えば、「この場合、●条の『▲▲▲・・・(◆◆◆・・・』という規定は、『△△△・・・(◇◇◇・・・』と読み替えるものとする。」という条文があった場合、「開きかっこ」に対応する「閉じかっこ」がデータ上は存在しないことになります。これにより、かっこの対応関係にズレが生じ、ネストが意図せず深く(あるいは浅く)なってしまうのです。 

  7. もちろん、一般的なフレームワークやライブラリは使用しています。その意味で、「粉からカレーを作る」「牛を育てるところからハンバーグを作る」といったレベルでスクラッチしたわけではありません。念のため。 

  8. 代表的な例として、Holmesの笹原さん(@kenta_holmes)、GVA TECH(AI-CON)の山本さん(@gvashunyamamoto)、LegalForceの角田さん(@NT_LegalForce)、Legal Technology(Legal Library)の二木さん(@LEGALLIBRARY_F)などが挙げられます。ゼロから事業を立ち上げられたこれらの方々を、私は心の底からリスペクトしています。自分には到底マネできないので。。 

  9. とはいえ、リーガルテックについては、起業する人、開発する人、使ってみる人、情報発信する人、静観する人など、関わり方は人それぞれだと思います。個々の技術やプロダクトに対する評価も人それぞれだと思いますし、それでいいと思います。 

  10. このときは、プログラミングを学べば技術のことが分かるようになると思っていました。しかし、今はちょっと違って、両者は(相互補完的な関係にはあるものの)基本的には別個のものであり、それぞれ勉強する必要があると考えています。法務に例えるならば、「契約書を何百通レビューしたところで、それだけでは民商法のことが分かるようにはならない。逆もまた然り。両方とも、それはそれとして勉強しなければならない。」という感覚でしょうか。 

  11. 全然関係ないですが、バチェラー3面白かったですね。 

  12. もともと髪の毛は多い方なので、まだ大丈夫だと思います。 

  13. MITメディアラボ(元)所長・伊藤穣一氏の有名なフレーズ。スピーチの全文はこちら。 

  14. 日本法令外国語訳データベースシステムで公開されている英訳法令は現在約750(全法令の1割弱)にとどまりますが、重要法令を中心に今後3年間で大幅な拡充が予定されているようです。http://www.moj.go.jp/housei/hourei-shiryou-hanrei/housei03_00013.html 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuetifyをlaravelに入れる

vuetifyとは

公式サイトでは、全てのユーザーにリッチなWeb開発体験をお届けする、「one of the most popular JavaScript frameworks in the world」であると謳っている。
定期的なupdateやサポートも行っている模様。

他のvue.jsフレームワークとの比較の表が下記。なかなか魅力的なライブラリであるように思えます。

image.png

まんまと公式の謳い文句にのせられた感じはしますが、それじゃ使ってみようじゃないかと思い、自身の勉強用に作成したwebアプリに導入してみました。(laravel+vue+docker)

導入方法

閑話休題、本題の導入方法ですが、基本公式ページのやり方に沿えば簡単にインストールできますが、備忘録も兼ねて下記に手順を記載します。
(*ちなみに私の環境はlaravel6.6.2です。)

1.npmでvuetifyをダウンロード

npm install vuetify

npm install sass sass-loader fibers deepmerge -D

2.src/resources/js/app.jsに下記を追加

src/resources/js/app.js
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';
Vue.use(Vuetify);

const app = new Vue({
    el: '#app',  
    vuetify: new Vuetify()
});

これだけです。

試しに、Example-component内に、公式にある[cards]コンポーネントをサンプルのまま導入してみます。

src/resources/js/components/ExampleComponent.vue
<v-app>
<template>
  <v-card
    class="mx-auto"
    max-width="400"
  >
    <v-img
      class="white--text align-end"
      height="200px"
      src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
    >
      <v-card-title>Top 10 Australian beaches</v-card-title>
    </v-img>

    <v-card-subtitle class="pb-0">Number 10</v-card-subtitle>

    <v-card-text class="text--primary">
      <div>Whitehaven Beach</div>

      <div>Whitsunday Island, Whitsunday Islands</div>
    </v-card-text>

    <v-card-actions>
      <v-btn
        color="orange"
        text
      >
        Share
      </v-btn>

      <v-btn
        color="orange"
        text
      >
        Explore
      </v-btn>
    </v-card-actions>
  </v-card>
</v-app>
</template>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜデザインパターンを理解できないのか

本記事はうるる Advent Calendar 2019 14日目の記事です。

はじめに

エンジニアなら一度は必ず耳にする「デザインパターン」
ただし、「デザインパターン」の勉強って何をすればいいのかや、勉強したけどあまり理解できないという人は少なくないのではないでしょうか?
そこで自分が1ヶ月必死に勉強をした際に思った、必ず抑えとくべきことを紹介したいと思います。
※上級者向きではないので悪しからず...

デザインパターンとは

ソフトウェアをより修正・保守しやすくするもの
つまり、目的は柔軟性(再利用性)を保つこと

抑えとくべき事

  1. デザインパターンはただの考え方に過ぎない
  2. 継承とコンポジションの理解

デザインパターンは考え方の違い

デザインパターンと聞いて難しそうだなと感じてしまう人がいると思いますが、
デザインパターンは単にオブジェクト指向開発での設計思想です。
なので、デザインパターンの1つ1つには必ず、「前提・目的・解決策・メリット・デメリット」が存在していると思っています。
ここではTemplate Methodパターンを例に「前提・目的・解決策・メリット・デメリット」「サンプルコード」を書いてみます。

Template Method

クラス図
image.png

コードを記述する前に「前提・目的」を抑え、どのような考え方でパターンを適用するのかをイメージする

前提:
・コードの重複があれば、それは設計の整理が必要であることを示している

目的:
・重複の行を無くしたい
・一つのメソッドが長くなるのを防ぎたい

解決策:
・アルゴリズムが手順を定義し、一つ以上の手順の実装をサブクラスで提供する

メリット:
・再利用性が高くなる

デメリット:
継承を使用する分「柔軟性」が少なくなる

上記の「クラス図」「前提・目的」を見てもらえば分かると思うのですが、
Template Methodは単に別のメソッドで重複するコードがあれば、抽象クラスに処理を移動させましょうっと言っているだけです。
ここでコードを見てみましょう!!

Template Method サンプルコード

<?php

// 抽象クラスの定義はしてないが、共通の処理をメソッドに分ける
class BasicTraining
{
    private function exercise()
    {
        return "準備運動をします";
    }

    private function afterTraining()
    {
        return "プロテインを補給します";
    }

    public function doTraining()
    {
        echo $this->exercise()."\n";
        echo $this->mainTraining()."\n";
        echo $this->afterTraining()."\n";
    }
}

// サブクラスにそれぞれの実装を記述
class ChestTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "チェストプレスをする";
    }
}

// サブクラスにそれぞれの実装を記述
class LegTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "レッグプレスをする";
    }
}

// サブクラスにそれぞれの実装を記述
class AbsTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "腹筋をする";
    }
}

// Template Methodを使用する際はFactoryパターンも使用することが多い
class TrainingFactory
{
    public static function create($menu)
    {
        switch ($menu) {
            case "胸":
                return new ChestTraining();
            case "足":
                return new LegTraining();
            case "腹":
                return new AbsTraining();
            default:
                throw new Exception("不適切なメニューです");
        }
    }
}

try {

    $training = TrainingFactory::create("胸");
    $training->doTraining();

    $training = TrainingFactory::create("足");
    $training->doTraining();

    $training = TrainingFactory::create("腹");
    $training->doTraining();

} catch (Exception $e) {
    echo $e->getMessage();
}

デザインパターンでは「前提・目的・解決策」この3つは必ず存在する。
この3つを意識することによって理解するスピードが変わってくるので、ぜひ3点意識するようにしてください!!

継承・コンポジション

中にはコンポジションって何なの?と思う人もいると思います。
その場合は必ずオブジェクト指向開発の勉強を再度行ってください。
なぜなら、「コンポジションを理解していないとデザインパターンは理解することができない」と思うからです。

デザインパターンの目的

「柔軟性(再利用性)を保つこと」でしたよね。
コンポジションでシステムを作成すると柔軟性がかなり向上します。
何で柔軟性が向上するのかは、長くなるので別の機会で説明します。
コンポジションの理解には、こちらの記事がおすすめ ※ 引用失礼します

つまり、デザインパターンではコンポジションを多用しているのです。
逆にコンポジションを理解・使用できると一気にデザインパターンの理解力が向上します。
ここで先ほどのTemplate Methodを継承からコンポジションにシステム設計を変更して見ましょう!

サンプルコード 上記のコードをコンポジションで作成

<?php

// __constractでコンポジションを実現
class BasicTraining
{
    private $training;

    public function __construct($mainTraining)
    {
        $this->training = $mainTraining;
    }

    private function exercise()
    {
        return "準備運動をします";
    }

    private function afterTraining()
    {
        return "プロテインを補給します";
    }

    public function doTraining()
    {
        echo $this->exercise()."\n";
        echo $this->training->mainTraining()."\n";
        echo $this->afterTraining()."\n";
    }
}

// 継承をしていない
class ChestTraining
{
    public function mainTraining()
    {
        return "チェストプレスをする";
    }
}

// 継承をしていない
class LegTraining
{
    public function mainTraining()
    {
        return "レッグプレスをする";
    }
}

// 継承をしていない
class AbsTraining
{
    public function mainTraining()
    {
        return "腹筋をする";
    }
}

class TrainingFactory
{
    public static function create($menu)
    {
        switch ($menu) {
            case "胸":
                return new BasicTraining(new ChestTraining());
            case "足":
                return new BasicTraining(new LegTraining());
            case "腹":
                return new BasicTraining(new AbsTraining());
            default:
                throw new Exception("不適切なメニューです");
        }
    }
}

// ここの処理は変更していない
try {

    $training = TrainingFactory::create("胸");
    $training->doTraining();

    $training = TrainingFactory::create("足");
    $training->doTraining();

    $training = TrainingFactory::create("腹");
    $training->doTraining();

} catch (Exception $e) {
    echo $e->getMessage();
}

コンポジションに変更して

気づいた方もいるかもしれないのですが、Template Methodパターンを継承からコンポジションに変更すると
Strategyパターンにクラス図が変更します。
デザインパターン面白い!

Strategy

image.png

こういう風にパターン・使用する目的は違うけど、コードが似ているパターンはたくさん存在します。
なので、コンポジションを使用できるようになっていれば理解も加速すると思いますし、
あまりオブジェクト指向が分からない人は、もう一度オブジェクト指向の勉強から始めるといいと思います。

まとめ

今回自分が作成したサンプルコードはどっちのパターンがいいのかは正直どうでもいいのですが、
開発者同士が「前提・目的・解決策」を理解してシステムを作成することによって開発効率が劇的に向上すると思います。
ぜひ、下記のことを意識しながらデザインパターンの理解を深めていきましょう!!
- デザインパターンはただの考え方に過ぎない
- 継承とコンポジションの理解

参考文献

  • Head First デザインパターン
  • オブジェクト指向でなぜつくるのか 第2版
  • オブジェクト指向のこころ
  • 増補改訂版Java言語で学ぶデザインパターン入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP案件プロジェクトのソースコードを見て感じたこと

はじめに

PHP Advent Calender 14日目の記事です。
こちらの経験談で書いたコーディングの部分で実際の現場のコードを見て感じたことについてまとめた記事になります。

やらかしまくった一人プロジェクトの経験談

若干思い出しながらになりますが最後までお付き合いいただけると幸いです。

①複数クラスの同じ処理の共通化をし忘れる

これは、この現場だけじゃなくていろんな現場でやらかします。

修正前

classA.php
class A
{
処理A(処理B,Cと共通する部分あり)
}
classB.php
class B
{
処理B(処理A,Cと共通する部分あり)
}
classC.php
class C
{
処理C(処理A,Bと共通する部分あり)
}

修正後

classABC.php
class ABC {
処理D
}
classA.php
class A extends ABC
{
処理A
}
classB.php
class B extends ABC
{
処理B
}
classC.php
class C extends ABC
{
処理C
}

このように共通処理をクラスABCにまとめてそれを継承するといった形の方が可読性も良くなりますし、今後も共通化する部分のコーディングは行わなくて良くなるのできっと分かりやすい!
→いわゆるオブジェクト指向ですね。。。

②クラス名や変数名がイケてない

これも結構あるあるですよね。。。

ContainerCreationModelBean.php
class ContainerCreationModelBean
{
処理
}

これは自分がっていうより前任者がこんな感じで書いてたんですけど〇〇BeanってJavaで出てくるJavaBeansやんけって見た瞬間突っ込んでしまった。。。
まあ多分もともとJava書いてた人なんでしょうね・・・
とはいえこれもJavaBeansの仕様を理解してない人からすると意味分からないですよね・・・

③HTMLにPHP埋め込み

もはや害悪ですよね。。。
本当はPHPとHTMLを分けて書きたかったんですけど、これはもう規模も膨大だったので諦めて自分もそれに習って埋め込んじゃいました。。。

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>AAA</title>
</head>
<body>
<h1><?php echo "BBB"; ?></h1>
</body>
</html>

④コントローラーの肥大化

参考資料
やはりお前らのMVCは間違っている

「コントローラーがコントロールするのはアプリケーションの処理ではない。
ユーザーの操作に基づきモデルをコントロールするのが仕事。」
グサッと刺さりますね。
結構ここを間違えて捉えてコントローラーにアプリケーションの処理のコントロールを全てやらせようとしちゃうんですよね・・・
コントローラーが便利すぎてコントローラー任せになっちゃってて役割が崩壊してちゃったなぁ・・・

⑤ノーテストコード

これはまあなんとなく分かってたことではあったんですけどテストコードと呼ばれるものはなかったですね。
一応バッチ処理は一部あったのかな。。。
実際、テストは画面遷移をエクセルに画面スクショでエビデンス残すっていう形ですごく効率悪かったですね。。。
できればテスト用のコードも導入したかったんですけどね・・・

終わりに

5つ事例を紹介しました!
やっぱコーディングしてて思ったのはオブジェクト指向大事だよなぁって思いました。
まだ自分の書くソースコードは、なんちゃってオブジェクト指向なのでちゃんとオブジェクト指向を意識したコーディングができるようになりたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gitlab-runnerでキャッシュが消されてしまう

やろうとしたこと

gitlab-runnerで、毎回composer installをする無駄なインターネットトラフィックが許せなかったので、ジョブとかパイプラインをまたいで永続的にキャッシュしたかった。
composer updateのタイミングでキャッシュを消せば良いので、永続化しても問題はなさそう。

環境

オンプレサーバのdocker-composeで、gitlab/gitlab-ce:latestgitlab/gitlab-runner:latestを走らせている。

ハマりポイント

とりあえずCI/CDがちゃんと動くか試すか~と思って、失敗するテストを書いて試した(phpunit.xmlが存在しない。)。

Fetching changes with git depth set to 50...
Reinitialized existing Git repository in /builds/repository/.git/
From http://sealed.example.com
 * [new ref]         refs/pipelines/31 -> refs/pipelines/31
   fd6b04b..138134d  master            -> origin/master
Checking out 138134d2 as master...
Removing composer.phar
Removing vendor/

Skipping Git submodules setup
Checking cache for cache-composer...
No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted. 
Successfully extracted cache
$ curl -sS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar > composer.phar
$ php composer.phar -qn --prefer-dist install
$ vendor/bin/phpunit --configuration phpunit.xml
Could not read "phpunit.xml".
ERROR: Job failed: exit code 1

すると、キャッシュされるはずのveondor/composer.pharRemovingされてしまっている????

勘違いポイント

3時間ぐらいネットの海をさまよっていると、同じような出力が載っているissueがあった。悩みポイントは自分のと違うっぽかったけど、git cleanという文字列で気が付いてしまった。

原因

このRemovinggit cleanが追跡対象外のファイルを消しているだけで、gitlab-runnerのキャッシュとは全く関係が無い。
テスト成功時のログを見てもわかるように、キャッシュが行われるタイミングはテストが成功した後なので、テストが失敗している限りキャッシュは行われない。

Fetching changes with git depth set to 50...
Reinitialized existing Git repository in /builds/repository/.git/
From http://sealed.example.com
 * [new ref]         refs/pipelines/31 -> refs/pipelines/31
   fd6b04b..138134d  master            -> origin/master
Checking out 138134d2 as master...
Removing composer.phar
Removing vendor/

Skipping Git submodules setup
Checking cache for cache-composer...
No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted. 
Successfully extracted cache
$ curl -sS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar > composer.phar
$ php composer.phar -qn --prefer-dist install
$ php -v
PHP 7.2.24 (cli) (built: Oct 25 2019 05:17:56) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Xdebug v2.8.0, Copyright (c) 2002-2019, by Derick Rethans
Creating cache cache-composer...
vendor/: found 5081 matching files                 
composer.phar: found 1 matching files              
untracked: found 4278 files                        
No URL provided, cache will be not uploaded to shared cache server. Cache will be stored only locally. 
Created cache
Job succeeded

生意気にもナウでヤングなCI/CD環境!とか調子に乗ってた罰ですかね、大人しく手動でテストして画面のハードコピーを証憑.xlsに貼る作業に戻ります……

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<?= ?>で変数を囲んで展開させる表示について教えてください。

仮に、

<?php
$PL101="hello!";
?>
<form>
<?= $PL101 ?><BR>
</form>

とすれば、

hello!

と表示されると思います。

今、

$PL101="hello!";

$PL102="$PL101";  // <- ここの書き方を何かしら工夫するものとしてください。

となっているとして、直に$PL101は参照できないものとしてください。
そのような前提で、

<form>
<?= $PL102 ?><BR>
</form>

というように$PL101を参照せずに、

hello!

と表示させたいのですが、無理でしょうか?

もう少し具体的に言うと、

予め帳票のレイアウトのデータとして

$PL102="$PL101";
<form>
<?= $PL102 ?> 様<BR>
</form>

と書いてあります。
このレイアウトのデータをテンプレートにして、印刷したいときに

$PL101="やまだたかお";

というように代入し、

やまだたかお 様

と表示させたいのです。そういう展開方法があればベストですが、一工夫すれば同じことできるんじゃないかなと思っているところです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む