20200602のPHPに関する記事は11件です。

マイグレーション実行時にmysqlでユーザーを作成していなかった為エラー

マイグレーション実行時エラー

laravel7で新しくプロジェクトを作成したので
とりあえずマイグレーションの実行ししてみたら

エラー発生

  SQLSTATE[HY000] [1045] Access denied for user 'user_name'@'172.20.0.4' (using password: YES) (SQL: select * from information_schema.tables where table_schema =table_name and table_name = migrations and table_type = 'BASE TABLE')

原因

ただ単に.envに設定したユーザーとパスワードを登録していなかっただけ

解決方法

mysqlでユーザーを作成をする

create user 'user_name' identified by 'pass';

権限の確認
新規作成したユーザには
何も権限がないことを意味するUSAGEが付いている

mysql> show grants for user_name@'%';
+---------------------------------------+
| Grants for phper@%                    |
+---------------------------------------+
| GRANT USAGE ON *.* TO `user_name`@`%` |
+---------------------------------------+
1 row in set (0.00 sec)

全てのDBが対象となるグローバル権限【CREATE権限】の付与

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER ON *.* TO 'user_name'@'%' WITH GRANT OPTION;

実行後もう一度マイグレーションを実行したら成功

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

homebrewを使ってPHPのバージョンアップグレード

概要

homebrewを使ってPHPのバージョンアップグレードを行います。
php7.2系 → php7.3系

homebrewのインストール

Homebrewのインストールがまだの方は、以下の記事を参考に導入をおこなってください。

MacでLaravel開発環境構築 -Qiita

手順

現在のバージョンを確認します。

$ php -v
PHP 7.2.30 (cli) (built: Apr 23 2020 01:23:39) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.30, Copyright (c) 1999-2018, by Zend Technologies

現在バージョンはPHP 7.2.30であることが確認できました。

続いてbrew searchコマンドでインストール可能なPHPのバージョンを確認します。

$ brew search php@7
==> Formulae
php@7.2 ✔   php@7.3   php@7.4

現在インストールされているバージョンの横にチェックマークがついています。

brew installコマンドでphp@7.3をインストールしましょう。

brew install php@7.3

これでphp@7.3をインストールすることができました。

続いてPATHを通す作業をします。
さきほどのインストール実行文の中に、PATHをまだ通してなかったら以下のコマンドを実行してくださいと書いてあります。

If you need to have php@7.3 first in your PATH run:
  echo 'export PATH="/usr/local/opt/php@7.3/bin:$PATH"' >> ~/.bash_profile
  echo 'export PATH="/usr/local/opt/php@7.3/sbin:$PATH"' >> ~/.bash_profile

これをこのままコピー&ペーストして実行します。

$ echo 'export PATH="/usr/local/opt/php@7.3/bin:$PATH"' >> ~/.bash_profile
$ echo 'export PATH="/usr/local/opt/openldap/sbin:$PATH"' >> ~/.bash_profile

こちらも実行文の中に書かれているのですが、
PHP を自動起動するよう設定をしてから再起動すればphp@7.3になるとのことです。

To have launchd start php@7.3 now and restart at login:
  brew services start php@7.3
Or, if you don't want/need a background service you can just run:
  php-fpm

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

$ brew services start php

実行後、ターミナルアプリを開き直して $ php -v でバージョンを確認

