20191213のPHPに関する記事は19件です。

Laravel の認可(Policy)にモデル以外のパラメータを与える

この記事について

Laravel #2 Advent Calendar 2019 - Qiita 14日目の記事です。

表題の通りではあるんですが、意外と使われてないなぁと思ったので記事にすることにしました。

はじめに

環境

  • Laravel 6.6.0

5.x でも同じです。

Policy について

特定のモデルに対し、ユーザーが操作権限があるかどうかを判定する仕組みで、公式ドキュメントには以下のような使用例が載っています。

class PostPolicy {
    public function update(User $user, Post $post) {
        return $user->id === $post->user_id;
    }
}

呼び出し側は、

class PostController extends Controller {
    public function update(Request $request, Post $post) {
        $this->authorize('update', $post);
    }
}

みたいになります。

この仕組みを利用するメリットは、

  1. 渡されたモデルのインスタンスに応じて Policy クラスをよしなに選択してくれる
  2. User インスタンスを自動でバインドしてくれる(デフォルトでは現在ログインしているユーザー)
  3. 権限がない場合は勝手に 403 エラーにしてくれる

あたりでしょうか。

権限管理が複雑なアプリケーションだと、アプリケーション独自で認可機構を実装したほうが柔軟にできると思うので、無理に使う必要はないと思いますが、そこはチームのポリシー次第、ということで。

ユースケース

  1. 他のモデルの状態が必要
  2. 静的な権限テーブルみたいなやつがある
  3. 動的にポリシーを差し替えたい

ざっと思いついたものを挙げましたが、基本的にはぜんぶ一緒です。

結論

authorize メソッドに配列で引数を渡すと、Policy の各メソッドには、スプレッド演算子によって展開された形で渡ってくるので、それらを使って複雑な認可ルールに対処できます。

呼び出し側でこのように呼ぶと、

$this->authorize('update', [$mainModel, $subModel]);

Policy のメソッドにはこのように渡ってきます。

public function update(User $user, MainModel $main, SubModel $sub) {

使用例

1. 他のモデルの状態が必要

ユーザーにも対象のモデルにもひもづかない、別のモデルの状態が必要になったとき、Controller でいったんそのモデルのインスタンスを取得して、authorize メソッドに渡してやります(ちょっといい例が浮かばなかったので適当なモデル名になっていますが、ご容赦ください)。

public function update(Request $request, Post $post) {
    $someModel = SomeModel::findOrFail($request->some_model_id);
    $this->authorize('update', [$post, $someModel]);
}

Policy 側では以下のように状態を参照できます(メソッドを呼んでもいいでしょう)。

public function update(User $user, Post $post, SomeModel $someModel) {
    return $user->id === $post->user_id && $someModel->acceptable;
}

2. 静的な権限テーブルみたいなやつがある

たとえばユースケースごとに操作可能な権限のリストを持っていて、それに合致しない場合は弾く、みたいなケースです。

ユースケースに決め打ちで(もしくはなんらかのルールに基づいてデータベースから取ってくるとかでも)、

class UpdateTask extends UseCase {
    public function validRoles(): array {
        return [Role::Admin, Role::Wheel];
    }
    public function invoke() {...}
}

みたいに権限のリストがあり、それを以下のように authorize に渡してやります。

public function update(UpdateTask $useCase, Post $post) {
    $this->authorize('update', [$post, $useCase->validRoles()]);
}

Policy 側はこうなります。

public function update(User $user, Post $post, array $roles) {
    return $user->hasRole($roles) || $user->id === $post->user_id;
}

3. 動的にポリシーを差し替えたい

ユースケースごとに独自で認可ルール(関数)を持っていて、そいつをなんらかのルールで動的に生成する、といったようなケースです。

class UpdateTask extends UseCase {
    public function authorizeRule(): \Closure {
        return function (User $user, Task $task): bool {
            // ...
        };
    }
    public function invoke() {...}
}

呼び出し側は、

public function update(UpdateTask $useCase, Post $post) {
    $this->authorize('update', [$post, $useCase->authorizeRule()]);
}

で、Policy 側はこうなります。

public function update(User $user, Post $post, \Closure $rule) {
    return $rule($user, $task);
}

ここまでくると、Policy 使う必要ない気もしてきますが、認可の仕組み自体は Laravel に任せて、ドメインロジックはドメインモデルに任せる、という分担もやりやすいと思うので、検討する価値はあるんじゃないかと思います。

おわりに

上記に挙げた以外にもユースケースがありましたら、コメント欄にて教えていただけると助かります :bow:

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

フレームワークを理解するために自作PHPフレームワーク作ってみた

この記事は2019新卒 エンジニア Advent Calendar 2019の1日目の記事です。
(参加しようと思ったのが13日でしたが、空いている枠が1日しかありませんでしたので1日目の記事として投稿しました。すみません。)

動機

仕事でフレームワークを使ってWebアプリを開発しています。
自作でフレームワークを作ることで、

  • プログラマとしてのスキルアップ
  • フレームワークが何をしているのか理解できるようになり、バグの原因発見が容易になる
  • 機能追加の際にフレームワークの動きも加味していいコードが書けるようになる

以上のメリットがあると考えたためです。

どのように作ったか

パーフェクトPHPの第7~8章を参考にしました。

得られた知見(大きなもの一部抜粋)

スクラッチでフレームワークを作ることで以下のような知見を得られました。

PHPがどのようにレスポンスを返すか

header('HTTP/1.1 200 OK') #HTTP header
echo $content; #HTTP body

恥ずかしいことにechoでresponsebodyを返していることすら理解していませんでした...
HTTPRequest/Responseを追えるようになったのは大きな成長だと思います。

リクエストURLからコントローラへの橋渡し

$tokens = explode('/', ltrim($url, '/'));
foreach ($tokens as $i => $token) {
   if (0 === strpos($token, ':')) {
      $name = substr($token, 1);
      $token = '(?P<' . $name . '>[^/]+)';
   }
   $tokens[$i] = $token;
}
$pattern = '/' . implode('/', $tokens);
$routes[$pattern] = $params;

urlを分割し、controller名とaction名に分ける処理です
この後ルーティングを参照し、controller名を使って目的のphpをrequireします
普段投げているリクエストURLがどのようにcontrollerと結びついているのかを知ることができました

まとめ

普段意識せずにviewとcontrollerを配置してwebアプリを実装していました。
今回のようにスクラッチでフレームワークを実装してみると大小様々に得られるものが多く、特に

  • PHPの言語的な理解
  • フレームワーク内の処理に関する理解

が深まって良いのではないかと思います。

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

【Laravel】保守性をアップさせるかもしれない 3 つのテクニック

はじめに

初めまして、MasaKu です。

今年も残すところわずかですね。

今年もたくさんのコードを書いてきたことだと思います。

中には埃をかぶりまくった個人開発のコードもあるかと思いますが、年末のこの時期にちょっとだけリファクタリングとして手を入れていみませんか?

今回は、Laravel で作成されたアプリケーションを、ちょこっとした工夫だけで可読性をアップできるかもしれないテクニックをご紹介します。

その1:ルートグループ

Laravel では web.php にルーティングする処理を記載します。

超ざっくりしたブログページのサンプルを作成しました。

// ホーム画面
Route::get('/home', 'HomeController@home');

// ブログページ
Route::get('/blog', 'BlogController@blog');
Route::get('/create', 'BlogController@create');
Route::get('/update', 'BlogController@update');
Route::get('/delete', 'BlogController@delete');

この時、ブログページにアクセスする際は、twitter の OAuth でログイン認証している状態にさせたいという場合は各ルーティングにミドルウェアを設定をするのが便利かと思います。

// ブログページ
Route::get('/blog', 'BlogController@blog')->middleware('oAuth');
Route::get('/create', 'BlogController@create')->middleware('oAuth');
Route::get('/update', 'BlogController@update')->middleware('oAuth');
Route::get('/delete', 'BlogController@delete')->middleware('oAuth');

もちろん、このように1件ずつ Middleware を設定することもできるのですが、これだと Middleware を差し替えようとした際に、全ての Middleware の定義を修正しなければならなくなります。

それに、こういったコードがたくさん並ぶとパッと見たときに、どの Middleware を設定しているのかがわかりづらいし、Middleware を付け忘れてしまうことにもなりかねません。

こういう時は、ルーティングをグループ化して、そのグループに Middleware を設定するとコードがスッキリしてわかりやすくなります。

// ブログページ
Route::middleware(['oAuth'])->group(function(){
    Route::get('/blog', 'BlogController@blog');
    Route::get('/create', 'BlogController@create');
    Route::get('/update', 'BlogController@update');
    Route::get('/delete', 'BlogController@delete');
});

このようにすれば、パッと見ただけで Middleware をかける処理が一覧として理解しやすく付け忘れるということもなくなると思います。

ちなみに、グループ化しておくことのメリットとして、グループ内のコントローラ対して名前空間を指定することもできるということもあります。

今後コントローラの名前空間を変更した場合なども一括で設定することができるので非常に便利です。

// ブログページ
Route::namespace('Blog')->middleware(['oAuth'])->group(function(){
    Route::get('/blog', 'BlogController@blog');
    Route::get('/create', 'BlogController@create');
    Route::get('/update', 'BlogController@update');
    Route::get('/delete', 'BlogController@delete');
});

その2:サービスコンテナからサービスクラスを利用する

Laravel ではコントローラ内に処理を記載して、処理結果を View に渡すようにしていきます。

そのため、コントローラ内が複雑になってしまうことをできるだけ避けたいのではないでしょうか。

特に、似たような処理がほかのコントローラ内にあるような状態はできるだけ避けたいです。

そんなときは、共通処理をサービスクラスに書き出してサービスコンテナに登録し、コントローラ内でそのクラスのインスタンスを利用するようにしましょう。

Laravel プロジェクトの app フォルダ内に OriginClasses というフォルダを作成します。

その後、OriginService.php というファイルを作成し、以下のように入力してください。

<?php


namespace App\OriginClasses;


class OriginService
{
    private $msg;
    private $data;

    public function __construct()
    {
        $this->msg = 'サービスクラスを取得';
        $this->data = ['ホーム', 'ブログ'];
    }

    public function getMessage()
    {
        return $this->msg;
    }

    public function getData()
    {
        return $this->data;
    }
}

メッセージとデータを取得する共通処理です。

この処理を各コントローラ内で呼び出して利用できるようにしていきます。

Laravel では app()というメソッドを通してサービスコンテナからインスタンスを取得することができます。

以下のようにコントローラ内に app('App\OriginClasses\OriginService') を記載することで、インスタンスを取得することができます。

public function blog()
{
    // OriginServiceクラスのインスタンスを取得
    $originService = app('App\OriginClasses\OriginService');

    echo $originService->getMessage();
    return view('blog');
}

このようにしておくことで、コントローラ内に共通の処理がいたるところにベタ書きされることを防ぐことができます。

今後、同じ処理を別のコントローラに埋め込みたくなったときや、これまで定義していた共通処理を修正する場合も、クラスファイルのメソッドを修正するだけですべてのコントローラ内の処理を修正することができるので非常に便利です。

その3:ファサードを使う

先程はサービスコンテナからインスタンスを取得する方法をご紹介しましたが、サービスを利用する入口だけを使ってサービスを利用する方法もがります。

ファサードではコントローラ側でインスタンスを取得するのではなく、サービスの入口だけを利用して処理することができます。

まずは、Laravel プロジェクト内のappフォルダにFacadesというフォルダを作成します。

その後、Facadesフォルダ内にOriginService.phpというファイルを作成して以下のように入力してください。

<?php

namespace App\Facades;
use Illumination\Support\Facades\Facade;

class OriginService extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'originservice';
    }
}

