20201206のPHPに関する記事は21件です。

【2020年版】明日話せる!PHP Weekly News 注目記事をご紹介!

はじめに

皆さんこんにちは。MasaKuです。
ブラックフライデーでPCパーツを購入しホクホクしております。

ラクスアドベントカレンダー 7日目です。

今回執筆する記事について

今年、PHP関連のビックニュースとしましては、以下のようなものがありました。

  • PHP誕生25周年
  • PHP 8
  • Larvel 8
  • Composer 2.0
  • Xdebug 3.0

また、世間的には新型コロナウイルスの流行による働き方改革が世界的に実施され、それの付随するテーマも大きく話題になりました。

私の情報収集源としては、Twitterやその他メディアから仕入れることが多いですが、今年一番の情報源になった媒体は PHP: The Right Way でも紹介されている PHP Weekly News というPHP関連のメルマガです。

というわけで、今年 PHP Weekly News で発信された記事の中で感銘を受けたものをご紹介したいと思います。

PHP Weekly News について

記事は週1回メルマガ配信されており、構成は以下の通りです。

  • 1. Articles
    • いわゆる時事ネタ。PHPに限らずエンジニアに対する自己啓発的な内容もある。
  • 2. Tutorials and Talks
    • PHPに関連する基礎知識やツール、設計など技術寄りの雑多なネタ
  • 3. News and Announcements
    • フレームワークやツールのバージョンアップ関連のニュース
  • 4. Podcasts and Vlogs
    • ポッドキャストやVlogの更新紹介
  • 5. Reading and Viewing
    • 書籍関連のお知らせ
  • 6. Jobs
    • 求人情報

今回は以下のカテゴリのものから注目すべき記事をピックアップしていきたいと思います。

  • 1. Articles
  • 2. Tutorials and Talks

本題

さっそく気になったニュースを以下の通りご紹介いたします。

※1 執筆時期が12月上旬ですので、12月分の記事は現在少なめです。年内のうちに随時更新していきたいと思います。
※2 私がメルマガの購読を開始したのは 2020年6月 からなので、それ以前の内容についてはアーカイブより参照したものとなりますのでご了承ください。

1月

2月

3月

4月

5月

6月

7月

8月

9月

10月

11月

12月

おわりに

いかがでしたでしょうか。
時系列で並べてみると、この時期だったかと今年一年を振り返ることができたように感じました。

PHP界隈にとっては怒涛の1年だったのではないでしょうか。
特にPHP8の登場によってPHPが利用される範囲がより拡大される可能性が垣間見えた1年となったのではないでしょうか。

皆さんのお役に立てる情報へリンクできていたら幸いです!

以上で私の記事は終了とさせていただきたいと思います。

次回は、EichiSandenさんの記事です。
お楽しみに!

参考サイト

PHP Weekly News 公式HP
http://www.phpweekly.com/

PHP Weekly News Twitter アカウント
https://twitter.com/PHPWeeklyNews

PHP: The Right Way
http://ja.phptherightway.com/

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

テンプレートエンジンを使ってコード生成

はじめに

如何にしてコーディングを楽にするかを考えることは重要です。
DBスキーマと一対一で作成されているModelみたいな、同じような構造のファイルが並んでいるのを見るとコード生成をしたくなりますね。
そんなとき、自分はスクリプト言語とテンプレートエンジンを使ってちゃっちゃとコード生成スクリプトを組んでいます。

使用言語

色々なスクリプト言語はありますが、当記事では下記を使ったコード生成スクリプトについて記載しています。

  • php
  • twig(テンプレートエンジン)

Twigテンプレートエンジンとは

軽量・高速なPHPのテンプレートエンジンです。
もとはLaravelなどのフレームワークと組み合わせてHTMLを吐き出すのに使います。(MVCでいうViewの部分)
Viewに渡された変数をView側で変換できる、「フィルター」という機能が便利で、簡潔かつ直感的にテンプレートファイルを記載することができます。
例えば標準フィルターとして搭載されているupperはViewに渡された文字列をすべて大文字にします。