$ php -v
PHP 7.3.17 (cli) (built: Apr 29 2020 17:27:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.17, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.17, Copyright (c) 1999-2018, by Zend Technologies

無事 php@7.2系 から php@7.3系 にバージョンアップすることができました。

以上でhomebrewを使ってPHPのバージョンアップグレードは終了です。
お疲れ様でした。

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

DockerでWebアプリ環境構築

はじめに

本記事は動画( https://youtu.be/vY2z-fOOh6s )で説明に使用している
スライドを記事化したものです。動画と合わせて御覧ください。
vY2z-fOOh6s

※記事化する際に補足説明を加筆しており、内容は動画のスライドと異なります。
※本記事の内容は2020年3月時点の内容となります。OSやミドルウェア、Dockerのバージョンが異なることで記載通りの内容で完了しない場合があります。


構築する環境の構成

  • O S  : Cent OS 7.7
  • Webサービス : Apache 2.4.6
  • DBサービス  :MariaDB 10.4.6
  • Webアプリ言語:PHP 7.3.x

OSの準備

前回の動画: https://www.youtube.com/watch?v=oT-GSZD8bE4
前回の動画の概要:Dokcerインストール、CentOSのインストール。
1. コンテナ起動

    dos > docker run --privileged -d -p 22:22 -p 80:80 -p 8080:8080 -p 443:443 --name [作成コンテナ名] [イメージ名] /sbin/init
    dos > docker exec -it [コンテナ名] /bin/bash
    コンテナ > yum update
  1. ユーザの追加
root > useradd [ユーザ名]
root > passwd [ユーザ名]
  1. SSHインストールと接続!
root > yum install openssh-server
root > systemctl enable sshd.service
root > systemctl start sshd.service

接続はTeraTermをインストールして接続。


Webサービスの準備

  1. Apacheをインストール
root > yum install httpd

※CentOS標準のapacheをインストール


DBサービスの準備

  1. MariaDBリポジトリの追加
root > curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup |  bash
  1. MariaDBのインストール
root > yum install MariaDB-server

※MariaDBパッケージにアップデートがある場合は
root >  yum update MariaDB-server
  1. DBサーバの設定
root > vi /etc/my.ini/
  1. サーバ自動起動の設定とサーバ起動
root > systemctl enable mariadb
root > systemctl start  mariadb
  1. ユーザの設定
root > mysql
mysql > GRANT ALL PRIVILEGES ON *.* TO 'ユーザー名'@'ホスト名' IDENTIFIED BY 'パスワード' WITH GRANT OPTION;
mysql > FLUSH PRIVILEGES;

Webアプリ言語のインストール

  1. リポジトリの追加
> yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
  1. バージョンの確認
> yum info --enablerepo=remi,remi-php73 php php-mbstring php-xml php-xmlrpc php-gd php-pdo php-pecl-mcrypt php-mysqlnd php-pecl-mysql
  1. PHPインストール
> yum -y install --enablerepo=remi,remi-php73 php php-mbstring php-xml php-xmlrpc php-gd php-pdo php-pecl-mcrypt php-mysqlnd php-pecl-mysql
  1. PHPのインストール確認
> php -v

Webサービスの設定

  1. ドキュメントルートの設定
root > mkdir /var/www/webapp
root > chown takemi:takemi webapp
root > vi /etc/group

takemi: x :1000: → takemi : x : 1000 : apache
※takemiはadduser作成した一般ユーザです。adduserしたタイミングでユーザと同名のグループも作成されます。

root > systemctl restart httpd
user > vi /var/www/webapp/index.html
index.html
<html>
    <head>
        <title>Takemi Index</title>
    </head>
    <body>
        <div>Test Page</div>
    </body>
</html>
root > vi /etc/httpd/conf/httpd.conf
httpd.conf
        DocumentRoot=/var/www/html      →  DocumentRoot=/var/www/webapp
        <Directory “/var/www/html”>     → <Directory “/var/www/webapp”>
        Options Indexes FollowSymLinks      → Options Indexes FollowSymLinks  ExecCGI
        AllowOverride None          → AllowOverride All
  1. 設定の確認
    ブラウザでhttp://<コンテナのIP>にアクセスする

  2. PHPの動作確認
    ブラウザでhttp://<コンテナのIP>にアクセスする

  3. PHPの動作確認

user > vi /var/www/webapp/info.php
info.php
<?php phpinfo();

ブラウザで http://<コンテナのIP>/info.php にアクセスする。
phpinfo()の内容が表示される。


以上です!
これでHTMLもPHPも動作し、Webアプリの環境が整いました!
おつかれさまでした~

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

LaravelプロジェクトからFirebaseAPIを利用する前準備

環境について
OS:AmazonLinux2

1.サービスアカウントキーを作成する

GoogleCloudPlatformの認証サービス画面より
「サービスアカウントキー」を作成する
※APIキーではない

サービスアカウント
「firebase-adminsdk」を選択

キータイプ
「JSON」
を選択して、作成する

2.環境変数を設定する

1.で取得したJSONファイルをLaravelプロジェクトのディレクトリに配置し
環境変数(GOOGLE_APPLICATION_CREDENTIALS)にファイルパスをセットする

export GOOGLE_APPLICATION_CREDENTIALS="Laravelプロジェクトのディレクトリ/*****.json"

3.gRPC For PHPのインストール

こちらのGoogleのガイドを参考にgrpcをインストール

https://cloud.google.com/php/grpc?hl=ja

# pecl install grpc

Build complete.
Don't forget to run 'make test'.

running: make INSTALL_ROOT="/var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0" install
Installing shared extensions:     /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr/lib64/php/modules/
running: find "/var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0" | xargs ls -dils
 9083594     0 drwxr-xr-x 3 root root       17 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0
11304125     0 drwxr-xr-x 3 root root       19 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr
19801702     0 drwxr-xr-x 3 root root       17 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr/lib64
27955984     0 drwxr-xr-x 3 root root       21 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr/lib64/php
 3604422     0 drwxr-xr-x 2 root root       21 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr/lib64/php/modules
 3604421 35144 -rwxr-xr-x 1 root root 35985152 12月 23 20:06 /var/tmp/pear-build-rootyXfFt6/install-grpc-1.26.0/usr/lib64/php/modules/grpc.so

Build process completed successfully
Installing '/usr/lib64/php/modules/grpc.so'
install ok: channel://pecl.php.net/grpc-1.26.0
configuration option "php_ini" is not set to php.ini location
You should add "extension=grpc.so" to php.ini

PHPのgrpc拡張機能を有効にする。

# vi /etc/php.d/20-grpc.ini
; Enable gRPC extension module
extension=grpc.so

# systemctl restart httpd

4.FirebaseAPIライブラリを追加

LaravelプロジェクトにFirebaseAPIライブラリを追加します。

#composer require "grpc/grpc:^v1.1.0"
#composer require "google/protobuf:^v3.3.0"
#composer require google/cloud-firestore

参考資料
https://qiita.com/nirastamo/items/391d4a002dfb2901b268

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

MacでLaravel開発環境構築

概要

  • Homebrewの導入
  • Composerの導入
    • 上記のパッケージ管理システムを利用してPHP&Laravelのインストール

環境

macOS Mojave 10.14.6

Homebrewのインストール

Homebrewとはパッケージのインストールやアンインストールを簡単に行うことのできるパッケージ管理システムのひとつです。

Homebrewページに移動し、インストールコマンドをターミナルで実行します。

スクリーンショット.png

$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

インストールできたか確認

$ brew -v
Homebrew 2.2.15
Homebrew/homebrew-core (git revision 5385; last commit 2020-05-05)

HomebrewでPHPをインストールする

以下のコマンドでインストールするPHPパッケージを確認する。

$ brew search php
==> Formulae
brew-php-switcher  php-code-sniffer   php@7.2 ✔          phplint            phpmyadmin         phpunit
php                php-cs-fixer       php@7.3 ✔          phpmd              phpstan
==> Casks
homebrew/cask/eclipse-php 

導入したいバージョンのPHPをインストールする。

$ brew install php@7.3

インストールできたか確認

$ php -v

Composerをインストールする

Composerとは、PHP向けのパッケージ管理システムです。

Homebrewでインストールする

$ brew install composer

インストールできたか確認

$ composer -V

※直接Composerをインストールする方法

Homebrewを利用せずこちらでComposerファイルを直接ダウンロードして配置する方法もあります。
スクリーンショット 2020-06-02 17.20.16.png

ここから、最新バージョンのリンクをクリックしてください。
composer.pharというファイルがダウンロードフォルダにダウンロードされます。
続いて以下のコマンドを実行します。

$ cd ~/Download/
$ sudo mv composer.phar /usr/local/bin/composer

これでいつでもcomposerが呼び出せるようになりました。

配置したcomposerのパーミッション(アクセス権)を変更します。

$ chmod a+x /usr/local/bin/composer

これでcomposerが実行できるようになりました。

Homebrewでインストールした時と同様に、
「composer -V」
で正常にインストールできているか確認しましょう。

Laravelのインストール

Composerを用いてLaravelをインストールします。

#Laravelインストール
$ composer global require "laravel/installer"

環境変数PATHの設定

$ echo "export PATH=~/.composer/vendor/bin:$PATH" >> ~/.bash_profile source ~/.bash_profile

laravelのバージョン確認

$ php artisan -V

以上で、Laravel開発環境の構築は終了です。
お疲れ様でした。

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

LaravelでどうしてもSESSIONが追加できないときにすること

こんなにもシンプルなSESSIONが追加できない

app/Http/Controllers/HogeController.php
  public function show()
  {
    if (!Session::has('token')) {
      Session::put('token', 'token!');
      var_dump('saved!');
    }
    var_dump(Session::get('token'));
    return view('welcome');
  }

初回のみ「saved!token!」と表示され、2回目以降は「token!」となってほしい。
しかし、何度試しても「saved!token!」が表示される。

不用意にダンプしたらダメなんだってさ

https://laracasts.com/discuss/channels/laravel/sessions-are-not-saving?page=1

sessions work fine, but only if your code exits normally and runs the terminable middleware. If you dd() in your code then the session is not saved

https://laravel.com/docs/5.5/middleware#terminable-middleware

you should also run php artisan route:list and check that you have web against each route, once only per route

テストコードのvar_dumpを削除したらセッションにちゃんと保存できた。

app/Http/Controllers/HogeController.php
  public function show()
  {
    if (!Session::has('token')) {
      Session::put('token', token!);
    }
    return view('welcome');
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Laravel上でスーパーグローバル「$_SERVER」を参照する。

メモとして残します。

$_SERVERでも普通に参照できますが、フレームワークを使っている以上、スーパーグローバル変数を直接参照するのはどうかということで、一応、お作法にのっとって、Laravel上でスマートに参照する方法を紹介。

下記の例ではLaravelのヘルパrequestを使用して、帰ってきたオブジェクトのserverプロパティを参照しています。

■確認方法

$_SERVER要素全て

request()->server;
//↓実行結果

// "MIBDIRS" => "C:/hoge/php/extras/mibs"
// "MYSQL_HOME" => "\xampp\mysql\bin"
// "OPENSSL_CONF" => "C:/hoge/apache/bin/openssl.cnf"
// "PHP_PEAR_SYSCONF_DIR" => "\xampp\php"
// "PHPRC" => "\xampp\php"
// "TMP" => "\xampp\tmp"
// "HTTP_HOST" => "hoge.www.com"
// "HTTP_CONNECTION" => "keep-alive"
// "HTTP_PRAGMA" => "no-cache"
// "HTTP_CACHE_CONTROL" => "no-cache"
// "HTTP_UPGRADE_INSECURE_REQUESTS" => "1"
// "HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
// "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
// "HTTP_REFERER" => "http://hoge.www.com/"
// "HTTP_ACCEPT_ENCODING" => "gzip, deflate"
// "HTTP_ACCEPT_LANGUAGE" => "ja,en-US;q=0.9,en;q=0.8"
// "HTTP_COOKIE" => "remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=eyJpdiI6IjBIU1JxWVFCeG96T1RWeDFBcUtrVVE9PSIsInZhbHVlIjoieU5pRnVudTJub1wvTzJpUndLZlQ4MzNlaVlqdEZqK24xUTlQNX ▶"
// "PATH" => "hoge"
// "SystemRoot" => "C:\WINDOWS"
// "COMSPEC" => "C:\WINDOWS\system32\cmd.exe"
// "PATHEXT" => ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"
// "WINDIR" => "C:\WINDOWS"
// "SERVER_SIGNATURE" => "<address>Apache/2.4.39 (Win64) OpenSSL/1.1.1c PHP/7.3.8 Server at hoge.www.com Port 80</address>"
// "SERVER_SOFTWARE" => "Apache/2.4.39 (Win64) OpenSSL/1.1.1c PHP/7.3.8"
// "SERVER_NAME" => "hoge.www.com"
// "SERVER_ADDR" => "127.0.0.1"
// "SERVER_PORT" => "80"
// "REMOTE_ADDR" => "127.0.0.1"
// "DOCUMENT_ROOT" => "C:/Develop/hogeproject/public"
// "REQUEST_SCHEME" => "http"
// "CONTEXT_PREFIX" => ""
// "CONTEXT_DOCUMENT_ROOT" => "C:/Develop/hogeproject/public"
// "SERVER_ADMIN" => "hoge@hoge.test.com"
// "SCRIPT_FILENAME" => "C:/Develop/hogeproject/public/index.php"
// "REMOTE_PORT" => "59332"
// "GATEWAY_INTERFACE" => "CGI/1.1"
// "SERVER_PROTOCOL" => "HTTP/1.1"
// "REQUEST_METHOD" => "GET"
// "QUERY_STRING" => ""
// "REQUEST_URI" => "/"
// "SCRIPT_NAME" => "/index.php"
// "PHP_SELF" => "/index.php"
// "REQUEST_TIME_FLOAT" => 1591082776.119
// "REQUEST_TIME" => 1591082776

要素を指定して参照

//request()->server->get('要素名');//SERVER_NAMEなど
request()->server->get('SERVER_NAME');
//↓実行結果
//localhost.hoge.com

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

Laravelの認証でwebとapiの両方を使う

LaravelでのAPIの認証は、こちをご参照ください。
LaravelでのJWT認証

Laravelの認証は、config/auth.phpにのguardにて設定しますが、defaultをapiに設定するとwebでの認証ができなくなってしまいます。

config/auth.php
    'defaults' => [
        'guard' => 'api', // ← ここです。
        'passwords' => 'users',
    ],

一つのLaravelプロジェクトで、webとapiの両方を実装したいときの対応になります。

Laravelではauth()や、Auth::login()などで使用する認証は、guardのデフォルトの方となるようです。
デフォルト以外のものを使うときは、Auth::guard('[guard名]')->login()とguardを指定して使用します。
そうすることで、webとapiの両立を実現します。

defaultの設定

guardのdefaultはwebにしておきます。
make:authのコントローラーもそのまま使えるので便利です。

config/auth.php
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

API側の認証のコントローラにてguardを指定する

実装例のAuthControllerは、LaravelでのJWT認証で使用したものです。
Quick startで紹介されているものから少しだけ修正しています。
それに対して、上記のようにauth()Auth::guard('api')に変更します。

app/Http/Controllers/Api/AuthController.php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
//        $this->middleware('auth:api', ['except' => ['login']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = Auth::guard('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth()->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        Auth::guard('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        try {
            return $this->respondWithToken(Auth::guard('api')->refresh());
        } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => Auth::guard('api')->factory()->getTTL() * 60
        ]);
    }
}