それでは、作成したファサードを/config/app.phpaliases配列に追加していきます。

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Arr' => Illuminate\Support\Arr::class,

        // 一部省略

        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,
        'originservice' => App\Facades\MyService::class, //この行を追加

    ],

最後にaliasesに登録したファサードを Laravel のサービスプロバイダに設定して利用できるようにしましょう。

サービスプロバイダは以下のコマンドで作成できます。

php artisan make:provide OriginServiceProvider

作成したOriginServiceProvider.phpを以下の通り修正してください。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class OriginServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        app()->singleton('originservice',
            'App\OriginClasses\OriginSevice');
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

作成したサービスプロバイダを /config/app.php に設定します。

    'providers' => [
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,

        // 一部省略

        App\Providers\RouteServiceProvider::class,
        App\Providers\OriginServiceProvider::class, //この行を追加

    ]

これでファサードを通してサービスを利用できるようになりました。

あとは以下のようにすることでコントローラ側からサービスを利用することができます。

use App\Facades\OriginService;

class BlogController extends Controller
{
    public function blog()
    {
        OriginService::getMessage();
        return view('blog');
    }

ファサードの処理の流れを整理しますと以下のとおりになります

  1. ファサードを作成し getFacadeAccessor() メソッドに利用するサービスのファサード(入口)の名前を返すようにする
  2. config/app.php にファサードを登録する(エイリアスとファサードの実態を登録)
  3. サービスプロバイダを作成し、利用するサービス(コントローラ側で呼び出したい処理)をファサードの名前で設定する
  4. コントローラ側でファサードを経由してサービスの処理を実行する

おわりに

いかがでしたでしょうか。

いずれも基本的な内容にはなりますが、もし取り入れていなくてもちょっとした工夫だけで直ぐに取り入れることができる内容なので、リファクタリングとしてちょうど良い内容なのではないかと思いました。

ぜひお試しあれ!

参考資料

PHP フレームワーク Laravel 実践開発

Laravel 5.7 ミドルウェア

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

pukiwiki を markdown に変換する pukiwiki2markdown というツールを公開しました

とはいえ、1年前くらいに書いたツールなんですが、

「そういえば Qiita にどうやって実装したのか、とか書いてないなー」

と思ったので供養がてら記事しました。(∩´∀`)∩

デモサイト

スクリーンショット 2019-12-13 18.12.35.png

https://pukiwiki2markdown.saino.me/

画面向かって左側の input に pukiwiki の文章を入れると右側に変換結果が表示されます。

変換結果をクリップボードにコピーすることもできます。

ソースコード (Github)

https://github.com/kaishuu0123/pukiwiki2markdown

API っぽく動くエンドポイント

下記コマンドを実行することで、pukiwiki 文法から markdown 形式に変換できます。

body の中に文章を入れる感じですね。

curl -XPOST https://pukiwiki2markdown.saino.me/api/v1/convert \
     -H 'Content-Type: application/json' \
     -d '{"body": "*Header1\n**Header2\n"}'

実装するのに使った技術

バックエンド

Slim Framework というのは本家のサイトでも謳っている通り、PHP のマイクロフレームワークです。
選定理由としては 「pukiwiki のコードをそのまま再利用したかったから」 ということに尽きます。

pukiwiki 文法のパーサーを書こうかな〜とも思ったのですが、pukiwiki の元コードを見るとゴリゴリにロジックが書かれていたので、「これは元のコードを再利用した方が圧倒的に早い」と思い、変換するロジックも PHP 側に寄せました。

(変換ツールなので割と使い捨てっちゃ使い捨てですし)

フロントエンド

  • React
  • webpack

こちらはいわゆる「普通のフロントエンド」という感じで、何ら変わったことはしてないです。

ただ単にテキストを入力して変換結果をもらうだけなので、そんなに React の恩恵は受けていません。

最後に

自前で建てたい方はリポジトリの中に docker-compose.yml を同梱しているのでお試しください。
Docker image 提供は今のところ考えてはいません。本当はやった方がいいのかもしれないけど、変換するだけのツールですので。

今後何らかの改善点があるかな〜と考えましたが、現在のところ割と悪くない精度だったので、このまましばらく運用するかもしれません。

何かご意見あれば Issue や Pull Request は受け付けていますので、よろしくおねがいします٩(๑>◡<๑)۶

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

Laravelでリサイズした画像をdropboxにアップロードする

はじめに

iPhoneで撮った写真をDropboxにアップロードする機能を導入する際に、無料で使える容量が2Gしかなかったのでサイズを小さくしてアップロードしようとしたけど詰まったので備忘録としてやり方を載せておきます。

環境・事前準備

  • Laravel 6.0
  • Dropboxアカウント

Dropbox接続手順

composerでDropboxを操作するためのライブラリをインストール。

composer require benjamincrozat/laravel-dropbox-driver
config/app.php
'providers' => [
    BC\Laravel\DropboxDriver\ServiceProvider::class,
],
filesystems.php
'disk' => [
     'dropbox' => [
         'driver' => 'dropbox',
         'token' => env('DROPBOX_TOKEN'),
     ],

dropboxのaccessTokenを環境変数にセット。

.env
DROPBOX_TOKEN=accessToken

Intervention Image導入手順

画像をリサイズするためにパッケージをcomposerでインストール

php aritsan require intervention/image
config/app.php
'providers' => [
    Intervention\Image\ImageServiceProvider::class,
],

'alianses' => [
    'Image' => Intervention\Image\Facades\Image::class,
],

画像アップロード処理

Controllers/HelloController.php
//画像のリサイズとアップロードするために下記2つを追加
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

public function store(Request $request)
{
     //アップロードされたリクエストの中に画像があるかチェック
     if($request->hasFile('image')){
         $file = $request->file('image');
         $imgName = $file->getClientOriginalName();
         //画像リサイズ処理(横幅を1200pxに指定)
         $image = Image::make($file)
               ->resize(1200,null,function($constraint{
                    $constraint->aspectRatio();//aspectRatio、縦幅アスペクト比維持で自動調整
                });   
         //putメソッドの第一引数にパスを指定(下記のコードではdropboxデベロッパーで設定したフォルダ直下に保存される)
         Storage::disk('dropbox')->put($imgName,(string)$image->encode('jpg',100),'public');
        }

        return view('Hello');
    }

おしまい

AWS s3がアカウント認証でこけたので今回Dropboxを使うことにしました!
エンジニアとしてまだ未熟なので間違っているところがあればコメントいただけると助かります!

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

牛に言葉を喋らせる

cowsay というプロジェクトをご存知だろうか。
その名の通り、牛に言葉を喋らせるという高尚なプロジェクトである。
私も兼ねてから、このプロジェクトに参加したいと思っていました。

そして今日、牛に言葉を話させることに成功した。

$ php say.php

< Ohmg I'm a cow! >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

さらに、タックスにも言葉を喋らせられる。

$ php say.php -ctux -m僕はタックス!

< 僕はタックス! >
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

さらには、ドラゴンも召喚できる。

$ php say.php -cdragon -mお腹がすいたよ

< お腹がすいたよ >
      \                    / \  //\
       \    |\___/|      /   \//  \\
            /0  0  \__  /    //  | \ \
           /     /  \/_/    //   |  \  \
           @_^_@'/   \/_   //    |   \   \
           //_^_/     \/_ //     |    \    \
        ( //) |        \///      |     \     \
      ( / /) _|_ /   )  //       |      \     _\
    ( // /) '/,_ _ _/  ( ; -.    |    _ _\.-~        .-~~~^-.
  (( / / )) ,-{        _      `-.|.-~-.           .~         `.
 (( // / ))  '/\      /                 ~-. _ .-~      .-~^-.  \
 (( /// ))      `.   {            }                   /      \  \
  (( / ))     .----~-.\        \-'                 .~         \  `. \^-.
             ///.----..>        \             _ -~             `.  ^-`  ^-_
               ///-._ _ _ _ _ _ _}^ - - - - ~                     ~-- ,.-~
                                                                  /.-~

素晴らしい。

我が物のように書いてきたが、これは公開されているリポジトリを使っているだけです。
https://github.com/alrik11es/cowsayphp

コマンドラインで牛に言葉を喋らせる

リポジトリをクローンする。

ただ喋らすだけなら、クローンせず composer require alrik11es/cowsayphp で良いのだが、後ほどキャラクターを追加したいので、クローンする。

$ git clone git@github.com:zumikiti/cowsayphp.git
$ cd cowsayphp/

say.php を作る

say.php
<?php
require 'vendor/autoload.php';

use Cowsayphp\Farm;
use Cowsayphp\Farm\Cow;
use Cowsayphp\Farm\Dragon;
use Cowsayphp\Farm\Tux;
use Cowsayphp\Farm\Whale;

// コマンドラインから -c と -m を受け取る
$args = getopt('c:m:');

// それぞれ変数に代入
$char = $args['c'] ?? 'cow';
$msg  = $args['m'] ?? 'Ohmg I\'m a cow!';

// cowsayphp にデフォルトで用意されているキャラクターを代入
switch ($char) {
    case 'tux':
        $cow = Farm::create(Tux::class);
        break;
    case 'dragon':
        $cow = Farm::create(Dragon::class);
        break;
    case 'whale':
        $cow = Farm::create(Whale::class);
        break;
    default:
        $cow = Farm::create(Cow::class);
}

// 喋らす
echo $cow->say($msg);

コマンドラインで実行する

$ php say.php
< Ohmg I'm a cow! >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

引数に -c にキャラクター名、-m で喋らせたい言葉を指定できます。

$ php say.php -ctux -m僕はタックス

< 僕はタックス >
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

キャラクターを追加する

自分の好きなキャラクターを追加したいですよね。

例えば、みんな大好きカービィを追加したい。
src/Farm/Kirby.php を追加する

なお、カービィのAAは https://smashwiki.info/%E3%82%AB%E3%83%BC%E3%83%93%E3%82%A3%E3%81%AEAA からお借りしました。

src/Farm/Kirby.php
<?php
namespace Cowsayphp\Farm;

use Cowsayphp\AbstractAnimal;

class Kirby extends AbstractAnimal
{
    protected $character = <<<DOC

{{bubble}}
        \   ,-‐――、
         \/   ┃ ┃  ヽ-、
          し  " ∇ " |‐'
          ヽ___  _ノ、
          'ー-' ̄ `ー-'   

DOC;
}

say.php の switch に kirby を追加

say.php
    case 'kirby':
        $cow = Farm::create(Kirby::class);
        break;

コマンドラインで実行する

php say.php -ckirby

< Ohmg I'm a cow! >
        \   ,-‐――、
         \/   ┃ ┃ ヽ -、
          し  " ∇ " |‐'
          ヽ___  _ノ、
          'ー-'`ー-'

カービィのデフォルトのメッセージはぽよぽよ〜にしたい。

say.php
- $msg = $args['m'] ?? "Ohmg I'm a cow!";

switch ($char) {
...

    case 'kirby':
        $cow = Farm::create(Kirby::class);
+         $msg = 'ぽよぽよ〜';
        break;

...
}

- echo $cow->say($msg);
+ echo $cow->say($msg ?? ($args['m'] ?? 'Ohmg I\'m a cow!'));

コマンドラインで実行する

php say.php -ckirby

< ぽよぽよ〜 >
        \   ,-‐――、
         \/   ┃ ┃  ヽ-、
          し  " ∇ " |‐'
          ヽ___  _ノ、
          'ー-' ̄ `ー-'

終わりに

私が普段 PHP を使っているので、一番いじりやすいと感じた cowsayphp を使ってみましたが、cowsay は多くの言語で実装されており、さらには cow 以外のキャラクターを使ったものも多く存在します。

ざっと、GitHub で検索しただけでも以下リポジトリが見つかりました。

今回は、公開リポジトリを触ってみるだけになってしまいましたが、来年は私もコマンドラインで遊べる何かを作って公開してみたいと思いました。

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

Windows Server + IIS + PHP + OracleDB 設定したこと

はじめに

タイトルの環境を用意するために行ったこと、
参考にしたサイトを、メモしておきます。

Oracle Instant Clientが出てくるところまでは、スムーズに進みました。

Windows Server 初期設定編

日本語化

https://qiita.com/komacchi_u/items/66b908aa32ffdb9ef389

タイムゾーンの設定

https://support.microsoft.com/ja-jp/help/4026213/windows-how-to-set-your-time-and-time-zone

メニューバーが文字化けする問題の対策

https://answers.microsoft.com/ja-jp/windows/forum/windows_8-performance/%e3%83%a1%e3%83%8b%e3%83%a5%e3%83%bc%e3%83%90/19d210b1-611e-45a3-849b-ac798d924d29?messageId=0e7d4a39-df6a-4477-8e43-f8a70201b62c

IISとPHP編

IISとPHPのインストール(CGIも一緒に)

LaravelやNode.jsについては、今回は使用しないため、対応していない。
https://ottan.xyz/windows-server-iis-php-laravel-6739/

registのインストール

https://support.microsoft.com/ja-jp/help/2977003/the-latest-supported-visual-c-downloads

OracleDBを扱うための準備

php.iniの編集
以下の箇所のコメントアウトを外す

;extension=pdo_oci
;extension=mbstring

デバッグのために、エラーの表示設定変更

https://serverfault.com/questions/19561/how-can-i-display-and-log-php-errors-on-iis7
https://www.remember-the-time.xyz/2016/02/iis-on-php-500-error.html

Oracle Instant Clientを導入

ここが、一番ハマったポイントでした

Appディレクトリの中に格納
http://everything-you-do-is-practice.blogspot.com/2017/11/oracle-instant-client-windows.html

いくつかのファイルをPHPディレクトリに格納
https://qiita.com/nanasess/items/efa7c027838509a44586

サーバー再起動

IISマネージャーを選択し、対象のサーバーを選択。
再起動ボタンを選択し、再起動することで、php.iniなどの設定が反映される。

おわりに

普段はLinuxばかり触っていたので、
Windows Serverの設定は慣れてなく、探し方が悪いのか文献もあまり出てこなかったので、結構時間がかかってしまいました。

GUIでポチポチするだけで、構築できるのはよいですね:relaxed:

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

PHPの生みの親、ラスマス氏のインタビューの紹介

空いてたので・・・。

私の好きなインタビューです。

PHPの生みの親,ラスマス・ラードフ氏インタビュー
https://gihyo.jp/news/report/2015/12/1401

PHPをやって、なぜ、implodeとexplodeの引数の順番が違うのか、
array_mapとarray_filterの引数の順番が違うのか、
<?phpはなんで閉じちゃいけないんだとか、
この統一性のない言語に疑問を持った人も、このインタビューを読めば、
そんなことはどうでもいいと思えます。

私はもっとよいハンマーを使いたいですが、
でもすごく共感できる考え方です。

もう一つ、好きなインタビューです。

Linuxの背後にある精神
http://www.aoky.net/articles/linus_torvalds/the_mind_behind_linux.htm

何が面白いかといえば、
日頃お世話になっているLinuxの世界本部が見られることです。
でもセンスのいいプログラムはかけません・・・

ただのインタビューの紹介でした。

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

Azure Key Vault を利用した .env 内の機密情報の管理

.env と機密情報

Laravel を利用したプロジェクトは通常 .env を使って環境変数を管理しています。.env には各種認証情報などを含めることもあると思いますが、それらは Git などのバージョン管理システムに平文で保存するべきではありません。
かと言って、どこにも管理されておらず稼動している環境に置いてあるだけの状態というのも心許無さがあります。

そこで Azure Key Vault (和名:キー コンテナー)のシークレットと、Go で書いた簡素な vaultenv というツールで、 .env に直接機密情報を記述することなく管理できるようにしました。以下では Azure VM を利用していることを前提に、例を紹介します。

Azure Key Vault の準備

  • まずは Key Vault を作成します。(ここでは Azure CLI を使った例で説明します)
az keyvault create --location japaneast --name <YourKeyVaultName> --resource-group <YourResourceGroupName>
  • 開発者のグループやユーザーにセット(set)と一覧取得(list)の権限を付与します。格納されているデータは取得できないようにします。
az keyvault set-policy --resource-group <YourResourceGroupName> --name <YourKeyVaultName> --secret-permissions set list --object-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  • シークレットを復元したいデプロイ用途などの VM に対して、取得の権限を付与します。
az vm identity assign --name <NameOfYourVirtualMachine> --resource-group <YourResourceGroupName>
{
  "systemAssignedIdentity": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "userAssignedIdentities": {}
}
az keyvault set-policy --name <YourKeyVaultName> --object-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --secret-permissions get

Key Vault へ機密情報の格納

  • 機密情報を Key Vault のシークレットに保存します。
$ az keyvault secret set --vault-name <YourKeyVaultName> -n example-password --value "naisho"
{
  "attributes": {
    "created": "2019-12-13T03:41:15+00:00",
    "enabled": true,
    "expires": null,
    "notBefore": null,
    "recoveryLevel": "Purgeable",
    "updated": "2019-12-13T03:41:15+00:00"
  },
  "contentType": null,
  "id": "https://<YourKeyVaultName>.vault.azure.net/secrets/example-password/97a8cfac350c4b67b1f3510b1598cdce",
  "kid": null,
  "managed": null,
  "tags": {
    "file-encoding": "utf-8"
  },
  "value": "naisho"
}
  • 登録時に出力された id を {{ kv < id > }} の形式で任意のテキストファイルに埋め込みます。example-password 以下(/97a8c...) も含めるとバージョンを固定することができます。含めない場合は最新の値を取得します。
.env.template
USER=user1
PASSWORD={{ kv "https://<YourKeyVaultName>.vault.azure.net/secrets/example-password" }}

.env への展開

  • シークレットの取得を許可した VM 上で行います。上記のファイルを vaultenv を通すことにより {{ kv < id > }} で記述された部分が Key Vault に保存したデータに置換されます。実際にはデプロイ時に自動実行されるスクリプト内で行っています。
$ go get github.com/sensyn-robotics/vaultenv
$ vaultenv < .env.template > .env
$ cat .env
USER=user1
PASSWORD=naisho

まとめ

Key Vault のアクセスポリシーを設定するすることにより、開発者自身のアカウントでは保存されたデータを参照せずに、登録のみが行えるようにすることができます。さらに、データの取得権限を特定の VM に限定することで、(VM に対するアクセスコントロールを適切に行えていれば)機密情報が漏洩してしまうリスクを低減できます。

参考リンク

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

OpenStreatMapの環境を自前で整える

どうものらぬこです。

ちょっとやりたいことがあったので、個人所有のRapberryPi4に、OpenStreatMapの地図データ + PostgreSQL(with postGIS) + mapnik な環境と、データをいい感じに処理するためのフロントプログラミング環境として PHP + php-mapnik を導入しました。

OpenStreatMapサーバーを自前で用意するための記事は探せば結構出てくるのですが、情報が古かったり、環境の違いなどによりうまく動かなかったりと、オープンソースをいろいろ組み合わせて環境準備する系のあるあるな罠盛りだくさんで、意図通りに動作させるまでにはかなりの苦労を強いられました。

環境構築に利用した環境は以下の通りです。
Raspberry pi 4(4Gb) + Raspbian Buster with desktop(2019-09-26)
(最初にRaspberry pi 3B+でチャレンジしたのですがpostgreSQLにデータをインポートする処理がメモリー不足が原因で完了せず、失敗に終わりました)
ストレージ microSD 128Gb(環境構築には45Gbほどの容量を使用するため、最低でも64Gbは必要です)

RaspbianはDebian系Linuxのため、DebianやUbuntu等であれば、x86, x64系PC、AWS上のEC2インスタンス環境などでも、パッケージのバージョンなどで多少の差異は出てくるかもしれませんが、大体同等の手順で環境が建てられるかと思います。

この記事では、OpenStreatMap地図データ(日本)をPostgresql+postGISな環境にインポートし、日本国内の指定された範囲の地図をmapnikを使ってpng出力できるところまでをやります。

mod_tileを使ってタイルサーバに仕立て上げ、leafletjsを利用してgoogle mapのような地図アプリを構築するというようなお話は扱いませんのでご了承ください。

これ以降は、RaspberryPiにRasbianが導入されている前提で話を進めます。

まずは作業用ユーザを作ります。導入中、頻繁にroot権限を利用するため sudoグループに追加しておきます。

# adduser osm
ユーザ `osm' を追加しています...
新しいグループ `osm' (1001) を追加しています...
新しいユーザ `osm' (1001) をグループ `osm' として追加しています...
ホームディレクトリ `/home/osm' を作成しています...
`/etc/skel' からファイルをコピーしています...
新しいパスワード:
新しいパスワードを再入力してください:
passwd: パスワードは正しく更新されました
osm のユーザ情報を変更中
新しい値を入力してください。標準設定値を使うならリターンを押してください
        フルネーム []: 
        部屋番号 []: 
        職場電話番号 []: 
        自宅電話番号 []: 
        その他 []: 
以上で正しいですか? [Y/n] Y
# usermod -G sudo osm

postgreSQL + postGIS を導入します。
apt でインストールした場合、postgreSQLは11が、postGISは2.5系がインストールされます

# apt install postgresql postgis

OpenStreatMapのデータを格納するためのDATABASEを作成し、postGIS拡張を有効化します。
postgreSQLのユーザは、作業用ユーザアカウントと合わせてください。

# su postgres
$ createuser osm
$ createdb -E UTF8 -O osm gis
$ psql -c "CREATE EXTENSION hstore;" -d gis
$ psql -c "CREATE EXTENSION postgis;" -d gis
$ exit

OpenStreatMapの地図データをダウンロードし、それをpostgreSQLのデータベースに格納します。

japan-latest.osm.pbf は日本の地図データで、ファイル形式はxmlです。この記事を書いている時点では13Gbあります。
かなり昔の記事ですが9Gbと書かれていた記事もあったため、情報が充実するに従い、ファイルサイズも増加しているのだと思われます。

openstreetmap-cartoは、地図をレンダリングするために使用するスタイルシート的な情報です。
道路の色や線路の見た目、地図記号のアイコン情報などが格納されています。
また、地図データの取得方法もこの中で定義されていて、postgreSQLの接続設定情報、データを取得するためのSQL文なども含まれています。

osm2pgsqlというのが、OpenStreatMapのxmlデータをpostgreSQLにインポートするためのプログラムです。

# su - osm
$ wget -c http://download.geofabrik.de/asia/japan-latest.osm.pbf
$ wget https://github.com/gravitystorm/openstreetmap-carto/archive/v4.24.1.tar.gz
$ sudo apt install osm2pgsql
$ osm2pgsql --slim -d gis -C 1600 --hstore -S openstreetmap-carto-4.24.1/openstreetmap-carto.style japan-latest.osm.pbf

なお、openstreetmap-cartoのプロジェクトは、また後程使用します。削除せずそのまま残しておいてください。

次に、mapnikのインストールします。
mapnikはaptでもインストールできるのですが、日本語fontの設定がうまくいかなかったため、こちらもソースからビルドしています。
また、mapnikが依存するfreetypeもソースからビルドしています。aptでインストールされるバージョンにはビルドに必要な ft-config というプログラム(?)が含まれていないためです。

$ sudo apt install libpq-dev libboost-dev libboost-regex-dev libboost-filesystem-dev libboost-system-dev libboost-program-options-dev libharfbuzz-dev libbz2-dev

$ wget https://sourceforge.net/projects/freetype/files/freetype2/2.10.1/freetype-2.10.1.tar.gz
$ tar xvzf freetype-2.10.1.tar.gz 
$ cd freetype-2.10.1/
$ ./configure
$ make
$ sudo make install

$ git clone https://github.com/mapnik/mapnik.git
$ cd mapnik
$ git checkout v3.0.22
$ git submodule update --init
$ ./configure FREETYPE_LIBS=/usr/local/lib FREETYPE_INCLUDES=/usr/local/include/freetype2
$ make
$ sudo make install 

さて、mapnikはライブラリモジュールのため、mapnikの機能を利用して地図のレンダリングを行うには、何らかの言語でプログラムを書く必要があります。
ネイティブのC/C++のほか、Ruby/Java/Ptython/PHP等の各言語に対応したbindingも存在します。
僕は、使い慣れているPHPのbindingを選択しましたが、ご自分の使いたい言語向けに提供されているbindingを選択なさるのが良いかと思います。

以下は、phpのmapnikバインディングとして提供されているphp-mapnikの導入方法となります。

何はともあれ、まずはPHPをインストールします。
拡張モジュールのビルドも行うため、php-devもインストールします。

# apt install php php-dev

次に、php-mapnikをインストールします。

$ git clone https://github.com/garrettrayj/php7-mapnik.git
$ cd php7-mapnik
$ git checkout 2.0.0
$ phpize
$ ./configure
$ make 
$ make install

実際に地図をレンダリングするには、地図データの格納場所の設定(postgresqlサーバの設定)、地図の見た目(道路の色、線路の色、地図記号、地名などの表示用フォントなどなど)の設定が必要になります。
この情報を保持しているのが、osm2pgsql を実行する際にダウンロードした、openstreatmap-cartoというプロジェクトです。

地図内の地名などの表記もmapnikライブラリの機能で、画像としてレンダリングされます。

  • 日本語フォントの設定
  • 設定情報のビルド(openstreatmap-carto で定義されたデータから mapnik の設定データ(.xml)を生成する)。
  • 大陸・島々の海岸線データの生成

順にやっていきます。

以下、openstreatmap-carto ディレクトリ内で作業を行います。

$ cd openstreetmap-carto-4.24.1

レンダリングに使用するフォントの種類は、fonts.mssというファイルで定義されています。
日本語フォントの設定も記載されており、日本語のレンダリングにはNotoフォントというgoogleが作ったフォントを使用する設定となっています。ただ、日本語フォント本体はパッケージには含まれておらず、別途ダウンロードする必要があります。

Notoフォントのアーカイブはhttps://www.google.com/get/noto/ からダウンロードできます。
対応しているすべての言語のフォントが含まれているため、アーカイブのファイルサイズも大きめ(1.1Gb)です。

ファイルをダウンロードし、所定の場所(/usr/local/lib/mapnik/fonts/)に解凍すれば、地図レンダリング時に日本語が正しく表示されるようになる、、、はず、なのですが、提供されているフォント形式が.otf形式のためか、自分の環境ではうまくいきませんでした。

無償で提供されているTTF形式の日本語フォントのひとつ、みかちゃんフォントを使用することにしました(http://www001.upp.so-net.ne.jp/mikachan/)。サイトでは、ttf形式、ttc形式、otf形式の3種類を配布していますが、ttf形式をダウンロードしてください(だうんろーど→Windows用→ひとつづつ欲しい方はこちら から入手)。
なお、ダウンロードした.ttfファイルは、 /usr/local/lib/mapnik/fonts/ 以下に置いてください。

そして、fonts.mss を以下の内容で置き換えます。

@book-fonts:    "mikachan-P Regular";
@bold-fonts:    "mikachan-PB Regular",
@oblique-fonts: @book-fonts;

次に、地図のレンダリング設定を、mapnikの設定ファイル形式(.xml)に変換します。

$ sudo apt install npm
# sudo npm install -g carto
$ carto project.mml > mapnik.xml

carto 実行時にWarningがたくさん出力されますがスルーしておきます。

最後に、大陸・島々の海岸線データを準備しておきます。

openstreatmap-cartoプロジェクトディレクトリ内にスクリプトが用意されているので、これを呼び出すだけです。

$ python scripts/get_shapefile.py

以上で、すべて完了です。たとえば以下のようなスクリプトを動かせば、引数で指定された緯度、経度を中心とした 10km四方の地図がレンダリングされ、map.pngという名前で保存されます。

rendermap.php
<?php

ini_set('display_errors', 0);

$distance = 10;
$sizeImage = 500;

$lat = $argv[1];
$lng = $argv[2];
$latInterval = 360 / (6357 * 2 * 3.1416) * $distance;
$lngInterval = 360 / (6378 * cos(deg2rad($lat)) * 2 * 3.1416) * $distance;

$topLat = $lat - $latInterval / 2;
$bottomLat = $lat + $latInterval / 2;
$leftLng = $lng - $lngInterval / 2;
$rightLng = $lng + $lngInterval / 2;

$source = new \Mapnik\Projection('+init=epsg:4326');
$destination = new \Mapnik\Projection('+init=epsg:3857');
$transform = new \Mapnik\ProjTransform($source, $destination);
$boundingBox = new \Mapnik\Box2D(
    $leftLng,
    $topLat,
    $rightLng,
    $bottomLat);
$tileBoundingBox = $transform->forward($boundingBox);

$pluginConfigOutput = [];
exec('mapnik-config --input-plugins', $pluginConfigOutput);
\Mapnik\DatasourceCache::registerDatasources($pluginConfigOutput[0]);

$map = new \Mapnik\Map($sizeImage, $sizeImage, '+init=epsg:3857');

$fontConfigOutput = [];
exec('mapnik-config --fonts', $fontConfigOutput);
$map->registerFonts($fontConfigOutput[0]);

$exampleXmlPath = realpath(dirname(__FILE__)) . '/mapnik.xml';
$basePath = realpath(dirname(__FILE__));

$map->loadXmlFile($exampleXmlPath, false, $basePath);
$map->zoomToBox($tileBoundingBox);

$image = new \Mapnik\Image(234, 234);
$renderer = new \Mapnik\AggRenderer($map, $image);
$renderer->apply();
$renderedImage = $image->saveToString('png');
file_put_contents('map.png', $renderedImage);
$ php rendermap.php 36.0 134.0

環境構築にかかった時間

環境構築に要した時間は、諸々の調査、試行錯誤の時間を含めて3日ほど(土日含む)です。
待ち時間もそれなりで、postgreSQLへのデータインポートに一晩、mapnikライブラリのビルドに1時間ほどかかっています。

最初、raspberry pi 3B で同じことをやろうとしていたのですが、メモリー1Gbでは足りなかったようで、postgreSQLへのインポートが正常終了せず、失敗に終わっています。

何に使ったの?

一人で泊まれる宿検索|ソロ旅ねっと というサービスを個人で運営しているのですが、登録されている約22000件の宿それぞれの周辺広域地図を静的ファイルとして用意したかった、という目的で環境を作りました。
検索結果|ソロ旅ねっと

OpenStreatMapの公式サイトからも、表示範囲を指定して地図画像をレンダリング&ダウンロードすることはできるのですが、レンダリングにはそれなりのCPUリソースが必要で、利用規約にも、一度にたくさんの画像を取得した場合ブロックするかも、機械的な収集なんてもってのほか!という記載があり、自分の好きに使えるように、自前で環境構築をしてみることにしました。

実際、縦横234ピクセルの300km四方の広域地図22000枚をレンダリングするのに、raspberry pi 4で4日以上はかかっています。

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

SlimのDI-ContainerでRay.Diを使ってみる

この記事はPHP Advent Calendar 2019の13日目の記事です。

昨日はpolidogさんのhelicon/object-mapperを作ったでした。
型情報と連想配列のマッピングまでできるというのはアツいですね。僕もライブラリ公開していきたい。

さて本日はWeb Application FrameworkのSlimとDI ContainerのRay.Diを連携させる方法をご紹介します。

背景

僕はレガシーなフレームワークを使っている環境に関わっているのですが、
いい加減このフレームワークへの依存を切り離し、コードを減らしていきたいと常々思っています。

ある日、色々検討する過程で、「別のWAFでリクエストをハンドル・プロキシして古いコード動かせばよくね?」ということを思いつきました。
色々ある世のフレームワークの中で、PSRに準拠しており、学習コストが低い(コードすぐ読める)Slimを実験で使うことにしました。

また、該当のプロジェクトではコードの複雑な依存性を解消するために、DIコンテナとしてRay.Diを用いています。
極論を言ってしまえば、SlimでRay.Diを使えれば大体もうなんでもできるんじゃないか、ということですね。

Slimとは?

SlimはPHPのマイクロフレームワークです。

PSRに準拠した、ルーティングとDIを提供する小さなライブラリなので、
フルスタックフレームワークのネットワーク系の処理の差し替えに向いていると思い検討に入れました。

また、コード量が少なく、PSRに準拠した機構なので内容を把握しやすいです。
これらはフレームワークのレールから外れた対応を行うので、コードを読んで中身を把握しやすいという観点も重要です。

Ray.Diとは?

Ray.DiはPHPのDIコンテナです。
RESTFulアプリケーションのフレームワークであるBEAR.Sundayでも利用されています。(絶賛勉強中)

JavaのGuiceを参考に作られており、細かな挙動調整ができて非常に便利です。
アノテーションによるInjectionの制御やBindingの設定などの柔軟性が高く、既存の処理との兼ね合いを考慮した設定も可能なので、レガシーコードの改善で役立つシーンは多いと感じています。

なにより、フレームワークライブラリが提供するDIではないため、今回のようなフレームワーク移管に関する取り回しがとても効きます。

SlimとDI-Container

Slimは独自でDI-Conatinerの仕組みを持っていますが、これもPSRに準拠しているため差し替えが用意です。
参考: Dependency Container

SlimでDIを行う場合は以下のようにします。

<?php
use DI\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

// Create Container using PHP-DI
$container = new Container();

// Set container to create App with on AppFactory
AppFactory::setContainer($container);
$app = AppFactory::create();

// Add a service
$container->set('myService', function () {
    return new \MyService();
});

// Get a myService's instance
$app->get('/foo', function (Request $request, Response $response, $args) {
    $myService = $this->get('myService');
    // ...
    return $response;
});

このとき、DIはPSR-11で定義されているContainerInterfaceを期待しています。
なので、Ray.DiをContainerInterfaceでラップしたクラスを定義してやれば違和感なく利用できます。

Ray.Diのお手軽使い方

Redisを使った簡単データストア例です。昨今ならFirebaseとか使えよ感。

まずはInterfaceを定義。

src/Modules/Store/StoreInterface.php
<?php
namespace Example\Modules\Store;

interface StoreInterface
{
    public function get(string $id);
    public function set(string $id, $value);
}

本体を実装していきます。

src/Modules/Store/Store.php
<?php
namespace Example\Modules\Store;

use Predis\Client;
use Ray\Di\Di\Named;

final class Store implements StoreInterface
{
    private $client;

    /**
     * @Named("scheme=redis_scheme,host=redis_host,port=redis_port")
     */
    public function __construct(string $scheme, string $host, int $port)
    {
        $this->client = new Client([
            'scheme' => $scheme,
            'host' => $host,
            'port' => $port,
        ]);
    }

    public function get(string $id)
    {
        return json_decode($this->client->get($id), true);
    }

    public function set(string $id, $value)
    {
        $this->client->set($id, json_encode($value));
    }
}

モジュール定義します。

src/Modules/Store/StoreModule.php
<?php
namespace Example\Modules\Store;

use Ray\Di\AbstractModule;
use Ray\Di\Scope;

final class StoreModule extends AbstractModule
{
    protected function configure()
    {
        $this->bind(StoreInterface::class)->to(Store::class)->in(Scope::SINGLETON);
        $this->bind()->annotatedWith('redis_scheme')->toInstance($_ENV['REDIS_SCHEME']);
        $this->bind()->annotatedWith('redis_host')->toInstance($_ENV['REDIS_HOST']);
        $this->bind()->annotatedWith('redis_port')->toInstance($_ENV['REDIS_PORT']);
    }
}

呼び出し方。

<?php
use Ray\Di\Injector;
use Example\Modules\Store\StoreInterface;
use Example\Modules\Store\StoreModule;

$injector = new Injector(new StoreModule);
$store = $injector->getInstance(StoreInterface::class);

Slim with Ray.Di

実際にRay.DiをSlimと連携させていきます。

Containerのラッパーを作成

PSR-11のContainerInterfaceを実装します。
コンストラクタでAbstractModuleを受け取り、Injectorを作成します。

hasについてはRay.DiにそれっぽいI/Fが無く、UnboundのExceptionが発行されるようなのでとりあえずTry-Catchします。

src/Container.php
<?php
namespace Example;

use Psr\Container\ContainerInterface;
use Ray\Di\AbstractModule;
use Ray\Di\Injector;

final class Container implements ContainerInterface
{
    private $injector;

    public function __construct(AbstractModule $module)
    {
        $this->injector = new Injector($module);
    }

    /**
     * @inheritDoc
     */
    public function get($id)
    {
        return $this->injector->getInstance($id);
    }

    /**
     * @inheritDoc
     */
    public function has($id)
    {
        try {
            $this->get($id);
            return true;
        } catch (\Ray\Di\Exception\Unbound $e) {
            return false;
        }
    }
}

Ray.Diのモジュールを作成

モジュールをインストールするルートのModuleを定義します。
ここはBEAR.Sundayを参考にしました。ここでモジュールを一覧できるようになります。

src/AppModule.php
<?php
namespace Example;

use Example\Modules\Store\StoreModule;
use Ray\Di\AbstractModule;

final class AppModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new StoreModule());
    }
}

Slimアプリの作成

ContainerにAppModuleのインスタンスを渡して初期化したものを、AppFactoryに与えます。
これにより、Slimの処理からDI-Containerを呼び出せるようになります。

index.php
<?php
use Example\AppModule;
use Example\Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

$container = new Container(new AppModule());

AppFactory::setContainer($container);
$app = AppFactory::create();

$app->get('/', function (Request $req, Response $res, array $args) {
    $res->getBody()->write("Hello, slim");
    return $res;
});

DI-Containerと連携する

StoreInterfaceを元にモジュールをコールバックに注入する例です。
Controllerとか作ってコンストラクタでインジェクションすることもできます。

index.php
use Example\Modules\Store\StoreInterface;

$app->get('/posts/{id}', function (Request $req, Response $res, array $args) {
    $store = $this->get(StoreInterface::class);
    $id = $args['id'];
    $row = $store->get($id);
    if (!$row) {
        throw new \Slim\Exception\HttpNotFoundException($req, "id($id) post notfound");
    }
    $payload = json_encode($row);
    $res->getBody()->write($payload);
    return $res->withHeader('Content-Type', 'application/json');
});

$app->post('/posts', function (Request $req, Response $res) {
    $body = $req->getParsedBody();
    $id = com_create_guid();
    $store = $this->get(StoreInterface::class);
    $store->set($id, [
        'subject' => $body['subject'],
        'content' => $body['content'],
        'created_at' => (new \DateTime())->format(\DateTime::ATOM),
    ]);
    $payload = json_encode(['id' => $id]);
    $res->getBody()->write($payload);
    return $res->withHeader('Content-Type', 'application/json');
});

まとめ

今回作成したコードはこちらにおいてあります。参考にどうぞ。

Containerを一段覆わないと行けない点はありますが、少ないコードで移植性の高い基盤が作れそうです。

Slimは簡単にかけるフレームワークだなと思ってたんですが、基礎モジュールがPSRのInterface依存なので、非常に拡張しやすいですね。(昨今のフレームワークはだいたいそうかな?)

Ray.Diも拡張性が高く、結合度の高いプロジェクトの中で部分的に疎結合な空間を作っていく際にとても役立ちます。
依存性が複雑 & 密結合なレガシーコードの改善の参考になれたら嬉しいです。

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

LaraDockを使って10分でLaravel+Nginx+MySql+Redisのローカル環境を構築してみる。

はじめに

こちらは TECOTEC Advent Calendar 2019 の13日目の記事です。
折り返しとなります。

Qiitaの記事や他所のブログでも散々書かれていることかと思いますが、復習がてらLaraDockを使ってLaravelのローカル環境を構築してみようと思います。
目指せ10分でローカル環境構築!

※当方macで作業しているため、macでの手順になります。Windowsの方は適時Windows版に読み換えてお試しください。

やること