{# フィルター機能はbashにおけるパイプ('|')のようなイメージ #}
{# 変数の後ろの'|'で区切られているフィルター処理によって随時変換される #}
{# 変数name内に例えば'apple'が渡された場合、下記の出力は'APPLE'になる #}
{{ name|upper }}

Twigには大量の便利な標準フィルターが搭載されています。

さらに独自のフィルターを作成することもできます。

実践

目的

例えば下記のようなCSVファイルがあるとして、物理名からモデルファイルを生成したい場合を考えます。
プロパティとして$id$name$numがあり、そのgetterとsetterが存在するphpクラスファイルを生成するイメージです。

id,name,num
1,りんご,3
2,みかん,5
3,さくらんぼ,7

準備

composerとtwigのインストールを行っておきます。

php composer.phar require "twig/twig:^3.0"
  • 今回用いるディレクトリ構成は以下
.
├── composer.json
├── composer.lock
├── composer.phar
├── data
│   └── test.csv
├── main.php
├── output
├── template
│   └── model.tpl
└── vendor
    └── ...

コード

生成したいソースコードのテンプレートファイルを作成し、phpファイルに向けて書き出すことでコード生成を成立させます。
ucfirstは最初の文字を大文字にするカスタムフィルターです。(twigの標準フィルターには存在しなかったため作成しています。)

  • main.php
<?php

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

use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;

$loader = new FilesystemLoader(dirname(__FILE__) . '/template');
$twig = new Environment($loader);

// カスタムフィルター定義
$twig->addFilter(
    new TwigFilter(
        'ucfirst',
        function (string $str) {
            return ucfirst($str);
        }
    )
);

// csv読み込み(物理名取得)
$fp = fopen("./data/test.csv", "r");
$columns = fgetcsv($fp);
fclose($fp);

// ソースコード吐き出し
$template = $twig->load("model.tpl");
file_put_contents(
    "./output/test.php",
    $template->render(["className" => "test", "columns" => $columns])
);

  • model.tpl
<?php

namespace Path/To/Model;

class {{className|ucfirst}}
{

{% for column in columns %}
    /**
     * @var string
     */
    private string ${{column}};

{% endfor %}

    /**
     * {{className}} constructor.
{% for column in columns %}
     * @param string ${{column}}
{% endfor %}
     */
    public function __construct(
{% for column in columns %}
        string ${{column}}{% if not loop.last %},{% endif %}

{% endfor %}
    )
    {
{% for column in columns %}
        $this->{{column}} = ${{column}};
{% endfor %}
    }

{% for column in columns %}
    /**
     * @return string
     */
    public function set{{column|ucfirst}}(): string
    {
        return $this->{{column}};
    }

{% endfor %}

{% for column in columns %}
    /**
     * @return string
     */
    public function get{{column|ucfirst}}(): string
    {
        return $this->{{column}};
    }

{% endfor %}
}

実行

main.phpを実行すると下記のようなファイルがoutputディレクトリに吐き出されます。

  • test.php
<?php

namespace Path/To/Model;

class Test
{

    /**
     * @var string
     */
    private string $id;

    /**
     * @var string
     */
    private string $name;

    /**
     * @var string
     */
    private string $num;


    /**
     * test constructor.
     * @param string $id
     * @param string $name
     * @param string $num
     */
    public function __construct(
        string $id,
        string $name,
        string $num
    )
    {
        $this->id = $id;
        $this->name = $name;
        $this->num = $num;
    }

    /**
     * @return string
     */
    public function setId(string $id): string
    {
        return $this->id = $id;
    }

    /**
     * @return string
     */
    public function setName(string $name): string
    {
        return $this->name = $name;
    }

    /**
     * @return string
     */
    public function setNum(string $num): string
    {
        return $this->num = $num;
    }


    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getNum(): string
    {
        return $this->num;
    }

}

おわりに

CSVのスキーマから対応するコード生成を行う簡単なスクリプトを組みました。
テンプレートエンジンを使うことの利点は、構造情報取得ロジックの部分を変えずに、吐き出すソースコードの形を自由に変えられることだと思います。
ロジックとView(コードテンプレート)を切り分けることで、すばやくメンテのし易いコード生成スクリプトを作ることができるのではないでしょうか。

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

[自分用メモ]ローカルのXAMPP環境で複数サイトを制作する方法

0. やりたいこと

  • PHP学習用にxamppをローカル環境に入れているが、デフォルトの設定だと複数のWebサイトの制作ができない。 そこで、複数サイト制作を可能にするために、「バーチャルホスト」の設定をして、複数のドメインを使う設定を行った。 本記事はその作業の際の忘備録として残しておく。

1. 環境

  • Windows10
  • xampp環境構築済み

2. そもそも「バーチャルホスト」って何?

バーチャルホストって何?

Apache バーチャルホスト説明書 https://httpd.apache.org/docs/2.4/ja/vhosts/

バーチャルホストという用語は、1 台のマシン上で (www.company1.com and www.company2.com のような) 二つ以上のウェブサイトを扱う運用方法のことを指します。

test1.comやtest2.com等、複数のドメインを運用していた場合、それぞれドメインごとにサーバを建てるのではなく、1台のWebサーバで複数のドメインのサービスを構築しよう!ということが出来る。

図はこちら

【Apache】virtual hostでの複数URL運営
https://www.toyo104-memo.com/entry/apache-virtual-host
↓↓図の画像リンク
https://cdn-ak.f.st-hatena.com/images/fotolife/t/toyo--104/20180610/20180610001649.png

早速、やってみよう。

3. 手順

  • 「localhost」とは別の"2つ目のドメイン"用のルートフォルダを作成する。

    • xamppのインストールフォルダが「C:\xampp」なので、「C:\xampp\htdocs」以下に新しいフォルダを作成する。
    • 今回作成するドメイン名は「example.com」とします。
  • バーチャルホスト設定ファイル編集の有効化:「C:¥xampp¥apache¥conf¥httpd.conf」の編集

    • バーチャルホストの編集ファイルが有効になっているか確認
    • 「Include conf/extra/httpd-vhosts.conf」部分の先頭のコメントアウト「#」があったら削除しておく
# Virtual hosts
Include conf/extra/httpd-vhosts.conf
  • バーチャルホストを有効化:「C:\xampp\apache\conf\extra」配下の「httpd-vhosts.conf」を編集
    • 20行目付近: ##NameVirtualHost *:80の##をコメントアウト
    • ファイルの一番最後に有効にしたいドメインの情報を記述
<VirtualHost *:80>
    DocumentRoot "C:\xampp\htdocs\example.com"
    ServerName example.com
</VirtualHost>

・DocumentRoot:ドメインの一番上のルートフォルダのパス
・ServerName:ルートフォルダに紐づけられるドメイン名

  • hostsファイル<host>にドメインを追加
    • 「C:\WINDOWS\system32\drivers\etc」下の「hosts」ファイルを編集し、新しく追加したドメインを記述
127.0.0.1 example.com

XAMPPを再起動すると、ドメインが有効になる。

4. まとめ

複数のサービスを同時に開発することもあるので、この手順はよく使う。
自分用メモです。

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

laravelでゲストログイン機能を作成

今やポートフォリオには必須の機能とされているゲストログイン機能を作成しました

環境
PHP 7.3.11
Laravel Framework 7.29.3

今回はLoginControllerをいじっていきます

LoginController.php
//省略//

public function guestLogin() {
        $name = 'ゲスト';
        $password = 'guestpass';

        if(Auth::attempt(['name' => $name, 'password' => $password])) {
            return redirect('/home');
        }

        return redirect('/');
    }

Auth::attemptは引数に指定したレコードがDB内にあればtrue,そうでなければfalseを返す

今回は予めゲストログイン用のユーザーアカウントを作成しておき名前とパスワードをname,passwordに代入、
Auth::attemptメソッドの引数に指定すればもちろんtrueが帰ってくるので認証が成功し
/homeにリダイレクトされるといった感じです。

web.php
Route::get('/login/guest', 'Auth\LoginController@guestLogin');

/login/guestを踏めばguestLoginメソッドが実行されます

guestLoginComponent.vue
<template>
  <div>
    <a class="nav-link" @click="openModal">ゲストログイン</a>
    <div class="overlay" @click="closeModal" v-if="showContent">
      <div class="dialog" @click="stopEvent">
        <p class="borderbottom pt-2 pb-4">ゲストユーザーとしてこのサイトをお試しになれます</p>
        <p class="text-right pr-2">
          <a @click="closeModal" class="mr-4 black">キャンセル</a>
          <a href="/login/guest" class="ml-auto">ログイン</a>
        </p>
      </div>
    </div>
  </div>
</template>

<script>
   export default {
     data() {
       return {
         showContent: false
       }
     },

     methods:{
      stopEvent(){
        event.stopPropagation()
      },
      openModal(){
        this.showContent = true
      },
      closeModal(){
        this.showContent = false
      }
    }
  }
</script>


今回はゲストログインボタンを押したらダイアログが表示され「ログイン」をクリックしたらメソッドが発火するというデザインにしてみました。

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

googleアカウントでログインする機能を実装した時のメモ

googleアカウントによるログイン機能の実装について紹介します。
通常のログイン画面にグーグルアカウント連携ボタンを追加しました。
GCPへの登録やグーグルAPIの勉強から、既存のDBへの書き込み作業等のデバッグでかなり苦労したので、メモ書きとして記録しておきます。

1.事前準備(GCPの登録)
まずGoogle Cloud Platformのページで左上の「Project」よりプロジェクトを作成を選択します。そのあと「OAuth同意画⾯の設定」に進み、「クライアントID」、「クライアントシークレット」、「URI」を設定します。
詳細は以下リンクを参照ください。
https://qiita.com/kmtym1998/items/768212fe92dbaa384c27

2.composerのインストール
※composerをインストールしておく必要があるので、composerの事前知識がない人は詳細を確認してください。1行目のコマンド(require '../../vendor/autoload.php'; )がgoogleAPIを読み込んでいて、今回のアカウント連携機能に必要になります。

サーバー環境に接続して以下のコマンドを入力します。
(サーバーやローカル環境の接続方法は各自確認ください。)
php composer.phar require google/apiclient

これで事前準備は完了です。

3.redirect.phpの作成
つづいて、実装していきます。
作成するファイルは1つです。

redirect.php
<?php

require '../../vendor/autoload.php';     
session_start();


// init configuration
$clientID = '***************************.apps.googleusercontent.com';
$clientSecret = '*************************_';
$redirectUri = '******************/redirect.php';

// create Client Request to access Google API
$client = new Google_Client();
$client->setClientId($clientID);
$client->setClientSecret($clientSecret);
$client->setRedirectUri($redirectUri);
$client->addScope("email");
$client->addScope("profile");

if (isset($_GET['code'])) {
  $token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
  $client->setAccessToken($token['access_token']);

  // get profile info
  $google_oauth = new Google_Service_Oauth2($client);
  $google_account_info = $google_oauth->userinfo->get();
  $email =  $google_account_info->email;
  $name =  $google_account_info->name;

  /* now you can get mail and account information*/

    require_once("db.php");
    $dbh = db_connect();     /* this is connection to exsiting MySQL*/

            try {
                $statement = $dbh->prepare("SELECT * FROM {$tablename1} WHERE mail= :email and account= :account");  
                $statement->bindParam( ':email',$email, PDO::PARAM_STR);
                $statement->bindParam( ':account',$name, PDO::PARAM_STR);

                $statement->execute();
                $count = $statement->rowCount();   

                /*  CHECK EMAIL AND NAME IN DATABASE */
                if ($count){
                           $_SESSION["NAME"] = $name;    
                    }else{

                /* INSERT USER TO DATABASE */        
                    $statement = $dbh->prepare("INSERT INTO {$tablename1} (account,mail) VALUES (:account,:mail)");
                    $statement->bindValue(':account', $name, PDO::PARAM_STR);
                    $statement->bindValue(':mail', $email, PDO::PARAM_STR);
                    $statement->execute();

                    $_SESSION["NAME"] = $name;
                    $dbh->commit();

                    $dbh = null;         
                    }

               } catch (PDOException $e) {
                        $errors['database'] = 'データベースエラー';
                         echo $e->getMessage();   
               } 

           $_SESSION["NAME"] = $name;    
          $move="******************************" ;    
          header("Location: $move");        /* move to redirect page */    
}

?>

<html lang="en">
  <body>

        <p><?php echo "<a href='".$client->createAuthUrl()."'>Google Login</a>" ?></p>

  </body>
</html>

解説します。

$clientID = '***************************.apps.googleusercontent.com';
$clientSecret = '*************************_';
$redirectUri = '******************/redirect.php';

この部分でGCPで設定した項目を入力してください。
$redirectUri部分は、移動したいサイトのアドレスを入力するのが本来の使い方のようですが、どうもそれをすると、アカウント情報を処理する前にサイト移動してしまうので、ここでは同じページ(redirect.php)を指定しています。

 require_once("db.php");
 $dbh = db_connect();     /* this is connection to exsiting MySQL*/

この部分はMySQLへ接続するためのコードです。

 /*  CHECK EMAIL AND NAME IN DATABASE */
                if ($count){
                           $_SESSION["NAME"] = $name;    
                    }

この部分で既存のアカウントかどうかを判別しています。
既存のアカウントであれば、そのままログインします。

else{
        /* INSERT USER TO DATABASE */        
         $statement = $dbh->prepare("INSERT INTO {$tablename1} (account,mail) VALUES (:account,:mail)");
        $statement->bindValue(':account', $name, PDO::PARAM_STR);
         $statement->bindValue(':mail', $email, PDO::PARAM_STR);
         $statement->execute();

既存アカウントでない場合は、この部分でMySQLにアカウント登録しています。

    $_SESSION["NAME"] = $name;    
          $move="******************************" ;    
          header("Location: $move");        /* move to redirect page */

最後に、この部分でページ移動しています。
$move=の部分に移動先のページ(https:// )を記載します。

実際に作ったページがこちらです。
https://estacionsuzuki.com/engineering/community/login/index.php

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

【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン

概要

docker(docker-compose)でLEMP環境(PHP/nginx/MySQL)を構築し、Laravelの新規プロジェクト作成、構築した環境を破棄してから環境の再構築までをハンズオン形式で行います。

ご注意

記事を寄稿したタイミングでは、補足の解説は特になくコマンドだけのご紹介だったので、コマンド部分のみコピペすれば20分で終わる内容でした。
しかし、継ぎ足しで解説を追記していった結果、普通に読みながら進めていくと2時間ぐらいのボリュームになってしまってます?

ご了承下さい?‍♂️

更新履歴

  • 2020/09/09 Laravel8(当時はLaravel6が出た頃)がリリースされ、こちらの記事も内容が古くなったので全て書き直しました。

目的

Dockerの細かい概念などはすっ飛ばして、
このハンズオンの通りに進めればDocker×Laravelの環境を構築できます。
習うより慣れろで行ってみましょう!

ハンズオン終了時のGitHub

https://github.com/ucan-lab/docker-laravel-handson

ハンズオンイベント

【ぺちオブ】ゆーきゃんプレゼンツ!Laravel + Docker 環境構築ハンズオン

https://phper-oop.connpass.com/event/137152

あの有名なぺちオブのイベントとしてハンズオン講師をやらせていただきました!
ありがとうございます?

レビュワー

スペシャルサンクス?‍♂️?

前提条件

  • Mac基準ですが、Windowsでも動作することを確認済み
  • コマンドは [環境] $ とprefixを付けてます。
  • 所要時間は事前準備部分を除いて20分を目安としてます。
  • エディタはファイル編集時にお好みのもので開いて作業ください?

構築環境(Dockerイメージ、各種バージョン)

  • php: 7.4-fpm-buster
  • composer: 1.10
  • nginx: 1.18-alpine
  • mysql: 8.0
  • Laravel: 8.x

dockerおすすめ書籍

冒頭で習うより慣れろと言いましたが、習っておくのもそれはそれで良いことなのでオススメの書籍や記事をご紹介します。私はこの辺りの記事や本を読んで勉強したのでオススメしてます。

マンガでわかるdockerシリーズ

@llminatoll さんがDockerについてマンガで分かりやすく説明されてます。
初めての方はこちらのマンガでDockerの概念を学んでから進めると理解しやすいです。

dockerおすすめ記事・サイト

公式系

記事

スライド

事前準備

Windowsユーザー

Git Bashのインストールをお願いします。(Linuxコマンドをそのまま打てるようになります)

Git Bash を起動した際は下記のコマンドを実行ください。

$ exec winpty bash

GitHubアカウントの作成

Gitの初期設定

user.emailuser.name が設定されていればok!
Gitの初期設定を行ってない場合、下記の記事を参考に初期設定をお願いします。

Mac Git 初期設定

最終的に下記のように表示されればokです。

$ git config --list | grep user
user.email=xxx@yyy.com
user.name=zzz

GitHub SSH接続設定

$ ssh -T github.com
Warning: Permanently added 'github.com,52.69.186.44' (RSA) to the list of known hosts.
Hi ucan-lab! You've successfully authenticated, but GitHub does not provide shell access.

successfully authenticated の文字が出ればok!
Warningは気にしなくてok!

GitHub SSH接続設定を行ってない場合、下記の記事を参考に初期設定をお願いします。
もしくはハンズオンをHTTPSに置き換えて進めてください。

Mac GitHub SSH接続設定

docker, docker-compose のインストール

[Mac] Docker for Macをインストール

[Windows] Docker for Windowsをインストール

イントール確認

[mac] $ docker --version
Docker version 19.03.12, build 48a66213fe

[mac] $ docker-compose --version
docker-compose version 1.26.2, build eefe0d31

Docker起動確認

インストールしたDockerを起動してください。
running 状態になっていればokです。

スクリーンショット 2019-09-29 1.39.55.png


ここまでで事前準備終了です。


今回のハンズオンのゴール

  • 3層アーキテクチャのコンテナの構築
    • ウェブサーバー(web)
      • nginxで静的コンテンツ配信サーバを構築
    • アプリケーションサーバー(app)
      • nginxを経由してPHPを動作させるアプリケーションサーバを構築
      • PHPパッケージ管理ツールComposerのインストール
    • データベースサーバー(db)
      • MySQLデータベースサーバーの構築
  • Laravelをインストールしてwelcome画面の表示
  • LaravelとMySQLを連携し、マイグレーションを実行
  • Docker環境の破棄
  • Docker環境をGitHubから再構築

?【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン?

  • 所要時間: 20分目安

作業ディレクトリを作成

[mac] $ mkdir docker-laravel-handson
[mac] $ cd docker-laravel-handson

最終的なディレクトリ構成

.
├── README.md (この名前にするとGitHubで見た時にHTMLに変換して表示してくれる)
├── infra (*1)
│   ├── mysql (*1)
│   │   ├── Dockerfile
│   │   └── my.cnf (*1)
│   ├── nginx (*1)
│   │   └── default.conf (*1)
│   └── php (*1)
│       ├── Dockerfile (この名前にするとファイル名の指定を省略できる)
│       └── php.ini (*1)
├── docker-compose.yml (この名前にするとファイル名の指定を省略できる)
└── backend (*1)
    └── Laravelをインストールするディレクトリ

このディレクトリ構成を目指します。
(*1) 任意の名前に変更してもokです。

リモートリポジトリを作成

https://github.com/new

GitHub Create remote repository
GitHub docker-laravel-handson

リポジトリ名 docker-laravel-handson のGitHubリモートリポジトリを作成する。

リモートリポジトリへ初回コミット&プッシュ

[mac] $ echo "# docker-laravel-handson" >> README.md
[mac] $ git init
[mac] $ git add README.md
[mac] $ git commit -m "first commit"
[mac] $ git branch -M main

※ 2020/10/1 からデフォルトブランチがmasterからmainに変更されました。

リモートリポジトリを登録します。
リモートリポジトリ先は適宜置き換えてください。

[mac] $ git remote add origin git@github.com:ucan-lab/docker-laravel-handson.git

リモートリポジトリ先が正しく設定されていることを確認してください。

[mac] $ git remote -v
origin  git@github.com:ucan-lab/docker-laravel-handson.git (fetch)
origin  git@github.com:ucan-lab/docker-laravel-handson.git (push)

# もしリモートリポジトリ先を間違えた場合は下記のコマンドから変更できます。
[mac] $ git remote set-url origin <リモートリポジトリ>

リモートリポジトリ先の名前を origin と付けられていること。
リモートリポジトリ先のURL等に間違いがないことを確認する。

GitHubへpushします。

[mac] $ git push -u origin main
  • -u オプションは --set-upstream の省略
    • ローカルリポジトリの現在のブランチ(main)をリモートリポジトリ(origin)のmainにpush先を設定
    • 以降は git push だけで git push origin main と同義

GitHubのリモートリポジトリのmainブランチへpushされていることを確認します。

お好みのGUIエディタで開く

ymlファイルをCUIエディタで書いていくのは大変なのでお好みのGUIエディタ(今回はVSCode)で開きます。
code コマンドがインストールされていれば、簡単にプロジェクトをVSCodeで開けます。

[mac] $ code .

ScreenShot 2020-10-15 2.34.39.png

また、 control + shift + @ で統合ターミナルをVSCodeで開くことができます。
ファイルの編集もコマンドの実行もVSCodeだけで行えます。

ScreenShot 2020-10-15 2.36.26.png

アプリケーションサーバ(app)コンテナを作る

PHPアプリケーションサーバコンテナを作成します。
公式のPHP-FPMイメージをベースイメージとしてカスタマイズします。

ディレクトリ構成

.
├── infra
│   └── php
│       ├── Dockerfile
│       └── php.ini # PHPの設定ファイル
├── backend # Laravelをインストールするディレクトリ
└── docker-compose.yml

docker-compose.yml を作成する

touch コマンドで空ファイルを作成してますが、作成後はお好みのエディタで編集ください。
ファイル名のタイプミス防止のため空ファイルだけ作成してます。
当ハンズオンはこの流れで進めていきます。

[mac] $ touch docker-compose.yml

作成した docker-compose.yml を下記の通りに編集します。

docker-compose.yml
version: "3.8"
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work
  • docker-compose.yml ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。

docker-compose.yml: 設定値の補足

docker-compose.yml
version: "3.8"

Docker Composeファイルのバージョンを指定しています。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-versioning/

通常はメジャー番号とマイナー番号を両方指定します。
ちなみにマイナーバージョンを指定しない場合はデフォルト0が使用されます。

docker-compose.yml
# 下記は同じ指定になる
version: "3"
version: "3.0"
docker-compose.yml
services:
  app: # => サービス名は任意
    build: ./infra/php
    volumes:
      - ./backend:/work

サービス名に app (アプリケーションサーバー)の名前を付けて定義しています。
サービス名は任意に決められます。

build: で指定しているのはビルドコンテキストを指定します。
ビルドコンテキストとは、docker buildを実行する際の現在の作業ディレクトリのことをビルドコンテキスト(build context)と呼びます。

Dockerfile が置かれている ./infra/php ディレクトリをビルドコンテキストとして指定します。
Dockerビルドの際は Dockerfile のファイルを探すので、ファイル名の指定は不要です。

volumes: ではホスト側のディレクトリや名前付きボリュームをコンテナ側へマウントしたい時に指定します。
今回はホスト側の ./backend ディレクトリをappサービスのコンテナ内 /work へマウントしてます。

./docker/php/Dockerfile を作成する

  • Composerコマンドのインストール
  • Laravelで必要なPHP拡張機能のインストール
    • bcmath, pdo_mysql が不足しているので追加インストール
[mac] $ mkdir -p infra/php
[mac] $ touch infra/php/Dockerfile

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

infra/php/Dockerfile
FROM php:7.4-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer

RUN apt-get update && \
  apt-get -y install git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /work

./docker/php/Dockerfile: 設定値の補足

Dockerfileはテキストファイルであり、Dockerイメージを作り上げるために実行する命令をこのファイルに含めることができます。

各種Docker命令を解説します。

FROM php:7.4-fpm-buster

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#from

FROM命令はイメージビルドのためのベースイメージを設定します。
FROM イメージ名:タグ名 で指定します。

php | Docker Hub公式イメージをベースイメージとして利用します。

SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#shell

SHELL命令は何も指定しない場合は SHELL ["/bin/sh", "-c"] がデフォルト値となります(Linuxの場合)

パイプ中のあらゆる段階でエラーがあれば失敗とするため、pipefailオプションを明示的に指定してます。

-o オプションはオプションを設定するためのオプションです。
-e オプションを定義しておくと、そのシェルスクリプト内で何らかのエラーが発生した時点で、それ以降の処理を中断できます。
-u オプションを定義しておくと、未定義の変数に対して読み込み等を行おうとした際にエラーとなります。
-x オプションを定義しておくと、実行したコマンドを全て標準エラー出力に出してくれます。

おまじないと思ってもらえればokです。SHELL命令は必須ではないです。

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#env

ENV命令はコンテナ内のサーバー環境変数を設定します。

COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#copy

RUN apt-get update && \
  apt-get -y install git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#run

Debian系のパッケージ管理ツールは apt-get, apt とありますが、Dockerfile内で apt を実行するとCLIインターフェース向けではないと警告が表示されるため、apt-getを使用します。

apt-get update インストール可能なパッケージの「一覧」を更新します。
実際のパッケージのインストール、アップグレードなどは行いません。

apt-get -y install xxx Laravelのインストールに必要なパッケージをインストールします。
下記のパッケージをインストールしておけばokだと思います。

apt-get clean && rm -rf /var/lib/apt/lists/* ここはパッケージインストールで使用したキャッシュファイルを削除しています。

phpの公式Dockerイメージには、docker-php-ext-install, docker-php-ext-enable, docker-php-ext-configure のPHP拡張ライブラリを簡単に利用するための便利コマンドが予め用意されています。

docker-php-ext-install intl pdo_mysql zip bcmath PHPの拡張ライブラリをインストールしています。

COPY ./php.ini /usr/local/etc/php/php.ini

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#copy

WORKDIR /work

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#workdir

./docker/php/php.ini を作成する

php.ini はPHPの設定ファイル

  • PHPエラーメッセージの設定
  • PHPエラーログの設定
  • メモリ等の設定(お好みで)
  • タイムゾーン設定
  • 文字コード設定
[mac] $ touch infra/php/php.ini
php.ini
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

build & up

  • appコンテナの作成
  • PHPのバージョン確認
  • Laravelで必要なPHP拡張機能の確認
[mac] $ docker-compose up -d --build
  • docker-compose コマンドは docker-compose.yml があるディレクトリで実行します。
  • docker-compose updocker-compose.yml に定義したサービスを起動します。
  • -d 「デタッチド」モードでコンテナを起動します。
    • デフォルトは「アタッチド」モードで全てのコンテナログを画面上に表示
    • 「デタッチド」モードではバックグラウンドで動作
  • --build コンテナの開始前にイメージを構築します
    • 特に変更がない場合はキャッシュが使用されます。
[mac] $ docker-compose ps
            Name                          Command              State    Ports  
-------------------------------------------------------------------------------
docker-laravel-handson_app_1   docker-php-entrypoint php-fpm   Up      9000/tcp

docker-laravel-handson_app_1 コンテナの State が Up になっていたら正常に起動している状態です。

  • docker-compose ps コンテナ一覧を表示します。
    • Name: コンテナ名
    • Command: 最後に実行されたコマンド
    • State: 状態(Up)はコンテナが起動している状態
    • Ports: 9000/tcp(コンテナのポート)
    • 9000番のホストポートは公開されていないので、コンテナの外からはアクセスできない

appコンテナ内ミドルウェアのバージョン確認(コンテナに入ってコマンド実行)

作成したappコンテナの中に入ってPHP, Composerのバージョン、インストール済みの拡張機能を確認します。
Laravel 7.xのサーバ要件に必要な拡張機能が入っていることを確認します。

[mac] $ docker-compose exec app bash

# PHPのバージョン確認
[app] # php -v
PHP 7.4.6 (cli) (built: May 15 2020 13:01:21) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

# Composerのバージョン確認
[app] # composer -V
Composer version 1.10.10 2020-08-03 11:35:19

# インストール済みの拡張機能の一覧
[app] # php -m
[PHP Modules]
bcmath
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
intl
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]

exit でコンテナの外に出ます。

[app] $ exit

control + d でもコンテナから出られます。

補足説明

appコンテナ内に入ってphpコマンドを実行しています。

  • docker-compose exec 実行中のコンテナ内で、コマンドを実行します。
  • app サービス名(コンテナ名)を指定します。

PHPのバージョン確認(コンテナの外からコマンド実行)

コンテナの外から php コマンドを実行することもできます。

  • docker-compose exec [サービス名] [実行したいコマンド]
[mac] $ docker-compose exec app php -v
PHP 7.4.6 (cli) (built: May 15 2020 13:01:21) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

結果にコミット

[mac] $ git status
  (use "git add <file>..." to include in what will be committed)
    docker-compose.yml
    infra/php/Dockerfile
    infra/php/php.ini

[mac] $ git add .
[mac] $ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    new file:   docker-compose.yml
    new file:   infra/php/Dockerfile
    new file:   infra/php/php.ini

[mac] $ git commit -m "feat create docker app container"
[main f5a637f] feat create docker app container
 3 files changed, 49 insertions(+)
 create mode 100644 docker-compose.yml
 create mode 100644 infra/php/Dockerfile
 create mode 100644 infra/php/php.ini

[mac] $ git log

ウェブサーバー(web)コンテナを作る

nginxウェブサーバーコンテナを作成します。
nginxのベースイメージをそのまま利用します。

ディレクトリ構成

.
├── infra
│   └── nginx
│       └── default.conf # nginxの設定ファイル
├── backend
│  └── public # 動作確認用に作成
│       ├── index.html # HTML動作確認用
│       └── phpinfo.php # PHP動作確認用
└─── docker-compose.yml

docker-compose.yml へ追記する

  • ポート転送の設定(今回は10080ポートにする)
  • タイムゾーンの設定
version: "3.8"
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work

  # 追記
  web:
    image: nginx:1.18-alpine
    ports:
      - 10080:80
    volumes:
      - ./backend:/work
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /work
  • docker-compose.yml ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。
  • app コンテナの設定と同じインデントレベルで貼り付けます。

docker-compose.yml: 設定値の解説

    image: nginx:1.18-alpine

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#image

コンテナを起動させるイメージを指定します。
nginx | Docker Hubを指定してます。
今回は公式のnginxイメージをそのまま利用しています。(Dockerfileは不要)

ちなみにnginxは1.10, 1.12 等の偶数のバージョンが安定バージョンになります。
特に理由がなければ偶数バージョンをご利用ください。

    ports:
      - 10080:80

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#ports

nginxへ外(ホスト側)からコンテナ内へアクセスさせるため公開用のポートを設定します。
ホスト側:コンテナ側 と設定します。

    volumes:
      - ./backend:/work
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#volumes

ホスト側にあるディレクトリ、ファイルをコンテナ内へマウントさせています。

docker/nginx/default.conf を作成する

[mac] $ mkdir infra/nginx
[mac] $ touch infra/nginx/default.conf

Laravel公式にnginxの設定例が用意されているので、こちらを流用します。
https://readouble.com/laravel/7.x/ja/deployment.html

default.conf
server {
    listen 80;
    server_name example.com;
    root /work/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

root, fastcgi_pass ドキュメントルート設定を書き換えてます。
nginxの設定を詳しく知りたい方は下記の記事がオススメです。

build & up

[mac] $ docker-compose down
[mac] $ docker-compose up -d --build
  • docker-compose コマンドは docker-compose.yml があるディレクトリで実行します。
[mac] $ docker-compose ps
            Name                          Command              State           Ports        
--------------------------------------------------------------------------------------------
docker-laravel-handson_app_1   docker-php-entrypoint php-fpm   Up      9000/tcp             
docker-laravel-handson_web_1   nginx -g daemon off;            Up      0.0.0.0:10080->80/tcp

docker-laravel-handson_web_1 コンテナの State が Up になっていたら正常に起動している状態です。

また、Ports の項目が、appコンテナは9000/tcp でwebコンテナは 0.0.0.0:10080->80/tcp と表示形式が異なってます。
これはホスト上の10080番ポートをコンテナの80番ポートへ割り当てています。

nginxのバージョン確認

[mac] $ docker-compose exec web nginx -v
nginx version: nginx/1.18.0

webコンテナの確認

  • webコンテナの動作確認
  • HTMLとPHPが表示されるか
[mac] $ mkdir backend/public
[mac] $ echo "Hello World" > backend/public/index.html
[mac] $ echo "<?php phpinfo();" > backend/public/phpinfo.php

http://127.0.0.1:10080/index.html

「Hello World」が表示されることを確認する。

http://127.0.0.1:10080/phpinfo.php

phpinfoの情報が表示されることを確認する。

[mac] $ rm -rf backend/*

確認用に作成したHTML, PHPファイルは不要なので削除します。

結果にコミット

[mac] $ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   docker-compose.yml
    modified:   infra/php/Dockerfile

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    infra/nginx/default.conf

[mac] $ git add .
[mac] $ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   docker-compose.yml
    new file:   infra/nginx/default.conf
    modified:   infra/php/Dockerfile

[mac] $ git commit -m "feat create docker web container"
[main 5e278c1] feat create docker web container
 3 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 infra/nginx/default.conf

[mac] $ git log

Laravelをインストールする

  • app コンテナに入り、Laravelをインストール
  • welcomeページが表示されるか
[mac] $ docker-compose exec app bash
[app] $ composer create-project --prefer-dist "laravel/laravel=8.*" .
[app] $ php artisan -V
Laravel Framework 8.0.0

[app] $ exit

Laravel ウェルカム画面の表示

http://127.0.0.1:10080

LaravelのWelcome画面が表示されることを確認する。

結果にコミット

[mac] $ git status
[mac] $ git add .
[mac] $ git status
[mac] $ git commit -m "feat laravel install"
[mac] $ git log

データベース(db)コンテナを作る

MySQLデータベースコンテナを作成します。
MySQLのベースイメージをそのまま利用します。

参考記事

ディレクトリ構成

.
├── infra
│   └── mysql
│       ├── Dockerfile
│       └── my.cnf # MySQLの設定ファイル
└── docker-compose.yml

docker-compose.yml へ追記する

  • データベース名やユーザー名等の接続情報とタイムゾーンの設定は環境変数で渡す
  • トップレベルvolumeを使用してデータの永続化
version: "3.8"
services:
  app:
    build: ./infra/php
    volumes:
      - ./backend:/work

  web:
    image: nginx:1.18-alpine
    ports:
      - 10080:80
    volumes:
      - ./backend:/work
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /work

  # 追記
  db:
    build: ./infra/mysql
    volumes:
      - db-store:/var/lib/mysql

volumes:
  db-store:

./docker/mysql/Dockerfile を作成する

[mac] $ mkdir infra/mysql
[mac] $ touch infra/mysql/Dockerfile

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

infra/mysql/Dockerfile
FROM mysql:8.0

ENV MYSQL_DATABASE=laravel_local \
  MYSQL_USER=phper \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret \
  TZ=Asia/Tokyo

COPY ./my.cnf /etc/mysql/conf.d/my.cnf
RUN chmod 644 /etc/mysql/conf.d/my.cnf

コメントでいただいたのですが、Windows環境でボリュームマウントを行うと、ファイルパーミッションが777となるようです。
my.cnf に書き込み権限が付いてるとMySQLの起動時にエラーが発生します。
その対策としてボリュームマウントではなくDockerfileを作成して my.cnf ファイルコピー、読み取り専用に権限変更してます。

docker/mysql/my.cnf を作成する

  • 文字コードの設定
  • タイムゾーンの設定
  • ログ設定
[mac] $ mkdir infra/mysql
[mac] $ touch infra/mysql/my.cnf
my.cnf
[mysqld]
# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_0900_ai_ci

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# Error Log
log-error = mysql-error.log

# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

# General Log
general_log = 1
general_log_file = mysql-general.log

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

build & up

[mac] $ docker-compose down
[mac] $ docker-compose up -d --build
  • docker-compose コマンドは docker-compose.yml があるディレクトリで実行します。
[mac] $ docker-compose ps
            Name                          Command              State           Ports        
--------------------------------------------------------------------------------------------
docker-laravel-handson_app_1   docker-php-entrypoint php-fpm   Up      9000/tcp             
docker-laravel-handson_db_1    docker-entrypoint.sh mysqld     Up      3306/tcp, 33060/tcp  
docker-laravel-handson_web_1   nginx -g daemon off;            Up      0.0.0.0:10080->80/tcp

docker-laravel-handson_db_1 コンテナの State が Up になっていたら正常に起動している状態です。

[mac] $ docker-compose exec db mysql -V
mysql  Ver 8.0.20 for Linux on x86_64 (MySQL Community Server - GPL)

マイグレーション実行(エラーが発生します)

[mac] $ docker-compose exec app bash
[app] $ php artisan migrate

   Illuminate\Database\QueryException 

  SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = laravel and table_name = migrations and table_type = 'BASE TABLE')

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:671
    667▕         // If an exception occurs when attempting to run a query, we'll format the error
    668▕         // message to include the bindings with SQL, which will make this exception a
    669▕         // lot more helpful to the developer instead of just the database's errors.
    670▕         catch (Exception $e) {
  ➜ 671▕             throw new QueryException(
    672▕                 $query, $this->prepareBindings($bindings), $e
    673▕             );
    674▕         }
    675▕ 

      +37 vendor frames 
  38  artisan:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

[app] $ exit

SQLSTATE[HY000] [2002] Connection refused このエラーはよく見るMySQLのエラーです。
MySQLに接続拒否されたエラーなので、この場合は大体MySQLへの接続設定に誤りがあります。

backend/.env のDB接続設定を修正する。

[mac] $ vim backend/.env
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel_local
DB_USERNAME=phper
DB_PASSWORD=secret

backend/.env.example も同様にDB接続設定を修正しておきます。

[mac] $ vim backend/.env.example
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel_local
DB_USERNAME=phper
DB_PASSWORD=secret

マイグレーション実行(再実行)

[mac] $ docker-compose exec app bash
[app] $ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.07 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.04 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0.02 seconds)

試しにデータを作ってみる

[mac] $ docker-compose exec app bash
[app] php artisan tinker
$user = new App\Models\User();
$user->name = 'phper';
$user->email = 'phper@example.com';
$user->password = Hash::make('secret');
$user->save();
[mac] $ docker-compose exec db bash
[db] $ mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE
[mysql] > SELECT * FROM users;
+----+--------------------+-----------------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
| id | name               | email                       | email_verified_at   | password                                                     | remember_token | created_at          | updated_at          |
+----+--------------------+-----------------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
|  1 | phper              | phper@example.com           | NULL                | $2y$10$/cmcAIdF7twOntcazkn4/e61YAM2F99hCrOo.qRUz7cUa/ZydrbVS | NULL           | 2020-08-24 17:09:53 | 2020-08-24 17:09:53 |
+----+--------------------+-----------------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
1 rows in set (0.00 sec)

結果にコミット

[mac] $ git status
[mac] $ git add .
[mac] $ git status
[mac] $ git commit -m "feat create docker db container"
[mac] $ git log

GitHubにpush

[mac] $ git push

リモートリポジトリへpushされていることを確認します。

Docker環境の再構築

Docker環境の破棄

コンテナの停止、ネットワーク・名前付きボリューム・コンテナイメージ、未定義コンテナを削除

[mac] $ docker-compose down --rmi all --volumes --remove-orphans

作業ディレクトリの削除

プロジェクトを削除するので、GUIエディタは閉じておきましょう。

[mac] $ cd ..
[mac] $ rm -rf docker-laravel-handson

Visual Studio Code等のGUIエディタで開いている場合は、一度エディタを終了しましょう。

環境の再構築

GitHubからリポジトリをクローン

** 自身のリポジトリ先に適宜変更してください **

[mac] $ git clone git@github.com:ucan-lab/docker-laravel-handson.git
[mac] $ cd docker-laravel-handson
[mac] $ docker-compose up -d --build

http://127.0.0.1:10080

/work/public/../vendor/autoload.php を開くのに失敗してエラーになっていることを確認します。
git cloneが終わった状態では app コンテナ内に /work/vendor ディレクトリが存在しないためです。

Laravelインストール

app コンテナに入ります。

[mac] $ docker-compose exec app bash

vendor ディレクトリへライブラリ群をインストールします。
composer.lock ファイルを参照します。

[app] $ composer install

composer install 時は .env 環境変数ファイルは作成されないので、 .env.example を元にコピーして作成します。

[app] $ cp .env.example .env

スクリーンショット 2019-09-29 2.10.51.png

.envAPP_KEY= の値がないとこのエラーが発生します。
このコマンドでアプリケーションキーを生成できます。

[app] $ php artisan key:generate

Welcome画面が表示されることを確認します。

[app] $ php artisan migrate

最後にマイグレーションを実行して成功すればハンズオン終了です。
お疲れ様でした?? コンテナを停止して終了してください。

[app] $ exit
[mac] $ docker-compose down

オマケ

MySQLに接続したい

[mac] $ docker-compose exec db bash -c 'mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} ${MYSQL_DATABASE}'

mysql> show tables;
+---------------------+
| Tables_in_homestead |
+---------------------+
| migrations          |
| password_resets     |
| users               |
+---------------------+
3 rows in set (0.00 sec)

mysql> desc users;
+-------------------+---------------------+------+-----+---------+----------------+
| Field             | Type                | Null | Key | Default | Extra          |
+-------------------+---------------------+------+-----+---------+----------------+
| id                | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)        | NO   |     | NULL    |                |
| email             | varchar(255)        | NO   | UNI | NULL    |                |
| email_verified_at | timestamp           | YES  |     | NULL    |                |
| password          | varchar(255)        | NO   |     | NULL    |                |
| remember_token    | varchar(100)        | YES  |     | NULL    |                |
| created_at        | timestamp           | YES  |     | NULL    |                |
| updated_at        | timestamp           | YES  |     | NULL    |                |
+-------------------+---------------------+------+-----+---------+----------------+
8 rows in set (0.01 sec)

マイグレーションが実行されるとLaravel側で最初から用意されている users, password_resets のテーブルが生成されています。

Laravelのログをコンテナログに表示する

backend/.env を修正する。

LOG_CHANNEL=stderr

backend/routes/web.php

Route::get('/', function () {
    logger('welcome route.');
    return view('welcome');
});

http://127.0.0.1:10080

$ docker-compose logs
# -f でログウォッチ
$ docker-compose logs -f
# サービス名を指定してログを表示
$ docker-compose logs -f app

MySQLクライアントツールで接続したい

docker-compose.ymldb サービスに下記設定を追記して、コンテナを再起動して設定を反映してください。

    ports:
      - 33060:3306

dbコンテナへのポート転送設定がないとホストからアクセスが行えません。
MySQLクライアントツールは「Sequel Ace」がオススメです。

Windowsエラーメモ

Error response from daemon

$ docker-compose up -d --build
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).

解決策をいくつか

マイグレーションエラーの補足

Host '172.27.0.2' is not allowed to connect to this MySQL server

$ php artisan migrate
   Illuminate\Database\QueryException  : SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations and table_type = 'BASE TABLE')

  at /work/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server")
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=db;port=3306;dbname=homestead", "homestead", "secret", [])
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

このエラーが発生した場合は my.cnf を作成する前に docker-compose up -d でビルドしてしまった可能性が高いです。

$ docker-compose down --volumes --rmi all
$ docker-compose up -d --build

設定ファイルがない状態でMySQLの初期化が行われたでデータが永続化されてしまってるので一度ボリューム毎削除してビルドし直せばokです。

docker volumes の共有で Permission denied (SELinux) 問題

CentOS 等の場合 volumes でホストと共有したファイル(ディレクトリ)にアクセスできないことがあります。

書き込み権限を与える

$ chmod -R 777 logs
$ chmod -R 777 src/storage/logs

ログディレクトリはホスト側からもコンテナ側からも書き込みするので、書き込み権限を付与しておきましょう。

応用

より実用的な構成のdocker構成の記事を書いてますので、よかったらこちらの記事も読んでもらえると嬉しいです。

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

PHPで、あいうえお配列の取得

入力

<?php
$arr = str_split("あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん");
?>

結果

Array
(
[0] => あ
[1] => い
[2] => う
[3] => え
[4] => お
[5] => ...
...
)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPで、あいうえお配列の取得 (簡単)

入力
(str_splitだとなぜか文字化けするので、このようにしないといけません。)

<?php
$arr = preg_split("//u", "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわ");
var_dump($arr); // 表示のためのコード
?>

結果

array(46) { [0]=> string(0) "" [1]=> string(3) "あ" [2]=> string(3) "い" [3]=> string(3) "う" [4]=> string(3) "え" [5]=> string(3) "お" [6]=> string(3) "か" [7]=> string(3) "き" [8]=> string(3) "く" [9]=> string(3) "け" [10]=> string(3) "こ" [11]=> string(3) "さ" [12]=> string(3) "し" [13]=> string(3) "す" [14]=> string(3) "せ" [15]=> string(3) "そ" [16]=> string(3) "た" [17]=> string(3) "ち" [18]=> string(3) "つ" [19]=> string(3) "て" [20]=> string(3) "と" [21]=> string(3) "な" [22]=> string(3) "に" [23]=> string(3) "ぬ" [24]=> string(3) "ね" [25]=> string(3) "の" [26]=> string(3) "は" [27]=> string(3) "ひ" [28]=> string(3) "ふ" [29]=> string(3) "へ" [30]=> string(3) "ほ" [31]=> string(3) "ま" [32]=> string(3) "み" [33]=> string(3) "む" [34]=> string(3) "め" [35]=> string(3) "も" [36]=> string(3) "や" [37]=> string(3) "ゆ" [38]=> string(3) "よ" [39]=> string(3) "ら" [40]=> string(3) "り" [41]=> string(3) "る" [42]=> string(3) "れ" [43]=> string(3) "ろ" [44]=> string(3) "わ" [45]=> string(0) "" }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバーサイドとクライアントサイド

この記事について

phpを勉強していた中で調べたサーバー、クライアントについて書きましたので私と同じような初心者の方の役に立てたら幸いです。

サーバーサイドとクライアントサイド

それぞれの違いはプログラムの実行場所がサーバーか端末かの違いです。プログラムの実行場所とはいわゆる、コードがどこで処理されるかということです。アクセスしたサーバー側でプログラムを実行した場合サーバーサイドのプログラムと言い、アクセスした端末でプログラムの処理を実行した場合クライアントサイドのプログラムと言います。
では、各々の特徴を見ていきましょう。

サーバーサイド

  • まず1つ目は、情報の保守性が上がることです。サーバー側で動作しているのでクライアント側からでは基本的にソースコードを見ることができません。なので動作内容がわからないというのは、扱う情報の安全性が守られることにつながります。
  • 2つ目は、サーバー側で処理を行うのでデータベースに保存されているユーザー情報の取得や、新たに追加することが可能になるということです。これらができることにより、ユーザーのアカウントの新規登録や、ユーザーごとに表示する情報を変化させることができます。

これらの特徴があります。サーバーサイドで使われる主な言語としてPHPが挙げられます。

クライアントサイド

  • 1つ目はサーバーにある情報を要求し受け取っていることです。普段使用しているPCやスマートフォンがそれにあたります。googleなどのブラウザを通じ、サーバーにアクセスを行いプログラムの結果を反映させていたりします。
  • 2つ目はWEBの見た目を調整する際に使用されるということです。ページを表示した際に出てくる、デザインされた色やタイトルまたは、アニメーションなどは全てクライアントサイドで処理されたものです。

これらが特徴で挙げられます。そして、クライアントサイドの主な言語としてJava Scriptがあります。

まとめ

サーバーサイド、クライアントサイドについて見てきましたがそれぞれに特徴があり、メリットデメリットがあると思います。作成したいものにあったものが使えるように2つの判別はしっかりできるようになっておくとできることも広がると思います。

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

PHP基礎

Udemyで一通り学んだ内容のメモとして今回記載します。

画面に文を表示

<?php
print('PHPです。');
?>
<?php
echo('PHPです。');
?>

echoとprintとの違い

・echoはカンマ区切りで複数の文字列を連結できるが、戻り値を返さない。
・printはカンマ区切りで複数の文字列を連結できずに1つの引数をとるが、戻り値を返せる。

算術計算

<?php
print(1+1);
?>

画面に現在の時刻を表示

現在の時刻を表示

<?php
print(date('G時 i分 s秒'));
?>

date — 現在の日付/時刻を書式化します。

曜日を表示

<?php
print(date('l'));
?>

日本時間にする

<?php
date_default_timezone_set('Asia/Tokyo');
print(date('G時 i分 s秒'));
?>

※以下を参考

https://www.php.net/manual/ja/function.date.php

文字列を連結

<?php
//.でつなげる
print('現在は'.date('G時 i分 s秒').'です');
?>

オブジェクトを使って時刻を表示

<?php
//DateTimeという日付や時間に関する様々な動作を持つオブジェクトを使用する
$today=new DateTime();
print($today->format('G時i分s秒'));
?>

変数を使って計算結果を表示する

<?php $sum=100+1050+200;?>
<?php $tax=1.08;?>
合計金額は : <?php print($sum); ?>円です。
税込価格は : <?php print($sum*$tax); ?>円です。

while構文

1から365までを表示する

<?php
//初期化処理
$i=1;
//while(繰り返す条件)
while($i<=365){
//繰り返したい処理
   print($i."\n");
//更新処理
   $i=$i+1;
}
?>

for構文

<?php
//for(初期化処理;繰り返す条件;更新処理)
for($i=1;$i<=365;$i++){
//繰り返したい処理
   print($i."\n");
}
?>

1年後までのカレンダーを表示する

まずは実行した日の本日の日付を取得する

<?php
time()は本日の日付を取得する、それをn/j(D)形式に変更。
print(date('n/j(D)',time() +60+60*24));
?>

strtotime

文字列で指定したものをtimestampに変換する

<?php
//以下なら今日から二日後のtimestampを取得
strtotime('+2day')
//出力
print(date('n/j(D)',strtotime('+2day')));
print "\n":
//以下なら1543/1/31を取得
strtotime('1543/1/31')
//出力
print(date('n/j(D)',strtotime('1543/1/31')));
?>

365日後までの日付を取得する

<?php
for($i=1;$i<=365;$i++){
//文字列で指定したものをtimestamp(日付)に変換する。
  $date=strtotime('+'.$i.'day');
  print(date('n/j(D)',$date));
  print"\n";
}
//endforは何に対しての閉じなのか分かりやすくなる。
endfor:
?>

whileの場合

<?php
while(....):

endwhile;
?>

配列について

date('w')で月火水木金土日の数字を取得できる。

<?php
//ブラケット[]の中に入れた値は,(カンマ)で区切る
$week_name=['日','月','火','水','木','金','土'];
//配列のままでは画面に表示できないので添え字の形式で表示する
print($week_name[date('w')]);
?>

連想配列とforeach構文

<?php
//連想配列は要素を指定する前にキーを指定する
$fruits=['apple'=>'りんご',
         'grape'=>'ぶどう',
         'lemon'=>'レモン'
];
//値のみを出力
foreach($fruits as $val){
  print($val.':'"\n");
}
?>
<?php
//連想配列は要素を指定する前にキーを指定する
$fruits=['apple'=>'りんご',
         'grape'=>'ぶどう',
         'lemon'=>'レモン'
];
//$englishはインデックスが入り、$japaneseは値の要素が入る。
foreach($fruits as $english=>$japanese){
  print($english.':'.$japanese."\n");
}
?>

if構文

<?php
//Gは時間を取得し、現在の時間が表示される。
if(date('G')<9){
  printf('※ 現在受付時間外です。');
}else{
  print('ようこそ');
}

$x=1;
if($x>0){
  print('xは0ではありません');
}
?>

小数を切り上げ、切り下げる方式

<?php
//floorは小数が発生しても全て切り捨てる
print (floor(100/3000*100));
?>%引きです。
<?php
//ceilは切り上げ
print (ceil(100/3000*100)."\n");
?>
<?php
//round(四捨五入)
print (round(100/3000*100,1));
?>

sprintf

桁数、書式を合わせたいときに使用する

<?php
//0002年1月23日と表示される。
$date=sprintf('%04s年 %02d月 %02d日' ,'2',1,23);
print ($date);
?>

ファイルに内容を書き込む場合

<?php
//file_put_contentsはファイルに書き込むというファンクション。書き込みたいファイルのパスを指定する。ファイルには内容が書き込まれる。
$success=file_put_contents('../../newsdate/news.txt.','2018-06-01 ホームページをリニューアルしました。');
if($success){
  print('ファイルへの書き込みに成功しました');
}else{
  print('書き込みに失敗しました。フォルダの権限を追加してください');
}
?>

ファイルを読み込む

<?php
//読み込みたいファイルを指定する。
$news=file_get_contents('../../newsdate/news.txt.');
print($news);
?>

変数を指定しないで読み込む場合

<?php
//読み込みたいファイルを指定して表示できる。しかし、読み込んだファイルを再加工したりできない欠点がある。readfile('../../newsdate/news.txt.');
?>

ファイルに内容を追記する場合

<?php
$news=file_get_contents('../../newsdate/news.txt.');
//$newsに内容を追加。
$news.="2018-06-03 ニュースを追加しました。\n";
file_put_contents('../../newsdate/news.txt.', $news);
print($news);
?>

XMLの情報を読み込む

XMLとは、拡張できるマークアップ言語のこと。
rssと呼ばれるものが一般的。
webページが更新されるとrssが更新されます。

<?php
//XML形式のファイルをロードする。
$xmlTree=simplexml_load_file('https://h2o-space.com/feed/');
foreach($xmlTree->channel->item as $item):
?>
<a href="<?php print($item->url);?>">
<?php print($item->title);?></a>
<?php
endforeach;
?>

JSONを読み込む

JSONとはJavaScript Object Notationの略。JavaScriptで扱えるオブジェクトとして使える形式。
以下のような記載です。

//短くかける。
{
  "id":"https://h2o-space.com/2010/2/2",
  "url":"https//google.com",
  "title":"aaaa";
}

各データの内容が分かりやすくなります。

<?php
//jsonのデータを取得する
$file=file_get_contents('https://h2o-space.com/feed/json/');
//jsonを処理した結果をいれる。
$json=json_decode($file);
var_dump($json);
foreach($json->items as $item):
    ?>
    <a href="<?php print($item->url); ?>"><?php print($item->title);?></a>
    <?php
endforeach;
?>

json_decode

jsonの内容をPHPで処理する。

フォームに入力した内容を取得する

ファイルを2つ用意します。

index.html
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<label for="my-name">お名前:</label>
<!-- submit.phpのリンクを貼る -->
<form action="submit.php" method="get">
<input type="text" id="my-name" name="my-name" maxlength="255" value="">
<input type="submit" value="送信する">
</form>
</main>

</body>    
submit.php
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
お名前: 
<?php
print(htmlspecialchars
($_REQUEST['my-name'],ENT_QUOTES));?>
</pre>
</main>
</body>   
//index.htmlで記載した以下のnameの部分は$_REQUEST['my-name']の部分に当たる。
<input type="text" id="my-name" name="my-name" maxlength="255" value="">

//submit.php
print(htmlspecialchars
($_REQUEST['my-name'],ENT_QUOTES));?>

・$REQUEST(リクエスト変数)は、$_GET、$_POST、$_COOKIE の内容をまとめた配列(配列変数)
・htmlspecialcharsというファンクションを使い、htmlのタグなどで認識されるタグなどのセキュリティ対策を行う。情報を盗み取ったりすることを向こうにできる。また、パラメータは2つ必要である。
1つ目のパラメータは何を対象にするのか、2つ目はどう対処するのか。
・ENT
QUOTESは2つ目のパラメータに基本的に使う。文字列ではなく数字なので``は必要ない。

GETとPOSTの違い

GETはURLのところに?マークがつく。$_POSTの場合、URLの場合、裏側で送信できるのでURLに値はつかない。

チェックボックス、ラジオボタン、リストボックスの値を取得

index.html
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<form action="submit.php" method="post">
  <p>性別:
    <input type="radio" name="gender" value="male">男性
    /
    <input type="radio" name="gender" value="female">女性
  </p>

  <input type="text" name="my_name" value="初期値">
  <input type="checkbox" name="agree" value="on">同意する
  <select name="pref">
    <option value="hokkaido">北海道</option>
    <option value="aomori">青森県</option>
  </select>
  <input type="submit" value="送信する">
</form>
</main>
</body>    
submit.php
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
  <?php print(htmlspecialchars($_POST['gender'],
  ENT_QUOTES)); ?>
</pre>
</main>
</body>

複数選択可能なチェックボックスの場合

index.html
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<form action="submit.php" method="post">
  <p>ご予約希望日</p>
  <p>
    <!--PHPで複数選択可能なチェックボックスを受け取るためにはname属性(reserve)にブラケット[]を記載する--> 
    <input type="checkbox" name="reserve[]" value="1/1">1月1日<br>
    <input type="checkbox" name="reserve[]" value="1/2">1月2日<br>
    <input type="checkbox" name="reserve[]" value="1/3">1月3日<br>
  </p>
  <input type="submit" value="送信する">
</form>
</main>

</body> 
submit.php
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
  <?php 
  foreach($_POST['reserve'] as $reserve){
  print(htmlspecialchars($reserve,ENT_QUOTES).' ');
  } ?>
</pre>
</main>
</body>    

半角数字に直して数字であるかどうかのチェックをする

全角数字で誤って入力してしまう場合があるのでプログラム上で自動で半角に直せるようにする。

<?php
$age='20';
//mb_convert_kanaで半角数字に直してくれる
$age=mb_convert_kana($age,'n','UTF-8');
if(is_numeric($age)){
  print($age.'歳');
}else{
  print('※ 年齢が数字ではありません');
}
?>

is_numeric

パラメーターが数字であるかどうかをチェックしてくれる。

mb_convert_kana

仮名を("全角かな"、"半角かな"等に)変換する。
mb_convert_kana($age,'n','UTF-8');で半角の数字に直すことができる。

郵便番号を正規表現を使って表現する

<?php
  $zip='987-6543';
//小文字のaは英数字を半角のaに直すもの。-が入る場合があるから。
  $zip=mb_convert_kana($zip,'a','UTF-8');

  if (preg_match ("/\A\d{3}[-]\d{4}\z/" , $zip)) {
    print('郵便番号:〒' . $zip);
  }else{
    print('※ 郵便番号を 123-4567の形式でご記入ください');
  }
?>

preg_match

正規表現によるマッチングを行う

\d{3}は数字が3回続くという意味。[-]\d{4}で-で区切って、数字を4つ並べる。\Aは文章の先頭、\zは文章の最後。つまり、数字が3つ始まって、4つで終わること。
preg_match ("/\A\d{3}[-]\d{4}\z/" , $zip)

別のページにジャンプする

<?php
//googleのページにジャンプする。headerというファンクションはwebブラウザに対してheaderというものを送信する。location:は別のページに遷移するという意味。
header('location:http/google.com');
//強制終了
exit();

剰余算

計算結果の余りを求める。

例:テーブルの色を変える場合

<table>
  <?php
  for($i=1;$i<=10;$i++){
    if($i%2){
      print('<tr style="background-color:#ccc">');
    }else{
      print('<tr>');
    }
    print('<td>' . $i . '行目</td>');
    print('</tr>');
  }
  ?>
</table>

Cookieに値を保存する

index.php
<?php
$value='変数に保存した値です。';
setcookie('save_message','Cookieに保存した値です',time()+60*60*24*14)
?>
<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../css/style.css">

<title>PHP</title>
</head>
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
<a href="page02.php">Page02へ</a>
</pre>
</main>
</body>    
</html>
page02.php
<?php cookie_start();?>
<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../css/style.css">

<title>PHP</title>
</head>
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
<!-- 画面に文書を表示する -->
変数の値:<?php print($value);?>
Cookieの値<?php print($_COOKIE['save_message']);?>
</pre>
</main>
</body>    
</html>

setcookie

最初のパラメータにcookieのキーを指定、次に保存したい内容、3番目のパラメータはいつまで保持していくのか指定する。

//14日間保持できる。
setcookie('save_message','Cookieに保存した値です',time()+60*60*24*14)
//変数は表示した画面のみで使えるので以下はエラーとなる。
変数の値:<?php print($value);?>
//保存したCookieの値を表示
<?php print($_COOKIE['save_message'])

Cookieを使う際はセキュリティに注意する必要がある。

セッションに値を保存する

index.php
<?php
session_start();
$_SESSION['session_message']='値をセッションに保存しました';
?>

<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../css/style.css">

<title>PHP</title>
</head>
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
<!-- ここにプログラムを記述します -->
<p>セッションに値を保存しました。次のページに移動してみましょう</p>
&requo; <a href="page02.php">Page02へ</a>

</pre>
</main>
</body>    
</html>
page02.php
<?php
session_start();
$_SESSION['session_message']='値をセッションに保存しました';
?>

<!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../css/style.css">

<title>PHP</title>
</head>
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
<!-- ここにプログラムを記述します -->
<p>セッションに値を保存しました。次のページに移動してみましょう</p>
&requo; <a href="page02.php">Page02へ</a>

</pre>
</main>
</body>    
</html><!doctype html>
<html lang="ja">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../css/style.css">

<title>PHP</title>
</head>
<body>
<header>
<h1 class="font-weight-normal">PHP</h1>    
</header>

<main>
<h2>Practice</h2>
<pre>
<!-- 画面に文書を表示する -->
<?php print($_SESSION['session_message']);?>
</pre>
</main>
</body>    
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueソーシャルシェアリングのバージョン指定

vue-social-sharingのバージョン指定

ターミナル
npm i vue-social-sharing@2.4.6

@2.4.6の部分を好きなバージョンに指定するだけ!

ver.3だとSNSアイコンが表示されない事が多いのでこちらのバージョンを指定するといけました!

以上!

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

更新処理は一回で!PHP ユーザー情報編集

どうも!Twitterの詩人でお馴染みのざんをですっ!
「PHPの画像処理ってなんであんなにややこしいんだろなぁ。ぴえん。。」
って思っているそこのあなた!
安心してください。履いてますよ!!
じゃなくて、、、今回はそんな不安を解消すべく私ざんをが一言コメントと画像処理を綺麗に場合分けして一回の更新処理で済ませる方法を紹介していきます笑

info.php
<?php
session_start();
~
$id = $_GET['user_id'];
$db_user = "xxxxxx";
$db_pass = "xxxxxx";
$dsn = "xxxxxx";
try {
    $dbh = new PDO($dsn, $db_user, $db_pass);
} catch (PDOException $e) {
    echo $e->getmessage();
    exit;
}
$sql = "SELECT * FROM members WHERE id = :id";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(':id' => $id));
$value = $stmt->fetch();
$dbh = null;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>ユーザー情報</h1><p>画像:
<?php if ($value['image'] === ''): ?>
<p>未登録<p>
<?php else: ?>
<p><?php echo '<img src="./image/' . $value['image'] . '" width="190" height="145">'; ?></p>
<?php endif; ?>
<p>一言コメント:<?php echo $value['comment']; ?></p>

<!-- ログイン者であり、そのログインユーザーの情報のみ編集リンクが現れる -->
<?php if (isset($_SESSION['id']) && $_SESSION['id'] == $id): ?>

<!-- aタグのgetでよるIDと一言コメントと画像の名前を送信 -->>
<a href="info_edit.php?id=<?php echo $id; ?>&comment=<?php echo $value['comment']; ?>&image=<?php echo $value['image']; ?>">編集</a><br>

<?php endif; ?></body>
</html>

とユーザーのそれぞれの詳細表示画面がこれになります。
続いて編集画面

info_edit.php
<?php
session_start();
if (!isset($_SESSION['id']) || !isset($_SESSION['name'])) {
    header('Location: index.php');
    exit();
}
$id = $_GET['id'];
$comment = $_GET['comment'];
$image = $_GET['image'];
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>ユーザー情報編集画面</h1>
<form method="post" enctype="multipart/form-data" action="info_comp.php?id=<?php echo $id; ?>&image=<?php echo $image; ?>">
<p>画像</p>
<input type="file" name="image">
<p>一言コメント</p>
<textarea name="comment"><?php echo $comment; ?></textarea><br>
<input type="submit" value="変更"><br>
<a href="info.php?id=<?php echo $id; ?>">戻る</a>
</form>
</body>
</html>

formはpostなのにこうすることによってget送信も同時に出来るんです!
なんでかっていうのは次の処理の時にわざわざ画像データ検索したりしなきゃいけないからこうやって使いまわしているだけです!

続いては編集の裏側の処理です!

info_comp.php
<?php
session_start();
// URLの直接入力を弾く
if (!isset($_GET['id']) || !isset($_FILES['image']['name']) || !isset($_POST['comment'])) {
    header('Location: index.php');
    exit;
}
$id = $_GET['id'];
$comment = $_POST['comment'];
$user = "xxxxxx";
$pass = "xxxxxx";
$dsn = "xxxxxx";
try {
    $dbh = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    echo $e->getmessage();
    exit;
}
// 他のユーザーが変更できないようにする
if ($_SESSION['id'] == $id) {

    // 画像の形式チェック

    $type = $_FILES['image']['type'];
    if ($_FILES['image']['error'] || $type === 'image/jpeg' || $type === 'image/png' || $type === 'image/gif') {

        // 画像が変更無しの場合の処理
        if ($_FILES['image']['error']) {
            $image = $_GET['image'];
        } else {
            // ランダムな文字列(かぶる心配のないものならなんでもいい)
            $image = mt_rand(0, 9999999999);
            move_uploaded_file($_FILES['image']['tmp_name'], './image/' . $image);
            // データベースに画像がある場合
            if ($_GET['image'] !== '') {
                unlink('./image/' . $_GET['image']);
            }
        }

        //コメントがなかった場合はnullを保存
        if ($comment == '') {
            $comment = null;
        }
        $sql = "UPDATE members SET image = :image, comment = :comment WHERE id = :id";
        $stmt = $dbh->prepare($sql);
        $data = array(':image' => $image, ':comment' => $comment, ':id' => $id);
        $stmt->execute($data);
        $msg = '変更されました';
    } else {
        $msg = '画像のファイル形式が正しくありません。';
    }
} else {
    $msg = 'このプロフィールは編集できません。';
}
$dbh = null;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<p><?php echo $msg; ?></p>
<a href="info.php?id=<?php echo $id; ?>">戻る</a>
</body>
</html>

これで最低限の処理でupdateできたかなって感じです笑
「これが無駄だなぁ」とか「これが足りてねえなあ」とかあれば教えていただけると幸いです!!

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

AtCoder Typical DP ContestのA - コンテストを絵で理解してみたい(PHP)

AtCoderのこちらの問題に挑戦してみました。
https://atcoder.jp/contests/tdpc/tasks/tdpc_contest

入力例
3
2 3 5
PHP
<?php
    fscanf(STDIN, "%d", $d);
    $ps = explode(" ", trim(fgets(STDIN)));

    $ans = array([0 => TRUE]);

    foreach ($ps as $p) {
        foreach ($ans as $k => $v) {
            $ans[$k + $p] = TRUE;
        }
    }
    echo count($ans) . "\n";

foreachの中でforeachを回して、中のforeachの配列は処理中に中身が増えていきます。よりイメージしやすいように絵に描いてみました。

スクリーンショット 2020-12-06 14.53.05.png

こちらの記事は自分用のメモですが、適宜追記していきます。
コメントあれば、ぜひよろしくお願い申し上げます。

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

AtCoderのRobot armsの問題を絵に描いて理解してみる(PHP)

区間スケジューリングというジャンルの問題らしいです。公式の解説だけでは、悲しいかな、僕には理解できなかったので、絵に書いて理解してみました。

問題はこちらです。

入力例

入力例
4
2 4
4 3
9 3
100 5

こうやって絵に描いてみると「なんだそんなことか、確かにそりゃそうだよね」って感じになりました。
IMG_4331.jpg
1番目の一番右端と2番目の一番左端を比べていって、2番目の左端が1番目の右端よりも数字が小さければ、その2番目を取り除いていけばいいだけですね。

コード

コードにするとこんな感じ。

PHP
<?php
    fscanf(STDIN, "%d", $n);
    for ($i = 0; $i < $n; $i++) {
        fscanf(STDIN, "%d %d", $x, $l);
        $ss[] = max(0, $x - $l);
        $ts[] = $x + $l;
    }
    array_multisort($ts, SORT_ASC, $ss);
    $t = 0;
    $count = 0;

    for ($i = 0; $i < $n; $i++) {
        if ($ss[$i] >= $x) {
            $count++;
            $t = $ts[$i];
        }
    }
    echo $count . "\n";

時間に余裕ができたら随時更新していきたいです。
コメントあれば、ぜひよろしくお願い申し上げます!

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

AtCoderのRobot armsの問題を絵に描いて理解してみる

区間スケジューリングというジャンルの問題らしいです。公式の解説だけでは、悲しいかな、僕には理解できなかったので、絵に書いて理解してみました。

問題はこちらです。

入力例

4
2 4
4 3
9 3
100 5

こうやって絵に描いてみると「なんだそんなことか、確かにそりゃそうだよね」って感じになりました。
IMG_4331.jpg
1番目の一番右端と2番目の一番左端を比べていって、2番目の左端が1番目の右端よりも数字が小さければ、その2番目を取り除いていけばいいだけですね。

コード

コードにするとこんな感じ。

<?php
    fscanf(STDIN, "%d", $n);
    for ($i = 0; $i < $n; $i++) {
        fscanf(STDIN, "%d %d", $x, $l);
        $ss[] = max(0, $x - $l);
        $ts[] = $x + $l;
    }
    array_multisort($ts, SORT_ASC, $ss);
    $t = 0;
    $count = 0;

    for ($i = 0; $i < $n; $i++) {
        if ($ss[$i] >= $x) {
            $count++;
            $t = $ts[$i];
        }
    }
    echo $count . "\n";

時間に余裕ができたら随時更新していきたいです。
コメントあれば、ぜひよろしくお願い申し上げます!

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

PHPの基本関数!切り分けたファイルを呼び出して使い回す方法

はじめに

同じコードをそれぞのページにベタ書きしてませんか?

もし、その共通の部分の修正や変更があった場合、
ページ分だけ修正の作業が増え手間です。

その時に便利なPHPの関数があります。

共通の部分を別の「exammple.php」のようなファイルにして、
それを呼び出した方が完全に楽です。

修正があっても
「exammple.php」ファイルを編集するだけで全ページに反映されます。

PHP関数

下記の2通りの方法です。

<?php include("ファイルのパス"); ?>
<?php require("ファイルのパス"); ?>

「include」か「require」関数を使います。

ファイルのパスのところは
ご自身の環境に合わせて下さい。

このコードを書くファイルから見て
どこに共通のファイルを置いたかで確認すればわかります。

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

【サルが書く】Lazy Eager loadingでもキャストしたい

わきゃ!!

GIGアドベントカレンダー9日目になります!
GIGメンバーの投稿はこちら? から見てみてね!
https://qiita.com/organizations/gig-inc

$test = Test::where(['test' => 'test'])->get();

$test->load(['table_name' => function ($query) {
            $query->orderByRaw('CAST(table_name.column_name AS signed) ASC');
}]);

これでいけると思います。

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

PHPの無名関数を使ってみた

無名関数とは?

公式ドキュメントの引用です。

無名関数はクロージャとも呼ばれ、 関数名を指定せずに関数を作成できるようにするものです。 callable パラメータとして使う際に便利ですが、用途はそれにとどまりません。

https://www.php.net/manual/ja/functions.anonymous.php

callableパラメータ

callableパラメータとしての使い方を見てみます。

<?php

// 使用方法1

// クロージャ
$double = function($number) {
    return $number * 2;
};

$numbers = [1, 2, 3, 4];

// 各値を2倍にしていく
$new_numbers = array_map($double, $numbers);

// 結果
[2, 4, 6, 8]


// 使用方法2

$numbers = [1, 2, 3, 4];

$new_numbers = array_map(function($number) {
    return $number * 2;
}, $numbers);

// 結果
[2, 4, 6, 8]

変数の使用

次に、外部から変数を使用する場合の使い方を見てみます。

エラー例

<?php
$numbers = [1, 2, 3, 4];

// 倍率を指定する。
$magnification = 3;

$new_numbers = array_map(function($number) {
    return $number * $magnification;
}, $numbers);

// 結果
PHP Notice:  Undefined variable: magnification in......

そのまま、変数を使用しようとするとエラーとなります。
そこで、useを使用します。

<?php
$numbers = [1, 2, 3, 4];

// 倍率を指定する
$magnification = 3;

$new_numbers = array_map(function($number) use ($magnification) {
    return $number * $magnification;
}, $numbers);

// 結果
[3, 6, 9, 12]

もちろん参照渡しにすると、変数の値を変更できます。

<?php
$numbers = [1, 2, 3, 4];

// 倍率を指定する
$magnification = 3;

$new_numbers = array_map(function($number) use (&$magnification) {
    // loopごとに倍率に1を足す
    $magnification++;

    return $number * $magnification;
}, $numbers);

// 結果
[4, 10, 18, 28]

型指定

型を指定することができます。
指定していないからといってエラーにはならないですが、安全性、保守性の向上を考えると指定しておいたほうが良いです。
例としてLaravelのEloquentsを使用します。

<?php

$persons = App\Person::all();

array_filter($persons, function(Person $person) {
    // IDEによってはgenderに補完が効き、定義元へジャンプできるようになる。
    return $person->gender == '男子';
});

Laravel使用例

最後にLaravelのCollectionでよく使うので紹介します。

collect(['太郎', null, '次郎'])
    ->reject(function ($name) {
        // 名前がない要素を除外
        return empty($name);
    })
    ->map(function ($name) {
        // 末尾に「さん」をつける
        return $name . 'さん';
    })
    ->values();

values()を使うことでキーをリセットしてくれます。

公式ドキュメントのweb内検索でfunction()と検索するとどのような場面でfunction()を使用しているかわかるので参考になります。
https://readouble.com/laravel/5.8/ja/collections.html

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

LaravelのBladeテンプレートにCSSやJavascriptを書かない理由はこれかな?

はじめに

この記事は、私個人の意見を文章にしているだけの記事です。
そのため、コードサンプルや、技術の説明のような情報はありません。
もし、コードサンプルや技術説明をお探しであれば、別の方の記事を参考にしてください。

Bladeテンプレートでの問題点

以前、次のような記事を書きました。

LaravelのBladeテンプレートをVue.jsっぽく書いてみた

この手法だと、部品毎で分割しやすく、コードも見やすくなると思っていたんですよね。
しかし、実際に開発していて大きな問題が1つ発生しました。
それは、JavascriptのUnitテストができないことです。

解決方法の検討

次のように解決方法を検討しました。

1. Javascriptを外部のJSファイルに記述する

これは、Vue.jsやReact等の、フロントエンドのフレームワークを利用しない場合、当たり前の記述方法だと思います。
Laravelでは、Bladeテンプレートファイルの中でCSSやJavascriptを使用することは推奨されていません。
しかし、明確に理由まで記載してある情報ソースを見つけることができなかったので、自分は「盲目的にルールを守ること」はしませんでした。
その結果、問題点が発覚したので、反省が必要ですね。
とはいえ、Bladeテンプレート内に書くJavascriptのコードはほとんどがDOM操作です。
なので、外部ファイルにして単体テストをしたところで、どこまで信頼できるものかわかりませんね。
※DOM操作は他のUIに依存するものが発生しやすいので、結局は判定ロジックや、算出ロジックだけのテストになるのかと。。

2. フロントエンドフレームワークを利用する

これは、ある程度大きなプロダクトでは、当たり前の話です。
しかし、技術選定時は、「できるだけ多くの人が参画しやすいようにする」をテーマに、Laravelを選定しました。
というのも、私が参画させていただいている案件は、企業したてのスタートアップ企業様の案件です。
そのため、スペシャリストよりも、フルスタックな人が必要だったんですよね。

経営を考慮しての技術選定だったので、現段階でLaravelを選定したことについては仕方なかったと思っています。
しかし、Unitテストができないというのは、品質を担保する指標が1つなくなること。
なので、後々はフロントエンドのスペシャリストに参画していただき、Vue.jsやReactを採用してUnitテストができる状態も必要なのかなとも思います。
その時は、売り上げが拡大されてきた時ですかね。
※余談ですが、VueやReactを使えばSPAも簡単に実現できますし、付加価値は付けやすいでしょう。

3. Integrationテストの仕組み構築に尽力する

現段階では、これが一番現実的なのではないかと考えています。
ここまでは、Unitテストのことしか考えていませんでした。
しかし、Integrationテストでしっかりテストをする仕組みがあり、かつ、デバッグがしやすいのであれば問題ないかと思っています。
というのも、Integrationテストでバグが発生したらUnitテストからやり直しになることも多々あります。
また、網羅率100%のUnitテスト用コードを目指すと、テストをすることが目的になる可能性もあるんですよ。
そうなると、無駄なことに工数をかけてしまい、ビジネス的にはよろしくないですよね。
なので、今回はこの方向性が一番有効かと思います。

最後に

この記事では、設計による問題の解決手法を検討した内容を記載しました。
今回の場合だと、予算が潤沢にある企業様だとしても「技術選定からやり直し」や、「Unitテストのためだけの設計変更」はするべきではないと思います。
というのも、出戻りばかりしていたら、リリースまでたどり着けないからです。
スタートアップの企業様ですと収益化できない期間が長くなるのは、より苦しいですよね。
そのため、今回は、「出戻りはしない」かつ、「品質は落とさない」のどちらにも配慮して検討してみました。
もし、本当に設計変更やフレームワークの導入をするのであれば、スケールして、予算に余裕が出てきた時で良いと思います。

最後まで読んでいただき、ありがとうございます:relaxed:

私は、参画させていただいている企業様に心より感謝し、プロジェクトの成功に尽力致します。そして、自分のスキルアップに尽力することが、お世話になる企業の関係者様の利益になると信じています。

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

英語のテックジョークで使えそうなワード

良さげなワード

arrays(配列)-> a raise(昇給)
bugs(バグ) -> bugs(虫)
dark(IDEなどのダークモード) -> dark(暗闇)
table(DB table) -> table(机)
server(サーバ) -> server(レストラン店員)
cache(キャッシュ) -> cash(現金)
Java(プログラミング言語) -> java(コーヒー)

今日の名言

I hope my death makes more cents than my life.(私はこの人生よりも硬貨な死を望む)
※ make cents(小金を稼ぐ)とmake sense(意味をなす)をかけている

-アーサー(映画ジョーカーの主人公)

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

Symfony で composer install --no-dev が失敗する

業務でSymfonyを利用していますが、先日、運用環境のデプロイでエラーが発生したので、備忘録を兼ねて投稿します。

  • Symfony 3.0.1

発生したエラー

$ composer install --no-dev

Uncaught exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Error: Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Fatal error: Class 'Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle' not found in ...

原因

Composer scripts に以下のような記載があり、copmoser install 時に cache:clear が実行されますが、デフォルトでは --env dev で実行されます。

    ...
    "scripts": {
        "post-install-cmd": [
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
        ],
        "post-update-cmd": [
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
        ]
    },
    ...

このため、存在しないバンドルに対してアクセスしようとしてしまい、エラーとなってしまいます。

対応方法

今回はデプロイコマンドで composer install していたので、SYMFONY_ENV=prod で実行してあげるだけでした。

SYMFONY_ENV=prod copmoser install --no-dev

手動でキャッシュクリアしている場合は、cache:clear にも --env prod を指定してください。

./bin/console cache:clear --env prod

同じエラーで悩んでいる方がいましたら、ご参考いただければと思います。

参照

https://github.com/symfony/symfony/issues/7403

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