認証後のコントローラー

auth()->user()は、変更しなくてもユーザー情報の取得はできているようなので、この認証を使って作成するAPI側は特にguardの指定はしなくてもよさそうです。

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

CakePHPのCounterCacheで関連件数を自動カウント

CakePHP には、紐づく別テーブルの件数が変わるとカウンター用カラムを自動的に更新してくれる、「CounterCache」というビヘイビアがあります。

ドキュメントにある利用例です。

たとえば、記事のリストを表示するときに、記事のコメントの数を表示することができます。 または、ユーザーを表示するときに、彼女が持っている友人/フォロワーの数を表示することもできます。 CounterCache ビヘイビアーは、これらの状況を想定しています。
https://book.cakephp.org/4/ja/orm/behaviors/counter-cache.html

このビヘイビアを使ってみようと思います。

テーブルを作成

まず、テーブルを作成します。
以下のようなテーブル構成とし、記事テーブルがコメント数とタグ数のカラムを持つものとします。

  • 記事:コメント = 1:N (hasMany/belongsTo)
  • 記事:タグ = N:N (belongsToMany)
/* PostgreSQL */

--
-- 記事
--
CREATE TABLE articles (
  id SERIAL PRIMARY KEY,  -- ID
  title TEXT NOT NULL,    -- 記事タイトル
  body TEXT,              -- 記事本文
  name TEXT NOT NULL,     -- 記事作者氏名
  comment_count INTEGER,  -- コメント数
  tag_count INTEGER       -- タグ数
);