  • Dockerのインストール
  • LaraDockのインストール
  • Laravelプロジェクトの作成
  • LaraDockの設定変更
  • Laravelの設定変更

以上となります。

ではさっそくやってみましょう。

実演

Dockerのインストール

以下のサイトよりDocker for Macをダウンロードします。

https://docs.docker.com/docker-for-mac/install/

ダウンロードにはdocker hubへの登録が必要なので案内に従って登録してください。
Docker.dmgをダウンロードできたら起動してインストールします。
インストール後はdocker.appを起動しておいてください。

LaraDockのインストール

GitHubからLaraDockのファイルを取得します。
ターミナルを起動して作業ディレクトリへ移動してください。

ターミナルで作業
$ cd ~/work

cloneします。

ターミナルで作業
$ git clone https://github.com/LaraDock/laradock.git

取得できました。laradockディレクトリが作成されています。
ワークスペースのコンテナを起動するために.envファイルを作ります。

ターミナルで作業
$ cd laradock
$ cp env-example .env

Laravelプロジェクトの作成

※開発が進んでいる場合はこの作業はスキップしてください。
※チーム等で管理しているGitからソースを取得してください。

ワークスペースのコンテナを起動してログインします。

ターミナルで作業
$ pwd
~/work/laradock
$ docker-compose up -d workspace
~~~省略
Creating laradock_workspace_1        ... done

$ docker-compose exec --user=laradock workspace bash
laradock@0c7610d35e08:/var/www$ 

ログイン完了です。
Laravelプロジェクトを作成します。

workspace内で作業
$ composer create-project laravel/laravel server

serverプロジェクトが作成されました。

LaraDockの設定変更

ワークスペースからログアウトします。

workspace内で作業
$ exit

vim.envファイルを開きます。(エディタならなんでもいいです)

ターミナルで作業
$ pwd
~/work/laradock
$ vim .env

プロジェクトのパスを変更します。
元の記述をコメントアウトして追記するか、直接編集してください。

.env
# APP_CODE_PATH_HOST=../
APP_CODE_PATH_HOST=../server/

MySqlのデータを永続化させないために作業ディレクトリへ変更します。
※プロジェクトごとにデータを管理する想定です。共通で問題ない場合はこの作業はスキップしてください。。

.env
# DATA_PATH_HOST=~/.laradock/data
DATA_PATH_HOST=.laradock/data/

ポートを変更します。
※こちらは任意です。必要なければこの作業はスキップしてください。

.env
# NGINX_HOST_HTTP_PORT=80
NGINX_HOST_HTTP_PORT=8880

MySqlのバージョンと接続情報を変更します。
最新の8.xは色々あれなので5.7を指定します。
※最新で問題ない場合はこの作業はスキップしてください。

.env
# MYSQL_VERSION=latest
# MYSQL_DATABASE=default
# MYSQL_USER=default
MYSQL_VERSION=5.7
MYSQL_DATABASE=laradock
MYSQL_USER=laradock

MySqlとRedisのホスト名を設定しておきます。
ファイル内の一番下に追記してください。

.env
DB_HOST=mysql
REDIS_HOST=redis

設定を反映させるためコンテナを再起動します。
一緒にNginx・MySql・Redisを起動します。

ターミナルで作業
$ pwd
~/work/laradock
$ docker-compose stop
$ docker-compose up -d workspace nginx mysql redis

Laravelの設定変更

ワークスペースのコンテナにログインして作業します。

ターミナルで作業
$ pwd
~/work/laradock
$ docker-compose exec --user=laradock workspace bash
laradock@0c7610d35e08:/var/www$ 

.envを編集します。

workspace内で作業
$ vim .env

DBとRedis接続設定を書き換えます。

.env
DB_HOST=mysql
DB_DATABASE=laradock
DB_USERNAME=laradock
DB_PASSWORD=secret
~~
REDIS_HOST=redis

パーミッションを変更します。

workspace内で作業
$ chmod -R a+w storage
$ chmod -R a+w bootstrap/cache

依存ライブラリをインストールします。

workspace内で作業
$ composer install

接続確認!

http://localhost:8880/ にアクセスします。

スクリーンショット 2019-12-13 11.41.49.png

Laravelのトップページが表示されれば完了です。
非常に簡単にLaravelの環境が構築できました。
ホストやDBの情報などは適時自分の使いやすいように変更してください。
NginxやMySqlの細かい設定はまた別の機会にかければ書きたいと思いいます。
というかそのままでローカルで使う分には全く問題ないのであまりいじったことがないです。

おまけ

Laravelには便利な機能が沢山あります。

認証機能の実装

以下のコマンドを実行するだけで登録・ログイン・パスワードリセットなどが使用できるようになります。

workspace内で作業(ver5.xの場合)
$ php artisan make:auth
workspace内で作業(ver6.xの場合)
$ composer require laravel/ui
$ php artisan ui vue --auth

Laravelのバージョンで若干コマンドが異なるので注意が必要です。

表示を整えるためにnpm installとnpmを実行してcssとjsをコンパイルします。

workspace内で作業
$ npm install && npm run dev

マイグレーションを実行します。

workspace内で作業
$ php artisan migrate

認証機能の完成です。
スクリーンショット 2019-12-13 11.44.16.png
スクリーンショット 2019-12-13 11.46.01.png
スクリーンショット 2019-12-13 11.46.09.png

管理画面の実装

ついでに管理画面も作ってみます。

workspace内で作業
$ composer require encore/laravel-admin

以下のコマンドを実行します。

workspace内で作業
$ php artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider"
$ php artisan admin:install

http://localhost:8880/admin/ にアクセスします。
初期のIDパスワードは、ID:adminパスワード:adminとなっています。

スクリーンショット 2019-12-13 11.49.58.png
スクリーンショット 2019-12-13 11.51.04.png

そのままで使えることは少ないですが、上記で認証機能と管理画面が作れてしまいます。

起動シェルを作ってみる

PC起動後など起動コマンドが長かったりするのでシェルを作っておくと捗ります。

起動

up.sh
#!/bin/bash
cd ./laradock; docker-compose up -d workspace nginx mysql redis

ワークスペースへログイン

exec_workspace.sh
#!/bin/bash
cd ./laradock; docker-compose exec --user=laradock workspace bash

停止

down.sh
#!/bin/bash
cd ./laradock; docker-compose down

まとめ

LaraDockはローカル環境構築が非常に簡単です。
PHPでのメインフレームワークはしばらくLaravelが多くなりそうなので、LaraDockは扱えるようにしておくと色々便利そうです。
簡単なwebページ作るならあっという間です。

今後も色々試して使いこなしていきたいと思います。

よいエンジニアライフを!

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

AmazonLinuxでPHPを5.3→5.6にバージョンアップする

概要