--
-- コメント
--
CREATE TABLE comments (
  id SERIAL PRIMARY KEY,  -- ID
  article_id INTEGER NOT NULL REFERENCES articles(id),  -- 記事ID
  body TEXT,              -- コメント本文
  name TEXT NOT NULL      -- コメント作者氏名
);

--
-- タグ
--
CREATE TABLE tags (
  id SERIAL PRIMARY KEY,   -- ID
  tag TEXT UNIQUE NOT NULL -- タグ
);

--
-- 記事 <-> タグ(中間テーブル)
--
CREATE TABLE articles_tags (
  article_id INTEGER REFERENCES articles(id),  -- 記事ID
  tag_id INTEGER REFERENCES tags(id),          -- タグID
  PRIMARY KEY (article_id, tag_id)
);

1. hasMany アソシエーションの場合

記事テーブルのコメント数カラム (articles.comment_count) を CounterCache で更新します。
なお、これはドキュメントの例と同じです。

1-1. モデルの設定

cake bake でコードを自動生成します。

$ bin/cake bake model articles
$ bin/cake bake model comments
$ bin/cake bake model tags
$ bin/cake bake model articles_tags

カウント対象の CommentsTable で、CounterCache ビヘイビアをロードします。

/* src/Model/Table/CommentsTable.php(変更あり) */

public function initialize(array $config)
{
    // ... 省略 ...

    $this->belongsTo('Articles', [
        'foreignKey' => 'article_id',
        'joinType' => 'INNER',
    ]);

    // *** 以下 3行を追加 ****
    $this->addBehavior('CounterCache', [
        'Articles' => ['comment_count']
    ]);
}

カウンターカラムを持つ ArticlesTablecake bake の自動生成コードのままです。

/* src/Model/Table/ArticlesTable.php(変更なし) */

public function initialize(array $config)
{
    // ... 省略 ...

    $this->hasMany('Comments', [
        'foreignKey' => 'article_id',
    ]);
}

1-2. 動作確認

save() だけ試すのに Controller と Template を用意するのも面倒なので、ユニットテストで試してみます。

テストファイルに、以下のようなコードを追加します。

/*  tests/TestCase/Model/Table/ArticlesTableTest.php */

public function testCommentCount()
{
    $this->Articles->Comments->deleteAll(['id IS NOT NULL']);
    $this->Articles->deleteAll(['id IS NOT NULL']);

    $data = [
        'title' => 'テスト記事タイトル',
        'body' => 'テスト記事本文',
        'name' => 'テスト記事作者',
        'comments' => [
            [
                'name' => 'テストコメント(1) 作者',
                'body' => 'テストコメント(1) 本文',
            ],
            [
                'name' => 'テストコメント(2) 作者',
                'body' => 'テストコメント(2) 本文',
            ],
        ],
    ];

    $entity = $this->Articles->newEntity($data);
    $result = $this->Articles->save($entity);

    $article = $this->Articles->get($result->id, [
        'contain' => ['Comments']
    ]);

    pr($article->toArray());
}