  • PHP5.3を最新版ではなく5.6(もしくは何らかのバージョン)指定でアップデートしたい時の方法です
  • PHP5.3はApache2.2系、PHP5.6はApache2.4系と依存関係があるので、一緒にアップデートする必要があります
  • Apache再インストールの際にダウンタイムが発生するので、必要であればメンテナンス時間を設けましょう

流れ

  1. サーバーの状態を確認
  2. 現在のPHP,Apacheをアンインストール
  3. アップデート対象バージョンのPHP,Apacheをインストール
  4. (必要な場合)Apacheのhttpd.confを修正
  5. Apacheを起動

手順

OSの確認

対象サーバーにSSHでログインして以下コマンドを実行

# cat /etc/system-release

PHPバージョンの確認

# php -v
PHP 5.3.29 (cli) (built: May 12 2015 22:42:19)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2014 Zend Technologies

インストールされているPHPおよびライブラリのバージョン確認

# yum list installed | grep php
php.x86_64                           5.3.29-1.8.amzn1              @amzn-main
php-cli.x86_64                       5.3.29-1.8.amzn1              @amzn-main
php-common.x86_64                    5.3.29-1.8.amzn1              @amzn-main
php-mbstring.x86_64                  5.3.29-1.8.amzn1              @amzn-main
php-mysql.x86_64                     5.3.29-1.8.amzn1              @amzn-main
php-pdo.x86_64                       5.3.29-1.8.amzn1              @amzn-main
php-xml.x86_64                       5.3.29-1.8.amzn1              @amzn-main

インストールされているApacheのバージョン確認

# yum list installed | grep httpd
httpd.x86_64                         2.2.34-1.16.amzn1             @amzn-updates
httpd-tools.x86_64                   2.2.34-1.16.amzn1             @amzn-updates

インストール可能なPHPおよびライブラリのバージョン確認

# yum list available | grep php
(〜中略〜)
php56.x86_64                          5.6.40-1.143.amzn1            amzn-updates
php56-cli.x86_64                      5.6.40-1.143.amzn1            amzn-updates
php56-common.x86_64                   5.6.40-1.143.amzn1            amzn-updates
php56-mbstring.x86_64                 5.6.40-1.143.amzn1            amzn-updates
php56-pdo.x86_64                      5.6.40-1.143.amzn1            amzn-updates
php56-xml.x86_64                      5.6.40-1.143.amzn1            amzn-updates
php56-mysqlnd.x86_64                  5.6.40-1.143.amzn1            amzn-updates
(〜以下略〜)

php.iniのバックアップ

# cp /etc/php.ini /home/php.ini.yyyymmdd

httpd.confのバックアップ

# cp /etc/httpd/conf/httpd.conf /home/httpd.conf.yyyymmdd

インストール済みPHPおよびライブラリの削除

# yum remove php
# yum remove php-common
# yum remove php-cli
# yum remove httpd
# yum remove httpd-tools

PHP5.6およびライブラリのインストール

※Apacheは依存関係で自動的にインストールされる

# yum install php56 php56-common php56-cli

httpd.confの修正

バックアップしたhttpd.confを読み解き、必要な設定を加える

# vi /etc/httpd/conf/httpd.conf

2.2系と2.4系でアクセス許可設定の記法が変わっているので注意
(参考:https://qiita.com/nwsoyogi/items/c8eb1fedef3c00c5fbac)

2.2系
<Directory "/home/xxx/htdocs">
    Order allow,deny
    Allow from all
</Directory>
2.4系
<Directory "/home/xxx/htdocs">
    Require all granted
</Directory>

Apache起動

# service httpd start

確認

PHPのサンプルコードやPHPアプリケーション(phpmyadminなど)で動作確認ができたらOKです

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

LaravelとSpringBootでDIコンテナを利用してみる

はじめに

これはユアマイスターAdventCalendar2019の13日目の記事です。
(社会人になってから学んだことをアウトプットする記事になります。)

今回の経緯

社会人になってからDI(依存性の注入)という概念を知りました。
WEBサービスの開発を行う際に、フレームワークを利用する場面は多々ありましたが、主に利用していたCake PHPではDIという概念は出ていなかったと記憶しています。
(記憶違いだったらすみません。)

DIは、インスタンスをnewで作成して利用するのではなく、DIコンテナやサービスコンテナ呼ばれるもの(SpringBootではDIコンテナ、Laravelではサービスコンテナと呼ばれます)を利用して、あらかじめ登録されたインスタンスを利用します。

今回は、業務で利用しているSpringBootでのDIコンテナの利用、最近独学で学んでいるLaravelでのサービスコンテナを利用してみるというテーマで記事を書いてみたいと思います。

SpringBootの場合

コンストラクターインジェクションを利用する

ユアマイスターアドベントカレンダー2019 の7日目の記事で書いたコードを用いて書いていきます。
SpringBootで動的にDBを切り替えてみる
https://github.com/Masaki-Ogawa/datasourceDemo

  1. PersonRepository.java
PersonRepository.java
package com.example.dataSourceDemo.domain.repositories;

import com.example.dataSourceDemo.domain.models.Person;
import org.springframework.data.jpa.repository.JpaRepository;

@Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {

}

JpaRepositoryを継承したRepositoryクラス

  1. PersonServiceImpl.java
PersonServiceImpl.java
package com.example.dataSourceDemo.domain.services;

import com.example.dataSourceDemo.annotations.DataSource;
import com.example.dataSourceDemo.annotations.DataSource.DataSourceType;
import com.example.dataSourceDemo.domain.models.Person;
import com.example.dataSourceDemo.domain.repositories.PersonRepository;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class PersonServiceImpl implements PersonService {

  private final PersonRepository personRepository;

  public PersonServiceImpl(
      PersonRepository personRepository) {
    this.personRepository = personRepository;
  }

  /**
   * stgのDBからPersonテーブルのレコードを取得するメソッド
   * @return Personテーブルのレコード
   */
  @Override
  public List<Person> findAllPersonInStg() {
    return personRepository.findAll();
  }

  /**
   * stgのDBからPersonテーブルのレコードを取得するメソッド
   * @return Personテーブルのレコード
   */
  @DataSource(value = DataSourceType.PROD)
  @Override
  public List<Person> findAllPersonInProd() {
    return personRepository.findAll();
  }
}

ここでDIを行っています。

具体的には、

private final PersonRepository personRepository;

  public PersonServiceImpl(
      PersonRepository personRepository) {
    this.personRepository = personRepository;
  }

の部分でコンストラクターインジェクションによるDIを行っています。
@Service@Repositoryというアノテーションを利用することにより、DIコンテナに登録されます。
利用するには上記のように、コンストラクターインジェクション等を利用して、インスタンスを作成します。

参考
Spring Framework 要点まとめ ~ DIについて

LaravelでのDI

サービスプロバイダーを利用する

作成したサービスをサービスコンテナ登録するためのサービスプロバイダーを作成します。
今回はDemoServiceというサービスクラスを作成しました。

AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('App\Services\DemoService');
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

上記のようにサービスを登録します。

DemoService.php
class DemoService {
    public function show() {
        echo "何かを表示します";
    }
}

サービスクラスは今回は適当ですが、何か文字を出力するメソッドのみ実装します。

DemoController.php
class MessageController extends Controller
{
    protected $demoService;

    public function __construct(DemoService $demoService)
    {
        $this->demoService = $demoService;
    }

    public function index(Request $request) {

        return $this->demoService->show();
    }
}

このようにこちらもコンストラクターインジェクションを利用して、こちらもDIを行います。

終わりに

個人的には、@Service@Controller等のアノテーションで、DIコンテナに登録できるSpringBootの方が利用しやすいと感じています。
Java、SpringBootを利用してやはり、アノテーションの強力さに気づかされる場面が多々あります。

また、7日目のアドベントカレンダーで書きましたが、それぞれのフレームワークに良さや悪さがあり、多数のフレームワークに触るという経験は、今後何かものを作るときに、「どんなものを採用すれば、そのプロダクトにとって一番良いのか?」という判断材料になると思います。

今後も、業務、業務外を含めて触れていきたいと思います。

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

phpMyAdminが、AWS EC2複数台構成で接続できなかった件

同じ構成のEC2インスタンスを2つ用意して、ALBで振り分ける構成の案件がありました。
それぞれにphpMyAdminが入っており、接続先は同一のRDSです。

このphpMyAdminにログインしようとすると、エラーが表示されログインできない現象が起きました。
結論を先に書くと、対象のターゲットグループ設定の、維持設定を有効化にせいというアドバイスをもらい、解消しました。

調べてみたら、振り分けられてセッションが上手く処理できないから、ユーザー(ブラウザ)毎に割り振りを固定するってことみたいですね。

スティッキーセッションはAWS専門用語では無く、ロードバランサー持つ一般的な機能の名称です。

セッション、インフラの知識を深めなくては:thinking:

参考:https://dev.classmethod.jp/cloud/aws/stateless_ec2/

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

【Laravel】初心者から実務をこなしていくまでの6ヶ月にやったこと

CODEBASE2期生、新卒1年目の @avocadoneko です。
仕事では主に Laravel / Vue.js を使って、開発をしています。

この記事について

どんな人に読んでほしいか

  • プログラミングを学び始めたばかりの人
  • これから PHP, Laravel を学びたいと思っている人
  • Laravel で開発している会社に入社する予定の人

書いてあること

  • 初心者が PHP と Laravel をどうやって勉強してきたか
  • 仕事で学んだ Laravel のこと

書いてないこと

  • PHP、Laravel 以外のこと(Git とか Web のこととか他の言語とか)

注意

  • いくつか紹介している記事や動画では、Laravel のバージョンが古いことがあります。

やったこと

まずはPHPを触ってみる

Progate

[logo_1.jpg

ゲーム感覚でプログラミングを学べる教材。
有料版は月額980円。
超初心者が PHP はどんな言語なのか知るのの導入に役立つと思う。実際、プログラミングが何もわからない状態でもサクサクと進めることができた。
但し、Progate だけやっていても仕方がないので、基礎を学んだ後はやらなくてもいいと思う。

ドットインストール

[logo_2.jpg

月額1,080円(無料でも多くの動画を視聴できる)で手軽に学べる動画教材。
ローカル環境構築の方法も動画になっているので、動画を見るだけではなく、自分のPCで再現して学ぶのがおすすめ。
手を動かして学ぶのが好きな人には向いていると思う。
Laravel の動画に入る前に、PHPで小さいアプリケーションを作る動画をやるのが良さそう。

以下、良いなと思った動画↓

書籍

詳細! PHP 7+MySQL 入門ノート

book_1.jpg

この本は最初から読んでいって、知らなかったことだけ自分のPCで実行してみると良い。
また、 一番読んでほしいところは Chapter7 の「オブジェクト指向プログラミング」の章。
クラスの定義方法、継承、コンストラクタなど知っておくべき基本的なことについてについて、実際のコードと共に説明されている。

PHPフレームワーク Laravel入門

[book_2.jpg

Laravel 入門書として一番おすすめの本がこちら。
レビューにもある通り、かなり回りくどく書かれているからこそ、入門書として人気が高い。
この本の使い方は、とりあえず写経。
書いてあることは全部必要な知識だから、隅から隅まで読むべき。
写経は GitHub でリポジトリを作って動く単位でコミットしていき、コミットメッセージに気づいたことを書くといいと思う。
写経のやり方については下記の記事にわかりやすく書いてある。
技術書の写経を始めたのでやり方を書いておく

完全に理解しようとして行き詰まるより、とにかく読み切ることを目標にして、70~80%くらいの理解で最後のページまで写経するといいかもしれない。
また、丸々一冊読み切った本は、後に振り返るのにも便利。

記事

【PHP超入門】クラス~例外処理~PDOの基礎

$this とか スコープ定義演算子とか、とにかく PHP の基礎が全くわかってなかった頃にお世話になった記事。
初心者がつまずきやすいところがピンポイントで書かれていて、何度も助けられた。

チュートリアル

Laravel 5.5 入門として「基本のタスクリスト」を作成する

検索したら大量に出てくるチュートリアルの中でも、これは特に分かりやすかったなという印象。コントローラを使わずに web.php に処理を書いてタスクリストを実装しているので、使用するファイルが少なく、混乱せずにできた。
(実際開発するときはありえないけど、初心者には向いているチュートリアルだと思う)
さらなるステップとして、ここに機能を追加していくのもいい。私の場合は編集機能を追加した。

Laravelで飲食店検索LINE Botを作ろう!

このチュートリアルは私はやったことがないが、もっと前からあったらやってみたかった。
有料だが、Docker 開発環境の準備から丁寧に説明しているチュートリアルは少ない上、LINEbot を作るまでできたらそれなりに達成感がありそう。

Laravelでオリジナルのアプリを作る

DB 設計から機能、UI まで、一つのアプリを作ってみるのがおすすめ。
先に完成物の機能を具体的に決めてから実装したほうが良い。
自分が作りたいものを作るのが一番良い。
私の場合は下記のような機能を実装した。

  • 新規登録 / ログイン機能
  • イベントの作成 / 編集 / 削除
  • イベントごとに写真を追加 / 削除

テーブル数は3-4つくらいが丁度よかった。
余裕があったら、API叩いて、画面遷移なしでイベントや写真を作成・削除できるようにしてもいいかも。
自分でアプリを作ることで、自分がわからない部分が明確になるし、完成させたら自信になる。

業務に入ってから

仕事では独学と違って、0から何かを作ることは少ない。
初めて会社のプロダクトコードをみたときは、ファイルとコード量が多すぎてびっくりした。
業務ではチームで開発するし、人の入れ替わりがあるので保守性を意識する必要があるため、「動く」だけでなく「読める」コードを書く意識をしなければいけない。

書籍

PHPフレームワーク Laravel実践開発

book_4.jpg

前に紹介した書籍「PHPフレームワーク Laravel入門」(以下、青本)の中級者向けバージョン。
青本を読み終わった人向け。
同じ著者なので、青本が読みやすくて気に入ったら、こちらも読んでみると良い。
レビューで内容が薄いとあるが、その分気軽に読めるので全体を流し読みするのには最適。足りない部分はググって補うといいかと思う。
また、これもレビューにある通り、誤字脱字は多いのでたまに自分のPCで動かしてみても動かないことがあるので注意。

PHPフレームワーク Laravel Webアプリケーション開発

book_3.jpg

この本は、仕事でコードを書く上でのユースケースがたくさん書かれている。
どうやって実装したらいいか迷ってるときに、この本に手を伸ばすと、解決することがある。
全部読むというより、辞書代わりに手元に置いておくと良い。

記事

Laravelで始める依存性の注入(DI)

DIってなんだ??となったときに読んで役に立った記事

Laravelで実践クリーンアーキテクチャ

Laravel をクリーンアーキテクチャに当てはめるなら、どういう感じになるのか?そもそもクリーンアーキテクチャって???となったときに読んで役立った記事。

わからなくなったら

Laravel 日本語公式ドキュメント

実装中にわからないことがあってググった時、まずは公式ドキュメントを読むのがいい。
私は最初、書いてある日本語が難しくて読めなかったから、ある程度 Laravel に慣れてきたらでもいいと思う。
バージョンの違いや正確性を考慮すると、公式ドキュメントを読むのがいちばん(当たり前のことだけど)。

Laravel API

ドキュメントに書いてないメソッドなどもここで検索すれば出てくる。

最後に

いくつか宣伝をさせてください。

CODEBASE 沖縄 プログラミング教室

学生時代、エンジニアになるために通っていたスクールです。
3ヶ月間で、何もわからない状態からソフトウェアエンジニアとして新卒就職できるまで成長できました。

千株式会社

私が新卒で入社した会社です。
千株式会社 では幼稚園・保育園向けインターネット写真サービス「はいチーズ!」を提供しています。
新卒、中途共に絶賛採用中です!

週1回のリモートワーク、フレックス制度(コアタイムが12:00-15:00)など、柔軟な働き方ができる会社です。(朝の6時に出社すれば、なんと15時に退勤できてしまいます...!)
モダン(Laravel + Docker + CircleCI + AWS)で自由な環境で働いてみたい方におすすめの会社です。
もっと詳しく知りたい方は こちら

アドベントカレンダー 千 Advent Calendar 2019もやっているのでぜひ覗いて行ってください〜


この記事は CODEBASE okinawa Advent Calendar 2019 13日目の記事です。

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

Laravel(5.8)のFormファザードサンプル

概要

見た目がスッキリするのでLaravelの拡張としてよく使うFormFacade、ただ書き方を忘れる事があるのでメモとして残しておく。
公式はこちら

環境

  • laravel:5.8.*
  • laravelcollective/html: ^5.8

前提

  • old()はLaravelのヘルパーであり、直前のフォームに入力した値を取得する働きをする。
    • 第2引数は初期値
  • BootStrap4を使っているのでformで使うclassを指定している

  • セレクトボックス・ラジオボタン・チェックボックスで使う配列は下記のような形とする

$array = [
    1 => 'hoge',
    2 => 'fuga',
    3 => 'piyo',
];

フォームの開始と終了

// 開始
{{ Form::open(['route' => ['user.update', 'user' => $user->id], 'method' => 'put']) }}

// 終了
{{ Form::close() }}

テキスト

一番使う基本的な形
emailとかpasswordとかはほぼ同じ形なので省略

{{ Form::text('name', old('name', $user->name), ['class' => 'form-control']) }}

セレクトボックス

第2引数に配列、第3引数に初期値を入力すればよい

{{ Form::select('sample_id', $array , old('sample_id', $user->sample_id) , ['class' => 'form-control']) }}

ラジオボタン

第3引数についてはbooleanを設定する。
この場合は三項演算子の省略でtrueかfalseを返すようにしている。
注意点として配列のキーに0をもたせていると強制一致してしまう場合があるのでその場合は===を利用する。
ここで真偽値表を確認するとよい

@foreach($array as $key => $val)
  {{ Form::radio('sample_radio', $key, ($key == old('sample_radio', $user->sample_radio)), ['id' => 'radio'.$key]) }}
  {{ Form::label('radio'.$key, $val) }}
@endforeach

チェックボックス

第3引数についてはbooleanを設定する。
この場合はin_arrayの戻り値を利用している。($keyが各配列に存在するかチェックしている)
in_arrayの比較が不安だという方はin_arrayの第3引数にtrueと書きましょう。

@foreach($array as $key => $val)
  {{ Form::checkbox('sample_check[]', $key, in_array($key, old('sample_check', $user->sample_check)), ['id' => 'check'.$key]) }}
  {{ Form::label('check'.$key, $val) }}
@endforeach

テキストエリア

HTMLを出力したい場合があるのでサンプルでは「!!」でエスケープ処理を解除した例を書いておく

{!! Form::textarea('memo', old('memo', $user->memo), ['class' => 'form-control']) !!}

最後に

自分用のメモですが、誰かのためになれば幸いです。

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

Javascriptでテーブルの特定列の表示・非表示を切り替える

実装したいもの

HTMLのテーブルで、チェックボックスを用いて、列ごとに値の表示・非表示を行いたい。

  • 住所
  • 年齢
氏名 住所 年齢
山田 東京 20
佐藤 神奈川 24
渡辺 大阪 18

たとえば、上記テーブルで「住所」の列だけを、非表示になるようにしたい。

  • 住所
  • 年齢
氏名 年齢
山田 20
佐藤 24
渡辺 18

こんな感じ。

コード

以下のようにコードを記述する。テーブルはPHPで出力していてもよい。

sample.html
<body>
<input type="checkbox" id="address_check" onclick="checkbox_cell(this,'address_display')" checked="checked">住所
<input type="checkbox" id="age_check" onclick="checkbox_cell(this,'age_display')" checked="checked">年齢
<table>
 <tr>
  <th>氏名</th>
  <th id="address_display">住所</th>
  <th id="age_display">年齢</th>
 </tr>
 <tr>
  <td>山田</td>
  <td>東京</td>
  <td>20</td>
 </tr>
 <tr>
  <td>佐藤</td>
  <td>神奈川</td>
  <td>24</td>
 </tr>
 <tr>
  <td>渡辺</td>
  <td>大阪</td>
  <td>18</td>
 </tr>
</table>
</body>
sample.js
window.onload = function(){
var array = ["address","age"];
    for(var j=0;j<array.length;j++){
        var id = array[j] + "_display";
        var obj = array[j] + "_check";
        var CELL = document.getElementById(id);
        var TABLE = CELL.parentNode.parentNode.parentNode;
        for(var i=0;TABLE.rows[i];i++) {
            TABLE.rows[i].cells[CELL.cellIndex].style.display = (document.getElementById(obj).checked) ? '' : 'none';
        }
    }
}
function checkbox_cell( obj,id ){
    var CELL = document.getElementById(id);
    var TABLE = CELL.parentNode.parentNode.parentNode;
    for(var i=0;TABLE.rows[i];i++) {
        TABLE.rows[i].cells[CELL.cellIndex].style.display = (obj.checked) ? '' : 'none';
    }
}

注意点

  • 先にJavascriptを読み込んでおく。
  • checked="checked"は消しても動作する。必要に応じて使い分ける。

参考サイト

https://q.hatena.ne.jp/1192623624

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

PHPExcelでcellごとに加工する

はじめに

これは、いえらぶアドベントカレンダー用に投稿したものです。

  • 文系エンジニア
  • 入社1年目

とかいうプログラミング初心者が、特に下半期によくやったPHPExcelを使う案件で困ったことと見出した解決方法について書きます。
ググってもなかなか出てこなかった内容なので、同じ課題を抱えている人の参考になれば幸いですし、もしここ間違ってるよ、こうやったほうがいいよ、というものがあればコメントをいただけますと、めちゃくちゃ喜びます。

案件の経緯

あるとき、とあるクラウドサービスがありました。

そのサービスには、契約書をExcelで出力できる機能がありました。

出力した契約書には、契約内容が自動で入力されていました。

あら、なんて便利なんでしょう。

しかし、クライアントは言いました。

「もっと使えるようにしてほしい」
※もちろん、盛ったうえでの意訳です。

本題

要望が多岐にわたっていたので、いろいろやったんですが、
そのなかでもセルの加工に悩まされました。

例えば、「このセルの色を変えたい!」となったとき、
「PHPExcel 色 変更」とかで調べます。

そこででてきたコードがこちら

$sheet->getStyle( 'A1' )->getFill()->setFillType( PHPExcel_Style_Fill::FILL_SOLID )->getStartColor()->setARGB( 'FFFFCCCC');

参考:https://elearn.jp/phpexcel/cell/setfilltype.html

ふむふむ、なるほど。

なんかシートを取得して、文字列でセルを指定して加工するんだね。

おっしゃ!!!やったろ!!!!

そして、確認した既存のソースコードがこちら。

// シートから行を取得してループ
foreach ($sheet->getRowIterator() as $row) {
    // 行からセルを取得してループ
    foreach ($row->getCellIterator() as $cell) {
        // 取得したセルを加工する関数へジャンプ!
        $this->editCell($cell);
    }
}
/* ※関数名やコメントアウトは変えてます。 */

・・・。

シートのまま加工するんじゃないんか!!!

さっきググったやつと違う!!!!

文字列でセルの指定なんかできなくないか!?!?

というわけで、他のサイトも探してみました。

しかし、探しても探しても$sheet->からそのまま加工するやり方ばかり、、、

$cell->から始まる加工方法が全然でてきません。

結論

いろいろ検索して、試してみて、トライアンドエラーを繰り返していたので、結論に至った経緯をはっきり覚えているわけではありませんが、、、

var_dumpを加工した社内の独自関数を使って、objectの中をみたり
トライアンドエラーのなかでも、「目的は果たせなかったけどここは使える気がする」みたいな学びをしつつ、少しづつPHPExcelの理解を深めました。

そして出た結論。

PHPExcel_Cellの中にあるメソッドで使えそうなものを探せばセルごとに加工できる。

よく考えれば、インスタンス化したクラス(オブジェクト)の中にあるメソッドを->で使えるよっていう基本のところを理解したうえで、
ライブラリの深部までじっくり読むっていうことをすればどうってことなかったんですね。

自分はまだ初心者だし、そんなところ見ても分からないっていう先入観から、避けてしまっていました。

ビビらずにやってみるというのができていればもっと早く解決できたかもしれません。

まあ、やりたかったことの中には、そこまでわかってもやり方が分からず、先輩にパスしてしまったものもあるんですが、、、

さいごに

最初は、きちんとcellごとに加工する方法まで書こうと思っていたのに、あんまり会社のソースコード公開するのもどうなんだろ~と思っていたら一般論みたいな結論になっていました。
釣りみたいなタイトルですみません。
釣ろうと思ったわけではないです。

そんな感じで、私は社内でPHPExcelに詳しい人トップ8くらいには入れたのではないかと思います。(大きくでたのか小さくでたのかわからない)

これからも、このライブラリの使い方わかんね~~~~!!!!!ってなったときは、Google先生に頼りつつも自分でどうにかするようにしたいですね。

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