また、テスト用のデータは不要なので、tests/Fixture/ 以下のファイルの init()$this->records の中身を空にしておきます。

/* tests/Fixture/* */

public function init()
{
    $this->records = [];
    parent::init();
}

実行して save() 後の get() 結果を見てみると、「comment_count」が更新されていることがわかります。

$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest.php --filter testCommentCount

Array
(
    [id] => 1
    [title] => テスト記事タイトル
    [body] => テスト記事本文
    [name] => テスト記事作者
    [comment_count] => 2
    [tag_count] => 
    [comments] => Array
        (
            [0] => Array
                (
                    [id] => 1
                    [article_id] => 1
                    [body] => テストコメント(1) 本文
                    [name] => テストコメント(1) 作者
                )
            [1] => Array
                (
                    [id] => 2
                    [article_id] => 1
                    [body] => テストコメント(2) 本文
                    [name] => テストコメント(2) 作者
                )

        )
)
=> TABLE articles;
 id |       title        |      body      |      name      | comment_count | tag_count 
----+--------------------+----------------+----------------+---------------+-----------
  1 | テスト記事タイトル | テスト記事本文 | テスト記事作者 |             2 |          
(1 row)

=> TABLE comments;
 id | article_id |          body          |          name          
----+------------+------------------------+------------------------
  1 |          1 | テストコメント(1) 本文 | テストコメント(1) 作者
  2 |          1 | テストコメント(2) 本文 | テストコメント(2) 作者
(2 rows)

2. belongsToMany アソシエーションの場合

記事テーブルのタグ数カラム (articles.tag_count) を CounterCache で更新します。

ドキュメントに、belongsToMany アソシエーションで使うには、という説明があるのですが、例もなく、で、結局使えるのか?と不安になります。(StackOverflow でも解決したのか不明な質問が。。)

https://book.cakephp.org/4/ja/orm/behaviors/counter-cache.html#id2
CounterCache ビヘイビアーは、 belongsTo アソシエーションに対してのみ機能します。 たとえば、 "Comments belongsTo Articles" の場合、Article テーブルの comment_count を生成するために、 CommentsCache ビヘイビアーを CommentsTable に追加する必要があります。

これを belongsToMany アソシエーションに対して機能させることは可能ですが、 アソシエーションオプションで設定されたカスタム through テーブルで CounterCache ビヘイビアーを有効にして cascadeCallbacks 設定オプションを true にする必要があります。 カスタム JOIN テーブルを設定する方法は 'through' オプションの使用 を参照してください。

結論としては、以下のとおり利用できました。

2-1. モデルの設定

CounterCache ビヘイビアを、中間テーブル ArticlesTagsTable でロードします。
(ドキュメントに、中間テーブルで、と書いていないのがわかりづらい・・・)

/* src/Model/Table/ArticlesTagsTable.php(変更あり) */

public function initialize(array $config)
{
    // ... 省略 ...

    $this->addBehavior('CounterCache', [
        'Articles' => ['tag_count'],
    ]);
}

次に ArticlesTable で belongsToMany アソシエーションを指定する箇所で、中間テーブルを指定するのに「joinTable」の代わりに「through」を使い、「cascadeCallbacks」を TRUE にします。

belongsToMany アソシエーションの配列で可能なキー
https://book.cakephp.org/4/ja/orm/associations.html#belongstomany

/* src/Model/Table/ArticlesTable.php (変更あり)*/

public function initialize(array $config)
{
    // ... 省略 ...

    $this->belongsToMany('Tags', [
        'foreignKey' => 'article_id',
        'targetForeignKey' => 'tag_id',

        // *** 以下の 1 行を削除かコメントアウト ***
        // 'joinTable' => 'articles_tags',

        // *** 以下の 2 行を追加 ***
        'through' => 'articles_tags',
        'cascadeCallbacks' => TRUE,
    ]);
}

なお、TagsTable は自動生成コードのまま変更ありません。

/* src/Model/Table/TagsTable.php(変更なし) */

public function initialize(array $config)
{
    // ... 省略 ...

    $this->belongsToMany('Articles', [
        'foreignKey' => 'tag_id',
        'targetForeignKey' => 'article_id',
        'joinTable' => 'articles_tags',
    ]);
}

2-2. 動作確認

同じく、ユニットテストで試します。
テストファイルに、以下のようなコードを追加します。

/*  tests/TestCase/Model/Table/ArticlesTableTest.php */

public function testTagCount()
{
    $data = [
        'title' => 'テスト記事タイトル',
        'body' => 'テスト記事本文',
        'name' => 'テスト記事作者',
        'tags' => [
            [
                'tag' => 'タグ (1)',
            ],
            [
                'tag' => 'タグ (2)',
            ],
        ],
    ];

    $entity = $this->Articles->newEntity($data);
    $result = $this->Articles->save($entity);

    $article = $this->Articles->get($result->id, [
        'contain' => ['Tags']
    ]);

    pr($article->toArray());
}

実行して save() 後の get() 結果を見てみると、「tag_count」が更新されていることがわかります。

$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest.php --filter testTagCount

Array
(
    [id] => 1
    [title] => テスト記事タイトル
    [body] => テスト記事本文
    [name] => テスト記事作者
    [comment_count] =>
    [tag_count] => 2
    [tags] => Array
        (
            [0] => Array
                (
                    [id] => 1
                    [tag] => タグ (1)
                    [_joinData] => Array
                        (
                            [article_id] => 1
                            [tag_id] => 1
                        )
                )
            [1] => Array
                (
                    [id] => 2
                    [tag] => タグ (2)
                    [_joinData] => Array
                        (
                            [article_id] => 1
                            [tag_id] => 2
                        )

                )
        )
)
=> TABLE articles;
 id |       title        |      body      |      name      | comment_count | tag_count 
----+--------------------+----------------+----------------+---------------+-----------
  1 | テスト記事タイトル | テスト記事本文 | テスト記事作者 |               |         2
(1 row)

=> TABLE tags;
 id |   tag    
----+----------
  1 | タグ (1)
  2 | タグ (2)
(2 rows)

=> TABLE articles_tags;
 article_id | tag_id 
------------+--------
          1 |      1
          1 |      2
(2 rows)

補足

ドキュメントの最初に、以下のような注意書きがあります。

カウンターの値は、エンティティーが保存または削除されるたびに更新されます。 updateAll() または deleteAll() を使用するか、作成した SQL を実行すると、 カウンターは更新 されません。
https://book.cakephp.org/4/ja/orm/behaviors/counter-cache.html#id1

これは、CounterCache ビヘイビアは 内部的 には、save() 時に afterSave イベント として、カウンターカラムを UPDATE していることによります。(delete() 時は afterDelete イベント)。

updateAll()deleteAll() では afterSave/afterDelete イベントが発生しないので、カウンターは更新されないことになります。

https://book.cakephp.org/4/ja/orm/saving-data.html#Cake\ORM\Table::updateAll

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

Laravelのコレクションでユーザー(グループ)ごとに最新のメッセージを取得

うちでLaravelで質問回答アプリを実装しています。
対話者のメッセージボックスを作りたい、要件をまとめました:スクリーンショット 2020-06-02 6.44.49.png

ログイン中のユーザーはuser_meとし、他のユーザーごとに一番新しいメッセージを提示するメッセージリストを作りたいです。
そこでクリックしたら、そのユーザーとのメッセージモダルに飛ぶ。
ユーザー名は相手の名前、メッセージは対話中の一番新しいメッセージ(自分からメッセージのも含む)

SQLよりLaravelのコレクションはもっと使いやすいと思い、以下で案を。
まずuser_meと関わる全てのメッセージを時間順に並べて取得、

次に対話両方をグループとし、一番新しいメッセージだけ、リストにプッシュする。
リストに登録済のグループのメッセージはプッシュしません。

以下はMessageRepositoriesのメソッドの一部です。

namespace App\Repositories;


public function selectMessageByEachUser()
    {
        $me_id = user('api')->id;
        $messages = Message::with(['fromUser', 'toUser'])
            ->where('to_user_id', $me_id)
            ->orwhere('from_user_id', $me_id)
            ->orderBy('created_at', 'desc')
            ->get();

        $chat_room_groups = [];
        $new_messages = collect();
        foreach ($messages as $message) {
            $match_ids_1 = [$message->from_user_id, $message->to_user_id];
            $match_ids_2 = [$message->to_user_id, $message->from_user_id];
            if ($new_messages->isEmpty()) {
                $new_messages->push($message);
                array_push($chat_room_groups, $match_ids_1);
            } else {
                $res = !in_array($match_ids_1, $chat_room_groups, true)
                    && !in_array($match_ids_2, $chat_room_groups, true);
                if ($res) {
                    $new_messages->push($message);
                    array_push($chat_room_groups, $match_ids_1);
                }
            }
        }
        return $new_messages;
    }

次にメッセージコントローラは処理したリストをもらい、APIリソースをフロントエンドに発信

namespace App\Http\Controllers;

public function messageList()
    {
        $message=$this->message->selectMessageByEachUser();
        return new MessageCollection($message);
    }

どうせチャットルームグループは必要なら、グループに入るユーザーのIDと最新メッセージを保存するチャットルームのテーブルを作る方がいいかも?
メーセージを送信する際、新しいメッセージをテーブルに保存し、後処理はなしで、効率は上がります。

今は勉強でLaravelとVue.jsで質問回答アプリを実装しています。
ソースコードは以下に公開:
https://github.com/joychoinjapan/syukudai.net
興味があれば、ご覧ください。

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

docker上のphpコンテナでcomposer installしたら、メモリ不足で落ちた話

はじめに

docker上でlaravelの開発していて、必要なライブラリをcomposerでインストールしようとしたら下記のエラーが出て詰まった、、、:sweat_smile:

エラーメッセージ

Using version ^4.3 for laravel/socialite
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)

Fatal error: Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes) in phar:///usr/bin/composer.phar/src/Composer/DependencyResolver/Solver.php on line 223

原因調査

コンテナに入って、以下のコマンドを実行

$ php -r 'phpinfo();' | grep memory_limit

$ memory_limit => 128M => 128M

確かにデフォルトのphpのメモリが少ない、、、

ちなみに -r は <?php を略したもので、
コマンドライン上でphpのプログラムを実行するためのオプションです!

↓参照元 phpコマンドのオプション
https://www.php.net/manual/ja/features.commandline.options.php

対処方法

  • phpのコンテナのdockerファイルに以下を追加
php/Dockerfile
 COPY php.ini /usr/local/etc/php/ 
  • php.iniをdockerファイルのディレクトリに作成
php/php.ini
  memory_limit = -1

 -1は上限なしという意味

  • コンテナを再起動し中に入ってメモリを確認する
  $ php -r 'phpinfo();' | grep memory_limit           
  $ memory_limit => -1 => -1

 php.iniの設定に切り替わってる〜!
 ナイスですね〜〜〜:thumbsup:と全裸の監督から聞こえてきそうです!

結果

もう一度composer installしたら、、、

Package manifest generated successfully

無事インストールできました!マーベラス!

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