- 投稿日:2020-10-13T23:53:12+09:00
PHP 、Laravel 学習 ルーティングの設定
Laravelのダウンロード
これはあまり苦労せず終わったので、簡単に書いていきます。
とりあえず、LaravelをダウンロードするためにComposerと言うパッケージのダウンロードが必要になりますがそちらもあまり苦労ないと思うので順番さえ間違えなければすんなりいくと思います。「Composerってなんですか?」等、詳しく知りたい方はぐぐるとたくさん情報が出てきますのでググってみてください
ルーティングの設定
Laravelがインストールし終わったら早速最初の工程としてルーティングを設定していきます。
「リクエストが送られてきたら任意のレスポンスをする」という物です
自分だけ理解している記事は書きたくないので、噛み砕いて説明したいと思います。
普段ホームページを見ている時、何気なく操作しているところの一部です。
例えば、あるサイトを回遊している時「Aのページを見たいからAのページへ飛ぶためボタンをクリックして、Bのページへ飛ぶ」という操作をしたとします。何気なくやっているいつもの操作だと思いますが、この時の
●「Aのページへ飛びたい」というユーザーの要望をリクエストと言い●「Bのページを表示する」というサイト側の設定をレスポンスと言う
そしてこの「このリクエストが来たら、このレスポンスをすると言うのを決める設定」のことをルーティングと言います。
はじめにテストとして、「/hello」と言うリクエストが送られてきたら
「Hello World!」と言う言葉をブラウザに表示すると言うレスポンスを返してみます。ダウンロードしたLaravelファイルの中に「routes」と言うフォルダーがあるのでその中の「web.php」ファイルの一番下にRoute::get('/hello', 'HomeController@home');と言うコードを書いてみました。
このコードを詳しく解説すると「/helloと言うリクエストが来たらHomeControllerのhomeアクションが呼ばれ、然るべきレスポンスをする」
というコードです。
コントローラーとアクションも出てきましたが、説明が長くなるので次回以降に詳しく解説します。
しかし、ここで問題発生
ブラウザをのURLをhttp://127.0.0.1:8000/helloにして接続してみるとつながらず・・・「ホームコントローラーは存在しませんよ」と言うエラーが出てしまいました。
このエラーを解決するのにかなり時間がかかってしまったのですが、原因は①Laravelのバージョンアップでルーティングの表記の仕方が変わってしまった
②ルートの表記が間違えていた。の2点でした。
①の原因の解説としては、参考にした記事がLaravel7と言うバージョンだったのに対し、私がインストールしたのはLaravel8でした。
②の原因、ルートの表記が間違えていたと言うところですが「web.php」と言うファイル(ルーティングを実際に書いて設定するファイル)の中にどのような階層を踏んでルーティングするかを書くところがあるのですがこちらが間違えていました。
正しいソースコードを以下に載せておきます。①↓
Route::get('/hello', [HomeController::class, 'home']);②↓
use App\Http\Controllers\HomeController;次回以降の予定
①Controllerの詳しい説明と設定
②viewの詳しい説明と設定
③viewを簡単にスタイリングできるBootstrapの説明
こちらを念頭に置いて更新していきたいと思います。
- 投稿日:2020-10-13T23:29:27+09:00
PHP Laravel アプリ作成アウトプット
method_exists()関数
methood_exists(オブジェクト, メソッド)
第一引数で指定したクラスオブジェクトのクラス内に第二引数で指定したメソッドが含まれていればtrueを返し、そうでない場合falseを返します。property_exists
PHPマニュアル(https://www.php.net/manual/ja/function.property-exists.php)
機能 オブジェクトもしくはクラスにプロパティが存在するかどうかを調べる。
説明 property_exists ( mixed $class , string $property ) : bool
パラメータ class:確認するクラス、もしくはクラスのオブジェクトを指定します。 property:プロパティ名を指定します。env関数
env関数は、第一引数に指定した環境変数の値を返し、もしその環境変数が無ければ第二引数の値をデフォルト値として返します。
- 投稿日:2020-10-13T23:10:09+09:00
yps並走備忘録 Task5 Webアプリの作成
今回の主な課題
・MySQLのVIEWテーブルの理解
・Bootstrap UIの導入
・Laravel Mixの使い方事前準備
Node.jsとnpmのアップデート
sudo yum remove node npm -y
curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install nodejs -y
インストールで来たら下記コマンドでアップデートされていることを確認
$ node -v
例:v12.18.3
$ npm -v
例:6.14.6一度Laravelのディレクトリに戻り、Laravel Mix(開発用)でビルドします
cd /var/www/html/yps
rm -rf ./node_modules
npm install && npm run dev
LaravelのuiにBootstrapを指定
composer require laravel/ui
php artisan ui bootstrap
Bootstrapをビルド
npm install && npm run dev
今回使用するデータをMySQLへ
1. VS Code内でターミナルを起動(ctrl+shift+@)
2.pwd
⇒Laravelのプロジェクトフォルダ(/var/www/html/yps)にいることを確認
3.mkdir resources/sql
⇒SQLファイル格納用のディレクトリを作成
4.mysqldump -u root -p -d worldcup2014db > resources/sql/worldcup2014db.sql
⇒テーブルの定義ファイルを取得(データは入手出来ていません)
5.grep -i 'create table' resources/sql/worldcup2014db.sql
⇒テーブルを確認[^1]
6. 各テーブル(countries, goals, pairings, players)にフィールド(expired_at, deleted_at, updated_at, created_at)を追加5の結果
CREATE TABLE `countries` ( CREATE TABLE `goals` ( CREATE TABLE `goals_tmp` ( CREATE TABLE `pairings` ( CREATE TABLE `pairings_tmp` ( CREATE TABLE `players` ( CREATE TABLE `players_tmp` (6の参考例
CREATE TABLE `countries` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `ranking` int(11) DEFAULT NULL, `group_name` varchar(1) DEFAULT NULL, `expired_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;MySQLでデータベースを削除⇒作り直し
1.mysql -u root -p
2.drop database worldcup2014db;
3.create database worldcup2014db;
4.use worldcup2014db;
5.source resources/sql/worldcup2014db.sql;
⇒ 作ったSQLファイルを元にテーブルを作成
6.show tables;
⇒ テーブルが作成されているか確認
7. OKならexit;
でMySQL CLiから出るTask 3 同様にデータを取得~流し込みの準備
1.cd /tmp
2.sudo yum install wget unzip -y
3.wget http://tech.pjin.jp/wp-content/uploads/2016/04/worldcup2014.zip
4.unzip http://worldcup2014.zip
5.ls -la worldcup2014.sql
6. 各テーブル(countries, goals, goals_tmp, pairings, pairings_tmp, players, players_tmp)のCREATE TABLE ~ DEFAULT CHAESET=utf8;までを削除(↓の部分)CREATE TABLE `countries` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, `ranking` int(11) DEFAULT NULL, `group_name` varchar(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;**データの流し込み
7. mysql -u root -p -D worldcup2014db
8. source /tmp/worldcup2014.sql; //warningが出ますが特に問題はないそうです
9. 各テーブルのデータ件数をチェックして下記になっていればOKmysql> select count(*) from countries; +----------+ | count(*) | +----------+ | 32 | +----------+ mysql> select count(*) from goals; +----------+ | count(*) | +----------+ | 188 | +----------+ mysql> select count(*) from pairings; +----------+ | count(*) | +----------+ | 144 | +----------+ mysql> select count(*) from players; +----------+ | count(*) | +----------+ | 736 | +----------+問題なければ
exit;
でMySQL Cliから出る簡易アプリケーションの作成
使うデータは用意できたので、今度はデータを使ってアプリケーションを作成してみます。
モデルクラスの作成
Task3で作成したモデルクラスを削除
cd /var/www/html/yps
rm app/Models/Player.php
以下のコマンドを打ってデータベースと連動したモデルクラスを作成します
php artisan make:model Models/Country -m
php artisan make:model Models/Goal -m
php artisan make:model Models/Pairing -m
php artisan make:model Models/Player -m
上記で作成した各モデルクラスに明示的にテーブルを指定し、ついでに$dateも指定します。
※以下はPlayerモデルの例ですPlayer.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Player extends Model { protected $table = "players"; protected $dates = ["expired_at", "deleted_at", "updated_at", "created_at"]; }コントローラークラスの作成
php artisan make:controller CountryController --resource --model=Models/Country
php artisan make:controller GoalController --resource --model=Models/Goal
php artisan make:controller PairingController --resource --model=Models/Pairing
php artisan make:controller PlayerController --resource --model= Models/Player
php artisan make:controller WelcomeController --resource
Viewの作成
resources/views/welcome.blade.phpに以下をコピペ<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <title>yotaro prg</title> <!-- Fonts --> <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet"> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Jekyll v4.1.1"> <!-- Bootstrap core CSS --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> <!-- Favicons --> <link rel="apple-touch-icon" href="/yotaro.jpg" sizes="180x180"> <link rel="icon" href="/yotaro.jpg" sizes="32x32" type="image/jpg"> <link rel="icon" href="/yotaro.jpg" sizes="16x16" type="image/jpg"> <?php /* <link rel="manifest" href="/docs/4.5/assets/img/favicons/manifest.json"> <link rel="mask-icon" href="/docs/4.5/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c"> */ ?> <link rel="icon" href="/yotaro.jpg"> <meta name="theme-color" content="#563d7c"> <style> body { padding-top: 5rem; } .starter-template { padding: 3rem 1.5rem; text-align: center; } .bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } </style> </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> <a class="navbar-brand" href="#">Navbar</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExampleDefault"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">Home <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="#">Link</a> </li> <li class="nav-item"> <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a> <div class="dropdown-menu" aria-labelledby="dropdown01"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <a class="dropdown-item" href="#">Something else here</a> </div> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> <main role="main" class="container"> <h1>WorldCup 2014 選手一覧</h1> </br> <table class="table table-striped table-hover table-sm table-responsive-sm"> <thead> <tr> <th scope="col">#</th> <th scope="col">country</th> <th scope="col">uniform_num</th> <th scope="col">position</th> <th scope="col">name</th> <th scope="col">club</th> <th scope="col">birth</th> <th scope="col">height</th> <th scope="col">weight</th> <th scope="col">total_goals</th> </tr> </thead> <tbody> @foreach ($players as $player) <tr> <td>{{ $player->id }}</td> <td>{{ $player->c_name }}</td> <td>{{ $player->uniform_num }}</td> <td>{{ $player->position }}</td> <td>{{ $player->name }}</td> <td>{{ $player->club }}</td> <td>{{ $player->birth }}</td> <td>{{ $player->height }}</td> <td>{{ $player->weight }}</td> <td>{{ $player->t_goals }}</td> </tr> @endforeach </tbody> </table> <div class="row justify-content-end"> {{ $players->links() }} </div> </main> <!-- /.container --> <script src="{{ asset('js/app.js') }}"></script> </body> </html>ルーター作成
routes/web.phpに下記を記述web.phpRoute::get('/', 'WelcomeController@index'); Route::resource('players', 'PlayerController'); Route::resource('countries', 'CountryController'); Route::resource('goals', 'GoalController'); Route::resource('pairings', 'PairingController');課題はここから…
- テーブルビューを1つ(あるいは2つ)追加
- functionを1つ追加
- controllerからviewに変数渡し してこのツイートと同じ見た目になるようにします
答えはコチラ
Task 5はいくつか追加の課題もあります
1. レコード追加
2. 論理削除
3. phpMyAdminのインストールイージーモードになるyps委員長のブログはこちら
miyupaca log ⇒ yps学習記録その5以上でTask5は終了です。
(ここでかなりの脱落者が出ましたが、今は答えもGitHubに載っているのでコピペでもいけてしまうかと思います…)
- 投稿日:2020-10-13T23:10:09+09:00
yps並走備忘録 Task5 簡易Webアプリの作成(SQLとモデルを理解する編)
今回の主な課題
・MySQLのVIEWテーブルの理解
・Bootstrap UIの導入
・Laravel Mixの使い方事前準備
Node.jsとnpmのアップデート
sudo yum remove node npm -y
curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install nodejs -y
インストールで来たら下記コマンドでアップデートされていることを確認
$ node -v
例:v12.18.3
$ npm -v
例:6.14.6一度Laravelのディレクトリに戻り、Laravel Mix(開発用)でビルドします
cd /var/www/html/yps
rm -rf ./node_modules
npm install && npm run dev
LaravelのuiにBootstrapを指定
composer require laravel/ui
php artisan ui bootstrap
Bootstrapをビルド
npm install && npm run dev
今回使用するデータをMySQLへ
1. VS Code内でターミナルを起動(ctrl+shift+@)
2.pwd
⇒Laravelのプロジェクトフォルダ(/var/www/html/yps)にいることを確認
3.mkdir resources/sql
⇒SQLファイル格納用のディレクトリを作成
4.mysqldump -u root -p -d worldcup2014db > resources/sql/worldcup2014db.sql
⇒テーブルの定義ファイルを取得(データは入手出来ていません)
5.grep -i 'create table' resources/sql/worldcup2014db.sql
⇒テーブルを確認[^1]
6. 各テーブル(countries, goals, pairings, players)にフィールド(expired_at, deleted_at, updated_at, created_at)を追加5の結果
CREATE TABLE `countries` ( CREATE TABLE `goals` ( CREATE TABLE `goals_tmp` ( CREATE TABLE `pairings` ( CREATE TABLE `pairings_tmp` ( CREATE TABLE `players` ( CREATE TABLE `players_tmp` (6の参考例
CREATE TABLE `countries` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `ranking` int(11) DEFAULT NULL, `group_name` varchar(1) DEFAULT NULL, `expired_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;MySQLでデータベースを削除⇒作り直し
1.mysql -u root -p
2.drop database worldcup2014db;
3.create database worldcup2014db;
4.use worldcup2014db;
5.source resources/sql/worldcup2014db.sql;
⇒ 作ったSQLファイルを元にテーブルを作成
6.show tables;
⇒ テーブルが作成されているか確認
7. OKならexit;
でMySQL CLiから出るTask 3 同様にデータを取得~流し込みの準備
1.cd /tmp
2.sudo yum install wget unzip -y
3.wget http://tech.pjin.jp/wp-content/uploads/2016/04/worldcup2014.zip
4.unzip http://worldcup2014.zip
5.ls -la worldcup2014.sql
6. 各テーブル(countries, goals, goals_tmp, pairings, pairings_tmp, players, players_tmp)のCREATE TABLE ~ DEFAULT CHAESET=utf8;までを削除(↓の部分)CREATE TABLE `countries` ( `id` int(11) NOT NULL, `name` varchar(50) DEFAULT NULL, `ranking` int(11) DEFAULT NULL, `group_name` varchar(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;**データの流し込み
7. mysql -u root -p -D worldcup2014db
8. source /tmp/worldcup2014.sql; //warningが出ますが特に問題はないそうです
9. 各テーブルのデータ件数をチェックして下記になっていればOKmysql> select count(*) from countries; +----------+ | count(*) | +----------+ | 32 | +----------+ mysql> select count(*) from goals; +----------+ | count(*) | +----------+ | 188 | +----------+ mysql> select count(*) from pairings; +----------+ | count(*) | +----------+ | 144 | +----------+ mysql> select count(*) from players; +----------+ | count(*) | +----------+ | 736 | +----------+問題なければ
exit;
でMySQL Cliから出る簡易アプリケーションの作成
使うデータは用意できたので、今度はデータを使ってアプリケーションを作成してみます。
モデルクラスの作成
Task3で作成したモデルクラスを削除
cd /var/www/html/yps
rm app/Models/Player.php
以下のコマンドを打ってデータベースと連動したモデルクラスを作成します
php artisan make:model Models/Country -m
php artisan make:model Models/Goal -m
php artisan make:model Models/Pairing -m
php artisan make:model Models/Player -m
上記で作成した各モデルクラスに明示的にテーブルを指定し、ついでに$dateも指定します。
※以下はPlayerモデルの例ですPlayer.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Player extends Model { protected $table = "players"; protected $dates = ["expired_at", "deleted_at", "updated_at", "created_at"]; }コントローラークラスの作成
php artisan make:controller CountryController --resource --model=Models/Country
php artisan make:controller GoalController --resource --model=Models/Goal
php artisan make:controller PairingController --resource --model=Models/Pairing
php artisan make:controller PlayerController --resource --model= Models/Player
php artisan make:controller WelcomeController --resource
Viewの作成
resources/views/welcome.blade.phpに以下をコピペ<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <title>yotaro prg</title> <!-- Fonts --> <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet"> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> <meta name="generator" content="Jekyll v4.1.1"> <!-- Bootstrap core CSS --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> <!-- Favicons --> <link rel="apple-touch-icon" href="/yotaro.jpg" sizes="180x180"> <link rel="icon" href="/yotaro.jpg" sizes="32x32" type="image/jpg"> <link rel="icon" href="/yotaro.jpg" sizes="16x16" type="image/jpg"> <?php /* <link rel="manifest" href="/docs/4.5/assets/img/favicons/manifest.json"> <link rel="mask-icon" href="/docs/4.5/assets/img/favicons/safari-pinned-tab.svg" color="#563d7c"> */ ?> <link rel="icon" href="/yotaro.jpg"> <meta name="theme-color" content="#563d7c"> <style> body { padding-top: 5rem; } .starter-template { padding: 3rem 1.5rem; text-align: center; } .bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } </style> </head> <body> <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> <a class="navbar-brand" href="#">Navbar</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExampleDefault"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">Home <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="#">Link</a> </li> <li class="nav-item"> <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a> <div class="dropdown-menu" aria-labelledby="dropdown01"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <a class="dropdown-item" href="#">Something else here</a> </div> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> <main role="main" class="container"> <h1>WorldCup 2014 選手一覧</h1> </br> <table class="table table-striped table-hover table-sm table-responsive-sm"> <thead> <tr> <th scope="col">#</th> <th scope="col">country</th> <th scope="col">uniform_num</th> <th scope="col">position</th> <th scope="col">name</th> <th scope="col">club</th> <th scope="col">birth</th> <th scope="col">height</th> <th scope="col">weight</th> <th scope="col">total_goals</th> </tr> </thead> <tbody> @foreach ($players as $player) <tr> <td>{{ $player->id }}</td> <td>{{ $player->c_name }}</td> <td>{{ $player->uniform_num }}</td> <td>{{ $player->position }}</td> <td>{{ $player->name }}</td> <td>{{ $player->club }}</td> <td>{{ $player->birth }}</td> <td>{{ $player->height }}</td> <td>{{ $player->weight }}</td> <td>{{ $player->t_goals }}</td> </tr> @endforeach </tbody> </table> <div class="row justify-content-end"> {{ $players->links() }} </div> </main> <!-- /.container --> <script src="{{ asset('js/app.js') }}"></script> </body> </html>ルーター作成
routes/web.phpに下記を記述web.phpRoute::get('/', 'WelcomeController@index'); Route::resource('players', 'PlayerController'); Route::resource('countries', 'CountryController'); Route::resource('goals', 'GoalController'); Route::resource('pairings', 'PairingController');課題はここから…
- テーブルビューを1つ(あるいは2つ)追加
- functionを1つ追加
- controllerからviewに変数渡し してこのツイートと同じ見た目になるようにします
答えはコチラ
Task 5はいくつか追加の課題もあります
1. レコード追加
2. 論理削除
3. phpMyAdminのインストールイージーモードになるyps委員長のブログはこちら
miyupaca log ⇒ yps学習記録その5以上でTask5は終了です。
(ここでかなりの脱落者が出ましたが、今は答えもGitHubに載っているのでコピペでもいけてしまうかと思います…)
- 投稿日:2020-10-13T22:39:40+09:00
【laravel 8.x】Logging の設定とログフォーマットの実装方法【基本編】
laravel
のログにはPHPでよく使われているらしいMonolog
が実装されている。
こいつをごにょごにょしていい感じにしたいのが本記事。
(簡潔に書きたかったがソースも書くと長くなるねぇ…)Laravel は
ver8.9.0
。(v6くらいから同じだと思う)
目的はログのフォーマットの変更とカラーリング。
- ソース:ログ 8.x Laravel
- monolog:Seldaek/monolog: Sends your logs to files, sockets, inboxes, databases and various web services
- カラーリング: bramus/monolog-colored-line-formatter: Colored/ANSI Line Formatter for Monolog
- 大いに参考:Laravelでログのフォーマットを変えたい - Qiita
1. ログの出力方法
Laravel のログは RFC5424 ってので定義されている8つのレベルが扱える。
emergency
>alert
>critical
>error
>warning
>notice
>info
>debug
ログを出力する方法は facade を使う方法と helper を使う方法の2種類(たぶん)。
デフォルトではstorage/logs/laravel.log
に出力される。Log Facade でログを出力する。
use Illuminate\Support\Facades\Log; $name = 'sample kunn'; Log::info('Showing user: '.$name); // => [2020-10-13 19:27:04] local.INFO Showing user: sample kunn Log:notice('User failed to login.', ['id' => 3]); // => [2020-10-13 19:32:38] local.INFO User failed to login. {"id":3}第2引数(context) に渡したデータはシリアライズされて出力される(デフォルト)。
Logger Helper でログを出力する
logger()->info('sample text'); // => [2020-10-13 19:35:43] local.INFO sample textどちらとも
LogManager
を内部で呼び出しているので使い方は同じっぽい。
RFC5424 の 8 つのレベルの関数が定義されているので使い分けるべし。
Facade と Helper、決めた方に統一して使うのが賢そう。
参考:Illuminate\Log\LogManager | Laravel API
local.xxx
は.env
のAPP_ENV
の値かな?
production
とかlocal
といった実行環境の値となる。他のロガーでいうカテゴリやタイプといった塊は存在しない。(ほんとぉ?)
必要ならラッパーを作るなりする必要がありそう。
- Laravel ConsoleとHttpのログファイルを分ける - Qiita
- この辺りを参考に
processor
にフィールドを追加させるのが最適解だと思う。Exception ログ
use Exception; logger()->error(new Exception('Reigai'));Exception 系列を渡せば stackTrace も出力される。
2. ログの設定ファイル
基本的には
app/config/logging.php
に設定を書き込むだけで使える。app/config/logging.php'default' => env('LOG_CHANNEL', 'stack'), 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', 'days' => 14, ], 'stderr' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], ], ... ]
channel
というのはログの出力方法を指したもので、これを使ってログを出力する。
TVのチャンネルみたいに電波を分ける役割(でいいのかな?)。
デフォルトではstack
というものが指定されている。
stack
だけは特殊で、プロパティのchannels
で指定した複数のチャンネルに処理を渡すことができる。
TVでいう多チャンネル同時視聴。
デフォルトでは['single']
が指定されているので、single
というチャンネル1つにログの出力を渡している。
single
はlogs/laravel.log
に書き込むチャンネルなので、logファイルに追記されるわけだ。初めから定義されていたチャンネルを以下にまとめる。
channel名 説明 stack 複数のチャンネルへログを渡す single 指定したファイルにログを書き出す daily 指定したファイルにログを書き出す(所謂ローテート)
一日ごとにファイルを区切り、一定の日数が経過したら削除する(任意)slack Slack に出力する papertrail Papertrail に出力する stderr 標準エラー出力に出力する syslog Linux 等の syslog に出力する errorlog PHPの error_log() に出力する null 何もしない emergency (emergency ログだけ出力?) 他にも SNS に送ったり DB に送ったりと、大抵のやりたいことは誰かがライブラリを実装してくれてる。
3. stdout channel の追加
log を使ってて思ったのが、cli を操作している時は標準出力にも出力して欲しい点。
stderr
チャンネルは存在するので、それをベースにstdout
チャンネルを作成してみる。app/config/logging.php'stdout' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stdout', ], 'level' => 'debug', // all log ],
StreamHandler
のstream
パラメータにPHPの標準出力(stdout)を指定した。
level
によって出力するログレベルを制限できるが、最低値のdebug
にすることにより、全てのログを対象としている。
試しにchannels.stack.channels
にstdout
を追加すると標準出力にも出力されるようになるはず。
Handler
は monolog 側のシステムなので今回は省く。
詳しくはこのあたり。[オプション] CLI実行時(php artisan)のみ標準出力にも出力する
このまま
stack
に追記でもいいのだが、CLIを操作していない時に標準出力に出力されるのはあまり好みではない。
(schedule 処理、http のアクセス時等にも出力されてしまう(握りつぶされるが))
なので CLI で実行したときに限って出力するようにしてみる。App\Providers\AppServideProvider.phpclass AppServiceProvider extends ServiceProvider { public function boot() { ... // logger cli mode if(strpos(strtolower(php_sapi_name()), 'cli') !== false) { $path = 'logging.channels.stack.channels'; $stacks = collect(config($path, [])) ->push('stdout') ->unique(); config([$path => $stacks]); } } }php_sapi_name() という関数を実行すると、
apache
やcli
、cgi
といった文字列が帰ってくる。
これを利用して、名前にCLIが含まれていたら stack にstdout
を追加する仕組み。
collection の unique() を使うことで重複実装を防いでいる。とりあえず
AppServiceProvider
に実装したが、LoggerServideProvider
とかを作って実装したほうが賢い。参考:php - Detect if running from the command line in Laravel 5 - Stack Overflow
4. ログ文字列のフォーマット
さて本題。
デフォルトの出力文字列は以下のような形式だった。これは monolog のデフォルトの
Formatter\LineFormatter
で以下が適用されているためである。
[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n
参照:monolog/LineFormatter.php at master · Seldaek/monologこいつを変更したいので
LineFormatter
を拡張した class を作成する。App\Logging\Formatters\CustomLineFormatter.php<?php namespace App\Logging\Formatters; use Monolog\Formatter\LineFormatter; class CustomLineFormatter extends LineFormatter { public function __construct() { // 2020-10-13 17:12:15.375 [local.INFO] log message という形式で出力 $lineFormat = "%datetime% [%channel%.%level_name%] %message%" . PHP_EOL; $dateFormat = "Y-m-d H:i:s.v"; // PHP: DateTime::format parent::__construct($lineFormat, $dateFormat, true, true); } public function format(array $record): string { // var_dump($record); $output = parent::format($record); // var_dump($output); return $output; } }
LineFormatter
のコンストラクタは以下を引数に取る。
引数 説明 $format フォーマット文字列 $dateFormat 日付(%datetime%)のフォーマット文字列
参考:PHP: DateTime::format - Manual$allowInlineLineBreaks ログの改行を許すか $ignoreEmptyContextAndExtra context と extra の値が空のときに [] を削除するかどうか 参照:monolog/LineFormatter.php at master · Seldaek/monolog
format()
は今回は親のものを呼び出しているだけだが、ここでフォーマットのやり方を定義することができる。
引数のrecord
配列は以下の通り。$record = [ 'message' => $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, 'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), 'extra' => [], ];参照:monolog/Logger.php at master · Seldaek/monolog
ここで
level
やmessage
を加工してparent::format($record)
を呼べば、かなり自由度の高いことが可能だ。
context
はログ関数実行時の第2引数(配列?オブジェクト?)。
今のままだと context は握りつぶされているので、
$record['message] .= json_encode($record['context']);
みたいな実装が要るかも。ここで DB に保存をかけたりしても面白そう。
参考にした文献だと context をvsprintf()
に突っ込むことでprintf()
like な実装を示してた。
Levelに応じた絵文字とかタイマーとか実装できたら楽しそう。
extra
は実行された行番号やIPアドレスなどを取得できるが、今回は扱わない。
processor
という仕組みを使うのだが、この辺りが参考になるかも。Laravel にフォーマットを適用する
laravel に適用するには
tap
という配列パラメータに__invoke()
が実装されているクラスを指定してあげる必要がある。参考:ログ 8.x/ Laravel
の下の方の「チャンネル用Monologカスタマイズ」App\Logging\LineFormatterApply.php<?php namespace App\Logging; use App\Logging\Formatters\CustomLineFormatter; class LineFormatterApply { public function __invoke($logging) { $customLineFormatter = new CustomLineFormatter(); foreach($logging->getHandlers() as $handler) { $handler->setFormatter($customLineFormatter); } } }おそらく
CustomLineFormatter
を指定があった handler 全てに渡すのだと思う。
(extend とか implements とかが無い暗黙実装は嫌いだぁ…)これを
config/logging
の使わせたいチャンネルに指定してあげる。
今はstdout
とsingle
に。
(stack
に渡すと全チャンネルに適用できるが、子で指定していた場合上書きされた)
(formatter は一つしか追加できない?)app/config/logging.phpuse App\Logging\LineFormatterApply; 'stdout' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stdout', ], 'level' => 'debug', 'tap' => [ColorFormatterApply::class], // <-- これ ],こんな感じの出力に変えられた。
5. ログをカラフルに出力する
標準出力のログはやっぱり色が欲しい。
視認性も上がるし、なによりログの把握速度が圧倒的である。調べると monolog 側に良さそうなライブラリがあった。
bramus/monolog-colored-line-formatter: Colored/ANSI Line Formatter for Monologconsolecomposer require bramus/monolog-colored-line-formatter ~3.0
これをそのまま
formatter
扱いで定義すると色が付くのだが、それでは先程作成した文字列のフォーマットが適用されない。
なので、CustomLineFormatter
を拡張して色を付けることにする。App\Logging\Formatters\ColorLineFormatter.php<?php namespace App\Logging\Formatters; use Bramus\Monolog\Formatter\ColorSchemes\DefaultScheme; // quote by: // https://github.com/bramus/monolog-colored-line-formatter/blob/master/src/Formatter/ColoredLineFormatter.php class ColorLineFormatter extends CustomLineFormatter { private $colorScheme = null; public function getColorScheme() { if (!$this->colorScheme) { $this->colorScheme = new DefaultScheme(); } return $this->colorScheme; } public function format(array $record) : string { $output = parent::format($record); $colorScheme = $this->getColorScheme(); return $colorScheme->getColorizeString($record['level']).trim($output).$colorScheme->getResetString()."\n"; } }
parent::format($record)
した後に、その文字列に level に応じた色を付ける。
ライブラリのフォーマット部分を参考に必要な処理を移植した形だ。
参考:monolog-colored-line-formatter/ColoredLineFormatter.php at master · bramus/monolog-colored-line-formatterApp\Logging\ColorFormatterApply.php<?php namespace App\Logging; use App\Logging\Formatters\ColorLineFormatter; class ColorFormatterApply { public function __invoke($logging) { $coloredLineFormattetr = new ColorLineFormatter(); foreach($logging->getHandlers() as $handler) { $handler->setFormatter($coloredLineFormattetr); } } }で、これを
stdout
のチャンネルのtap
に指定すればOK。
※制御文字を使用しているので、ファイル出力には使わないように。とてもいい感じになった。
ログのメッセージ部は白のままにしたい時は、正規表現でその部分だけ抜き取って色を付けると良いかも。6. サンプル
自分が使用したいログの仕様をまとめて、それを実装したサンプルを置いておく。
- NOTICE 以上のログが1日毎に別のファイルに保存される
daily
チャンネル
- 保存期間(
days
)は7日- ERROR 以上のログが1つのファイルに保存される
singleError
チャンネル- CLIで実行した際、全てのログが標準出力に色付きで出力される
stdout
チャンネル
- これは
AppServiceProvide
で追加するのでstack
では触らないApp\Config\logging.phpuse Monolog\Handler\StreamHandler; use App\Logging\LineFormatterApply; use App\Logging\ColorFormatterApply; ... 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['dailyNotice', 'singleError'], 'ignore_exceptions' => false, ], 'singleError' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel_error.log'), 'level' => 'error', // upper error log 'tap' => [LineFormatterApply::class], ], 'dailyNotice' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'notice', // upper notice log 'days' => 7, 'tap' => [LineFormatterApply::class], ], 'stdout' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stdout', ], 'level' => 'debug', // all log 'tap' => [ColorFormatterApply::class], ], ],出力は以下の感じ。
log ファイルのカラーリングは VSCode の Log File Highlighter を使用している。おわりに
monolog の
handler
とprocessor
周りをまだ理解しきれてないので、とりあえず基本をまとめた。
あと laravel の Exception 周りも知らないといけないかも。これ
node-log4js
より楽かもしれぬ。
個人的にタイマーは実装してみたい。
綺麗なログを吐けると開発が捗る捗る…。続きは、今のログに手を入れるとき(書くとは言っていない)。
- 投稿日:2020-10-13T21:52:58+09:00
blade構文
【概要】
1.値
2.条件分岐ディレクティブ
3.繰り返しディレクティブ
補足(i)
補足(ii)
1.値
{{***}}{{}}の中身は値だけでなく、変数・関数なども使用できます。HTML文でエスケープ処理されたくない時は、下記のように使用します。
{{!! !!}}
2.条件分岐ディレクティブ
@if (条件式) //内容 @elseif (条件式) //内容 @else //内容 @endifまた@ifの逆の@unless-@endunlessもあります。
3.繰り返しディレクティブ
for
@for(初期値 ; 条件 ; 処理) #例:@for($i=0 ; $i<=10 ; $i++) //内容 #例:echo $i; @endforまた@breakは繰り返し処理の中断、@continueはcontinue以降は表示せず@forの繰り返し処理を引き続き行います。
foreach
@foreach ($配列 as $変数) //内容 @empty #--❶ //変数が空の時の内容 @endforeach❶については記載しなくてもOKです。@ifでいう@elseにあたります。
while
@while (条件) //内容 @endwhile
補足(i)
3.の繰り返しディレクティブには、$loopというループ変数があり、繰り返し処理のプロパティとして使用することが可能です。
@foreach($category as $item) @if ($loop->first) #---❶ <h1>アイテム一覧</h1> <ul> @endif <li>No.{{$loop->iteration}}.{{$item}}</li> #---❷ @if ($loop->last) #---❸ </ul> @endif @endforeach❶:loop->firstは、最初の繰り返しならtrueを返します。
❷:loop->iterationは、1から順番に繰り返し、その回数を返します。
❸:loop->lastは、最後の繰り返しならtrueを返します。
補足(ii)
index.phpファイルとindex.blade.phpファイルが同じviewフォルダの中の指定のフォルダに入っていたとします。その時に、controllerにある”return view('@@@@.index' , $@@@@)'はLaravelでは'index'としてした場合はindex.blade.phpファイルが優先して読み込まれます。
- 投稿日:2020-10-13T20:49:55+09:00
Laravelのキャッシュクリアの備忘録
上5つしかしてなかったので備忘録
php artisan cache:clear && php artisan config:clear && php artisan config:cache && php artisan route:clear && php artisan view:clear && php artisan clear-compiled && php artisan optimize && composer dump-autoload && rm -f bootstrap/cache/config.php
- 投稿日:2020-10-13T20:47:17+09:00
開発環境でのメール確認用にMailHogを利用する(Docker利用)
開発環境でメールの確認方法が面倒だったり、配信先に気を使う場合がありますが、MailHogというツールを使うとローカル内に閉じたメールの確認ができる環境を簡単に立ち上げることができます。
MailHogはGo言語でできているらしいです。
以前はMailCatcherという別の類似ツールを利用していましたが、MailHogの方が構築が容易だったので乗り換えました。
docker-composeで以下の構成で立ち上げます。
ファイル構成
. ├── docker-compose.yml └── php ├── docker │ ├── Dockerfile │ └── php.ini └── mail_test.php
docker-compose.yml
8025番ポートでwebインターフェースを利用するのでポートを設定します
version: '3' services: php: build: context: ./ dockerfile: php/docker/Dockerfile volumes: - ./php/:/var/www/html/ mailhog: image: mailhog/mailhog ports: - "8025:8025" #webインターフェース用ポートDockerfile
phpからのメール送信に
mhsendmail
が必要なので、Dockerfileでインストールします。FROM php:5.6-apache WORKDIR /var/www/html RUN curl -sSL https://github.com/mailhog/mhsendmail/releases/download/v0.2.0/mhsendmail_linux_amd64 -o mhsendmail \ && chmod +x mhsendmail \ && mv mhsendmail /usr/local/bin/mhsendmail COPY ./php/docker/php.ini /usr/local/etc/php/php.ini
[mail function]
を書き換えておきます。[mail function] ; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). ; http://php.net/sendmail-path ; sendmail_path = /usr/sbin/sendmail -t -i ; 以下に書き換え sendmail_path = "/usr/local/bin/mhsendmail --smtp-addr=mailhog:1025"mail_test.php
<?php $to = "hoge@localhost.local"; $subject = "TEST"; $message = "メールテスト"; $headers = "From: from@example.com"; mb_send_mail($to, $subject, $message, $headers);docker-compose で up
$ docker-compose up -d Building php Step 1/4 : FROM php:5.6-apache ---> 24c791995c1e Step 2/4 : WORKDIR /var/www/html ---> Using cache ---> 1294d05c5c03 Step 3/4 : RUN curl -sSL https://github.com/mailhog/mhsendmail/releases/download/v0.2.0/mhsendmail_linux_amd64 -o mhsendmail && chmod +x mhsendmail && mv mhsendmail /usr/local/bin/mhsendmail ---> Using cache ---> db0719944c4e Step 4/4 : COPY ./php/docker/php.ini /usr/local/etc/php/ ---> 264eb166413a Successfully built 264eb166413a Successfully tagged mailhog_php:latest WARNING: Image for service php was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Pulling mailhog (mailhog/mailhog:)... latest: Pulling from mailhog/mailhog df20fa9351a1: Already exists ed8968b2872e: Pull complete a92cc7c5fd73: Pull complete f17c8f1adafb: Pull complete 03954754c53a: Pull complete 60493946972a: Pull complete 368ee3bc1dbb: Pull complete Digest: sha256:8d76a3d4ffa32a3661311944007a415332c4bb855657f4f6c57996405c009bea Status: Downloaded newer image for mailhog/mailhog:latest Creating mailhog_php_1 ... done Creating mailhog_mailhog_1 ... donemailhogのWEBインターフェース
phpのコンテナ内からメール送信スクリプトを実行
$ docker exec -it mailhog_php_1 /bin/bash root@9c4b6a5df613:/var/www/html# php mail_test.php
- 投稿日:2020-10-13T19:07:57+09:00
【PHP】配列のキーを維持したまま array_column を使いたい
array_column について
PHPの
array_column
は,多次元配列から単一のカラムの値を返してくれる,とても便利な関数です。php// このような多次元配列から… $array = [ 2 => [ 'id' => 111, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 222, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 333, 'name' => 'tanaka', 'age' => 55 ], ]; // 単一のカラム値の配列を作れる var_export( array_column( $array, 'name' ) ); // array ( // 0 => 'suzuki', // 1 => 'sato', // 2 => 'tanaka', // )しかしながら,返り値の配列のキーは
- 第3引数を指定しない場合:単純に0から振り直される
- 第3引数を指定した場合 :指定カラムの値がキーとして使われる
のどちらかで,元の配列のキー(上の例だと「2」「3」「5」)は維持されません。
元の配列のキーを維持したいとき
次の3通りの方法を紹介します。
- 方法①
array_column
と他の関数を組み合わせる- 方法②
array_map
を使う- 方法③
foreach
を使う※2020/10/14 当記事の初投稿時には方法①のみを紹介していたのですが,コメント欄で@tadsanさんに教えていただいた方法②に加えて,foreachを使う場合の方法③も追加しました。また,まとめの内容を変更し,
array_column
についての蛇足も追記しました。
方法①
array_column
と他の関数を組み合わせる
array_keys
で元の配列のキーを取得し,第3引数を指定しないarray_column
の返り値と合体させましょう。対応する配列の生成にはarray_combine
を使います。php$array = [ 2 => [ 'id' => 111, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 222, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 333, 'name' => 'tanaka', 'age' => 55 ], ]; // キーを配列として取得 $keys = array_keys( $array ); // => [ 2, 3, 5 ] // 指定要素を配列として取得 $values = array_column( $array, 'name' ); // => [ 'suzuki', 'sato', 'tanaka' ] // それぞれの配列を合体 $combined = array_combine( $keys, $values ); // 出力 var_export( $combined ); // array ( // 2 => 'suzuki', // 3 => 'sato', // 5 => 'tanaka', // )上記の操作を1行で書くと以下のようになります。
phparray_combine( array_keys( $array ), array_column( $array, 'name' ) );
- メリット :関数の使い方として何をやっているかは分かりやすい
- デメリット:関数の呼び出しが多い
方法②
array_map
を使う「
array_column
を使いたい」という記事なのにタイトル詐欺もいいとこですが,こちらの方が簡潔に記述できます(コメント欄で@tadsanさんに教えていただきました)。(コメント欄からコピペ)$array = [ 2 => [ 'id' => 111, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 222, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 333, 'name' => 'tanaka', 'age' => 55 ], ]; var_dump(array_map(fn($a) => $a['name'], $array));上記のコードの配列操作部分を抜き出すと以下のようになります。
(コメント欄からコピペ)array_map(fn($a) => $a['name'], $array);
array_map
の第1引数のfn($a) => $a['name']
の部分がPHP7.4で導入された「アロー関数」なので,7.4より前の環境では「無名関数」を使って置き換えることになります。phparray_map( function ($a) { return $a['name']; }, $array );
- メリット :アロー関数が使える環境では簡潔に記述できる
- デメリット:ワンライナーで無名関数を使う場合には少し可読性が下がる
方法③
foreach
を使う「
foreach
を使わずに済むようにarray_column
があるのでは?」と言われればそれまでなのですが,方法②でarray_column
を使わなかったので一応。php$array = [ 2 => [ 'id' => 111, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 222, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 333, 'name' => 'tanaka', 'age' => 55 ], ]; $result = []; foreach ( $array as $key => $values ) { $result[ $key ] = $values['name']; } var_export( $result ); // array ( // 2 => 'suzuki', // 3 => 'sato', // 5 => 'tanaka', // )
- メリット :一見して何をやっているか分かりやすい
- デメリット:一時変数が必要,コードも複数行にわたる
まとめ
方法②の
array_map
とアロー関数を使ったワンライナーが一番タイプ数も少なくおすすめです。思想信条上の理由でクロージャを使いたくない方は,
- 1行にまとめたければ方法①
- だれが見ても分かりやすいコードにしたければ方法③
が良いと思います。記事タイトルにそぐわない結論になってしまいましたが。
蛇足
array_column
はPHP5.5から導入された関数ですが,よくよく調べてみると「第3引数が未指定の場合に元配列のキーを維持する案」がリリース前に出ており,実際にプルリクエストも出されていたみたいですが,議論の末に見送られた経緯があるようです。こんな記事を書いといてアレですが,元配列のキーを維持したくない場合の方が多いはずなので,これは見送られて良かったと思われます。それとは別に,「第3引数に true が指定された場合に元配列のキーを維持する案」もあったようで,この案についてはリリース後もたまにリクエストがあがっているみたいです(PHPで実装された array_column リポジトリの issue とか)。
ただ,PHP公式サイト上のこのページ(←何故かつながりづらい)では,「第3引数に true を指定する案」や「第4引数を追加する案」などのいかなる変更も既存の予想される動作を破壊し得るとして,
array_column
の実装者である ramsey さんがリクエストを close しており,キーを維持するための機能追加が行われることは今後まず無さそうです。参考
array_column
の公式ページに「第3引数に -1 を渡すと元の配列のキーを維持する関数」が掲載されています。この関数をコピペして使えば,array_column_ext( $array,'name', -1 )
のように簡潔な記述ができます(どんな場合でも使えるかどうかは検証してないので分かりません)。 ↩
- 投稿日:2020-10-13T19:07:57+09:00
【PHP】キーを維持したままarray_columnを使いたい
array_column
関数PHPの
array_column
は,多次元配列から単一のカラムの値を返してくれる,とても便利な関数です。// このような多次元配列から… $array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // 単一のカラム値の配列を作れる var_export( array_column( $array, 'name' ) ); // array ( // 0 => 'suzuki', // 1 => 'sato', // 2 => 'tanaka', // )しかしながら,返り値の配列のキーは
- 第3引数を指定しない場合:単純に0から振り直される
- 第3引数を指定した場合 :指定カラムの値がキーとして使われる
のどちらかで,元の配列のキー(上の例では「2」「3」「5」)は維持されません。
元の配列のキーを維持したいとき
array_keys
で元の配列のキーを取得し,第3引数を指定しないarray_column
の返り値と合体させましょう。対応する配列の生成にはarray_combine
を使います。$array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // キーを配列として取得 $keys = array_keys( $array ); // => [ 2, 3, 5 ] // 指定要素を配列として取得 $values = array_column( $array, 'name' ); // => ['suzuki', 'sato', 'tanaka'] // それぞれの配列を合体 $combined = array_combine( $keys, $values ); // 出力 var_export( $combined ); // array ( // 2 => 'suzuki', // 3 => 'sato', // 5 => 'tanaka', // )まとめ
元の配列のキーを維持して
array_column
を使うことができました。上記の操作を1行で書くと以下のようになります。$combined = array_combine( array_keys( $array ), array_column( $array, 'name' ) );参考
PHP array_column - how to keep the keys? - Stack Overflow1
値が
''
,null
,false
などのときに配列に取り込みたくない場合は,上記のワンライナーをarray_filter
で囲むといいそうです。 ↩
- 投稿日:2020-10-13T19:07:57+09:00
【PHP】キーを維持したまま array_column を使いたい
array_column
関数PHPの
array_column
は,多次元配列から単一のカラムの値を返してくれる,とても便利な関数です。php// このような多次元配列から… $array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // 単一のカラム値の配列を作れる var_export( array_column( $array, 'name' ) ); // array ( // 0 => 'suzuki', // 1 => 'sato', // 2 => 'tanaka', // )しかしながら,返り値の配列のキーは
- 第3引数を指定しない場合:単純に0から振り直される
- 第3引数を指定した場合 :指定カラムの値がキーとして使われる
のどちらかで,元の配列のキー(上の例では「2」「3」「5」)は維持されません。
元の配列のキーを維持したいとき
array_keys
で元の配列のキーを取得し,第3引数を指定しないarray_column
の返り値と合体させましょう。対応する配列の生成にはarray_combine
を使います。php$array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // キーを配列として取得 $keys = array_keys( $array ); // => [ 2, 3, 5 ] // 指定要素を配列として取得 $values = array_column( $array, 'name' ); // => ['suzuki', 'sato', 'tanaka'] // それぞれの配列を合体 $combined = array_combine( $keys, $values ); // 出力 var_export( $combined ); // array ( // 2 => 'suzuki', // 3 => 'sato', // 5 => 'tanaka', // )まとめ
元の配列のキーを維持して
array_column
を使うことができました。上記の操作を1行で書くと以下のようになります。php$combined = array_combine( array_keys( $array ), array_column( $array, 'name' ) );参考
PHP array_column - how to keep the keys? - Stack Overflow1
array_column - PHP.net2
値が
''
,null
,false
などのときに配列に取り込みたくない場合は,上記のワンライナーをarray_filter
で囲むといいそうです。 ↩
array_column
の公式ページに「第3引数に -1 を渡すと元の配列のキーを維持する関数」が掲載されています。 ↩
- 投稿日:2020-10-13T19:07:57+09:00
【PHP】元の配列のキーを維持して array_column を使いたい
array_column
関数PHPの
array_column
は,多次元配列から単一のカラムの値を返してくれる,とても便利な関数です。php// このような多次元配列から… $array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // 単一のカラム値の配列を作れる var_export( array_column( $array, 'name' ) ); // array ( // 0 => 'suzuki', // 1 => 'sato', // 2 => 'tanaka', // )しかしながら,返り値の配列のキーは
- 第3引数を指定しない場合:単純に0から振り直される
- 第3引数を指定した場合 :指定カラムの値がキーとして使われる
のどちらかで,元の配列のキー(上の例では「2」「3」「5」)は維持されません。
元の配列のキーを維持したいとき
array_keys
で元の配列のキーを取得し,第3引数を指定しないarray_column
の返り値と合体させましょう。対応する配列の生成にはarray_combine
を使います。php$array = [ 2 => [ 'id' => 100, 'name' => 'suzuki', 'age' => 21 ], 3 => [ 'id' => 101, 'name' => 'sato', 'age' => 34 ], 5 => [ 'id' => 102, 'name' => 'tanaka', 'age' => 55 ], ]; // キーを配列として取得 $keys = array_keys( $array ); // => [ 2, 3, 5 ] // 指定要素を配列として取得 $values = array_column( $array, 'name' ); // => ['suzuki', 'sato', 'tanaka'] // それぞれの配列を合体 $combined = array_combine( $keys, $values ); // 出力 var_export( $combined ); // array ( // 2 => 'suzuki', // 3 => 'sato', // 5 => 'tanaka', // )まとめ
元の配列のキーを維持して
array_column
を使うことができました。上記の操作を1行で書くと以下のようになります。php$combined = array_combine( array_keys( $array ), array_column( $array, 'name' ) );参考
PHP array_column - how to keep the keys? - Stack Overflow1
array_column - PHP.net2
値が
''
,null
,false
などのときに配列に取り込みたくない場合は,上記のワンライナーをarray_filter
で囲むといいそうです。 ↩
array_column
の公式ページに「第3引数に -1 を渡すと元の配列のキーを維持する関数」が掲載されています。この関数をコピペして使えば,array_column_ext( $array,'name', -1 )
のようにさらに簡潔な記述ができそうです。 ↩
- 投稿日:2020-10-13T18:13:48+09:00
Google Analytics Reporting API v4を使ってPV数とUU数を取得する
はじめに
はじめてのアナリティクス Reporting API v4: サービス アカウント向け PHP クイックスタートを参考にして、指定した期間のPV数とUU数を、日毎に取得するバッチを作成できたので、クイックスタートのHelloAnalytics.phpのgetReportメソッド にあたる部分の備忘録。
cronでバッチ実行して日毎のPV数、UU数を取得するバッチを実行したい人の役に立てたらと思います。前提
- PHP7.4
- Google API ConsoleでJSON形式の秘密鍵を作成済み
- クライアントライブラリインストール済み
- 例外系の処理も書いていますが省略。
PHPプログラム
HelloAnalytics.php<?php function getReport($analytics) { // Create the DateRange object. $dateRange = new Google_Service_AnalyticsReporting_DateRange(); $dateRange->setStartDate("7daysAgo"); $dateRange->setEndDate("today"); // Create the Metrics object. // PV $pageviews = new Google_Service_AnalyticsReporting_Metric(); $pageviews->setExpression('ga:pageviews'); // UU $uniqueUsers = new Google_Service_AnalyticsReporting_Metric(); $uniqueUsers->setExpression('ga:users'); // Create the Dimension object. // Daily $byDay = new Google_Service_AnalyticsReporting_Dimension(); $byDay->setName("ga:date"); // Create the ReportRequest object. $request = new Google_Service_AnalyticsReporting_ReportRequest(); $request->setViewId($viewId); $request->setDateRanges($dateRange); $request->setMetrics([$pageviews, $uniqueUsers]); $request->setDimensions([$byDay]); $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); $body->setReportRequests([$request]); $result = $analytics->reports->batchGet($body); //今回は日毎のPV数とUU数が欲しかったので、日付をキーとした連想配列にpvとuuを入れてみました。 $data = []; foreach ($result['reports'][0]['data']['rows'] as $row) { $data[$row['dimensions'][0]] = [ "pv" => $row['metrics'][0]['values'][0], "uu" => $row['metrics'][0]['values'][1], ]; }実際の中身の形式は以下のようになります。
var_dump($data); //以下結果例 [20201013] => array(2) { 'pv' => string(5) "12345" 'uu' => string(4) "8585" }...最後に
ソートの設定しなくても古い順から結果を得ることができるんですね。
クイックスタートの参考例ではPV数だけが返ってきており、かつ指定した合計数が返ってきてしまったのですが、$uniqueUsers->setExpression('ga:users'); $byDay->setName("ga:date");こちらの設定をすることで、PV数だけでなくUU数と、指定した期間の日毎の結果を得ることができたので嬉しかったです。
- 投稿日:2020-10-13T17:01:23+09:00
Moodle 3.9 マニュアル - アクセスを制限する
アクセス制限機能を使用すると、教師は、日付、取得した成績、グループまたはアクティビティの完了などの特定の条件に従って、アクティビティまたはコースセクションの可用性を制限できます。
注:コースのアクティビティとセクションを設定するときに[アクセスを制限する]オプションが表示されない場合は、[サイト管理]> [高度な機能]で有効になっていることを管理者に確認してください。
- 投稿日:2020-10-13T15:48:45+09:00
Laravelでログ出力と併せてメールで通知する
はじめに
バグを完全に防ぐことは難しいですが、問題が起こった場合には迅速に対処できるようにしておきたいものです。
CloudWatch LogsやZabbixなどでログ監視できていると安心ですが、実際には導入が困難な場合もあるでしょう。そこで、Laravelにてログ出力と併せてメールで通知する機能を設け、簡易的なログ監視を行いたいと思います。
この場合サーバー自体がダウンしているとどうにもならないですが、プログラムのバグであればすぐに気づくことができるでしょう。なお、LaravelはデフォルトでSlack通知に対応しているので、Slackを利用されている方はそちらの方が便利かもしれません。
Laravelのバージョンは5.7にて確認しています。
Handler.phpでメール通知
Laravelのドキュメントには下記のようにあります。
Laravelの例外はすべて、App\Exceptions\Handlerクラスで処理されます。
このクラスはreportとrender二つのメソッドで構成されています。両メソッドの詳細を見ていきましょう。reportメソッドは例外をログするか、BugSnagやSentryのような外部サービスに送信するために使います。つまり、下記のreportメソッド内にメール送付するロジックを記述すれば実現できるということです。
さっそく記述してみました。App\Exceptions\Handler.phppublic function report(Exception $exception) { $error['message'] = $exception->getMessage(); $error['code'] = $exception->getCode(); $error['file'] = $exception->getFile(); $error['line'] = $exception->getLine(); $error['url'] = url()->current(); Mail::send(['text' => 'emails.exception'], ["e" => $error], function (Message $message) { $message ->to(config('mail.to.address')) ->from(config('mail.from.address')) ->subject('【'.config('app.name').'】['.ENV('APP_ENV').'] サーバーエラー発生の連絡'); }); parent::report($exception); }resources\views\emails\exception.blade.php<?php $action = (\Route::getCurrentRoute()) ? \Route::getCurrentRoute()->getActionName() : "n/a"; ?> ---------------------------------------------------------------------- このメールは{{ config('app.name') }}から自動で配信しています。 ---------------------------------------------------------------------- {{ config('app.name') }}サーバーでエラーが発生しました。 ====================================================================== [Message] {{ $e['message'] }} ====================================================================== [Action] {{ $action }} [URL] {{ $e['url'] ?? '' }} [File] {{ $e['file'] }} [Line] {{ $e['line'] }} [Code] {{ $e['code'] }} ======================================================================うまく通知できましたが、しかしこれではすべての例外を通知してしまいます。
404などの400系エラーやバリエーションエラー、未認証でのログイン画面リダイレクトまで通知されてしまうと、肝心のサーバーエラーが起こった際に通知が埋もれてしまう危険性があります。Handler.phpでメール通知(改)
というわけで通知したいエラーをある程度限定したいと思います。
再びLaravelのドキュメントを見返すと、$dontReportプロパティを利用する事で特定の例外をログ対象から外すことができるとあります。例外ハンドラの$dontReportプロパティは、ログしない例外のタイプの配列で構成します。たとえば、404エラー例外と同様に、他のタイプの例外もログしたくない場合です。
今回は認証関連の例外とバリデーションチェック関連の例外を除外してみました。
また上記に加え、400系エラーの除外と、本番環境とステージング環境のみ通知を行う判定を加えたのが下記です。App\Exceptions\Handler.phpprotected $dontReport = [ \Illuminate\Auth\AuthenticationException::class, \Illuminate\Auth\Access\AuthorizationException::class, \Illuminate\Validation\ValidationException::class, ]; public function report(Exception $exception) { $status = $this->isHttpException($exception) ? $exception->getStatusCode() : 500; if ($exception instanceof \Exception && $this->shouldReport($exception)) { if (\App::environment(['staging', 'production'])) { $status = $this->isHttpException($exception) ? $exception->getStatusCode() : 500; if ($status >= 500) { $error['message'] = $exception->getMessage(); $error['status'] = $status; $error['code'] = $exception->getCode(); $error['file'] = $exception->getFile(); $error['line'] = $exception->getLine(); $error['url'] = url()->current(); Mail::send(['text' => 'emails.exception'], ["e" => $error], function (Message $message) { $message ->to(config('mail.to.address')) ->from(config('mail.from.address')) ->subject('【'.config('app.name').'】['.ENV('APP_ENV').'] サーバーエラー発生の連絡'); }); } } } parent::report($exception); }resources\views\emails\exception.blade.php<?php $action = (\Route::getCurrentRoute()) ? \Route::getCurrentRoute()->getActionName() : "n/a"; ?> ---------------------------------------------------------------------- このメールは{{ config('app.name') }}から自動で配信しています。 ---------------------------------------------------------------------- {{ config('app.name') }}サーバーでエラーが発生しました。 ====================================================================== [Message] {{ $e['message'] }} ====================================================================== [Action] {{ $action }} [URL] {{ $e['url'] ?? '' }} [File] {{ $e['file'] }} [Line] {{ $e['line'] }} [Code] {{ $e['code'] }} [Status] {{ $e['status'] }} ======================================================================おわりに
アプリケーションの安定稼働に少しでも寄与できれば幸いです。
- 投稿日:2020-10-13T14:57:12+09:00
Moodle 3.9 マニュアル - コースの形式
コース形式とは、コースのレイアウトを指します。コース形式は、[管理]> [コース管理]> [設定の編集]で選択できます。
管理者は、[管理]> [サイト管理]> [プラグイン]> [コース形式]> [コース形式の管理]で、コースのコース形式を有効、無効、または削除できます。また、コース設定で利用可能なコース形式が教師に表示される順序を変更することもできます。
内容
1 標準コース形式
1.1 ウィークリーフォーマット
1.2 トピック形式
1.2.1 セクションを1つだけ表示する
1.3 ソーシャルフォーマット
1.4 単一アクティビティ形式
2 寄稿されたコース形式
2.1 ボタンコース形式
2.2 折りたたまれたトピック
2.2.1 概要
2.3 デイリーフォーマット
2.4 グリッドフォーマット
2.5 メニュートピック形式
2.6 掲示板フォーマット
2.7 ワントピックフォーマット
2.8 タイル形式
2.9 トピック形式(色)
2.10 柔軟なセクション形式
2.11 キックスタート形式(コースウィザード)
3 関連項目1 標準コース形式
1.1 ウィークリーフォーマット
ウィークリーフォーマットの例
コースは週ごとに編成されており、各セクションには日付の見出しがあります。 Moodleはあなたのコースの毎週のセクションを作成します。毎週のセクションにコンテンツ、フォーラム、クイズなどを追加できます。今週が強調表示されます。ヒント:すべての生徒に同時に同じ資料を作成してもらいたい場合は、これを選択することをお勧めします。
注:コースの開始日が正しいことを確認してください。そうでない場合は、週の日付が間違っています。これは、新しいセクションの学生で使用するコースを復元する場合に特に重要です。
学生が異なる時間にコースを開始する場合は、コース相対日付機能を使用して、コース内の学生の開始日を基準にした各コースセクションの日付を表示できます。1.2 トピック形式
トピック形式の例
このコースは、教師がタイトルを付けることができるトピックセクションに編成されています。各トピックセクションは、アクティビティ、リソース、およびラベルで構成されています。 Moodleの新規インストールでは、これがデフォルトのフォーマットです。ヒント:これは、コースが目的に基づいており、各目的が完了するまでに異なる時間がかかる場合に使用するのに最適です。この例は、学生が以前のトピックからの知識に基づいて構築している足場です。
1.2.1 セクションを1つだけ表示する
ウィークリー、トピック、および機能を実装する提供された形式を使用すると、[管理]> [コース管理]> [設定の編集]から「ページごとに1つのセクションを表示」することができます。これは8つのトピックからなるコースで、3番目のトピックが現在表示されています。左右のリンクはトピック2と4につながります。
ノート:
コースのホームページには、セクション名とセクションの説明のテキストだけが、アクティビティ番号とリソース番号とともに表示され、名前はクリック可能です。これは、コースに初めてアクセスしたときに表示されるものです。名前をクリックしてトピックを選択すると、一度に1つのセクションが表示されます。
編集がオンの場合、メインコースページにはすべてのセクションのすべてのコンテンツが表示されます。
各セクションページの下部に「Jumpto ...」メニューがあります1.3 ソーシャルフォーマット
この形式は、メインページにリストされているソーシャルフォーラムという1つのメインフォーラムを対象としています。これは、より自由な形式の状況で役立ちます。コースではないかもしれません。 Moodleラウンジはソーシャルフォーマットコースの一例です。
ソーシャルフォーマットを選択するときに、コースページに表示するディスカッションの数を指定できます。
ソーシャルフォーラムは、ソーシャルフォーラムページの[このフォーラムを更新]ボタンをクリックして編集できます。フォーラムの紹介は、コースページの上部に表示されます。ソーシャルアクティビティブロックを使用して、アクティビティとリソースをサイドに追加できます。
1.4 単一のアクティビティ形式
単一のアクティビティ形式には1つのセクションしかなく、教師はコースに1つのアクティビティのみを追加できます。単一のアクティビティ形式を選択すると、教師が使用するアクティビティを選択するためのドロップダウンメニューが表示されます。シングルアクティビティコース形式のスクリーンキャストと実際の例については、以下の「関連項目」セクションを参照してください。
注:このコース形式は、以前のバージョンのMoodleのSCORM形式に置き換わるものです。ここでは、単一のアクティビティとしてSCORMパッケージを選択できるためです。
活動と社会的形式が非常に似ているので、フォーラムを伴う単一の活動形式。唯一の違いは、コースに他のアクティビティが含まれている場合です。単一のアクティビティ形式では、それらは「孤立した」ものとして、教師のみに表示されます。ソーシャルフォーマットはそれらをアクティビティブロックに表示し、学生が利用できるようにします。
2 寄稿されたコース形式
Moodleコミュニティは、プラグインディレクトリで利用可能な多くのコースフォーマットを開発しました。新しいものを作成する場合は、開発者向けドキュメントを参照してください。
MDL-27646およびMDL-28555を参照してください。おそらく、ディスカッションhttp://moodle.org/mod/forum/discuss.php?d=175758#p770737を参照してください。
2.1 ボタンコース形式
ボタンコース形式では、セクションに1つずつアクセスするためのJavaScriptのボタンを含むメニューが作成されます。セクションのグループ(例:モジュール、期間)を作成し、ボタンの色を変更する機能があります。詳細については、ボタンコース形式をご覧ください。
2.2 折りたたまれたトピック
これは、基本的に標準のトピックおよびウィークリー形式と同じ形式ですが、「0」を除く各セクションに「トグル」があります。トグルの目的は、ユーザーに提示される初期情報の量を減らして、多くのコンテンツでコースを悩ます可能性のある「死のスクロール」を減らすことです。トグルの「状態」は、コースごと、ユーザーごとに記憶されます。詳細については、Collapsed_Topics_course_formatにアクセスしてください。
2.2.1 概要概要
「折りたたまれたトピック」の概要については、次のビデオを参照してください。
2.3 デイリーフォーマット
日次形式は、週ごとではなく日ごとにセクションを表示する週次形式の変更です。
2.4 グリッドの形式
グリッドコース形式は、モジュール式の視覚的なコース形式です。すべてのトピックを非表示にし、短いタイトルのアイコンのグリッド(トピックごとに1つ)を作成します。アイコンをクリックすると、対応するトピックのコンテンツが「ライトボックス」スタイルの表示で表示されます。
2.5 メニュートピック形式
menutopic形式では、メニューにトピック/セクションを表示できます。
2.6 掲示板フォーマット
掲示板形式は、コース上部のニュースフォーラムに最新の投稿を表示します。
2.7 ワントピックフォーマット
onetopic形式では、各トピックがタブに表示され、リソースへの呼び出しの間に現在のタブが保持されます。これにより、ブログまたは用語集としてモジュールから戻ると、開始した場所からタブに戻ります。このフォーマットは、Moodleの標準フォーマットである「トピック」に基づいています。
2.8 タイル形式
タイル形式では、コーストピックがリストではなく「タイル」として表示されます。クリックすると、タイルのコンテンツがアニメーション化されたトランジションとともにタイルの下に表示されます。レイアウトは、さまざまな画面サイズと向きに適応します。各タイル内で、アクティビティを「サブタイル」として表示するように設定することもできます。各タイルのアイコンは、事前定義されたセットから選択できます(つまり、教師はそれらをアップロードする必要はありません)。
2.9 トピック形式(色)
色付きのトピック形式は「トピック」標準形式に基づいており、教師は各コースセクションの前景色と背景色を指定できます。
2.10 柔軟なセクション形式
Moodle 2.4+の柔軟なセクションフォーマットでは、ネストされたセクションを使用でき、各セクションは展開(親セクションページのすべてのコンテンツを含む)または折りたたみ(別のページへのリンクとして)で表示できます。
2.11 キックスタート形式(コースウィザード)
キックスタートコース形式は、教師がコースを作成するのに役立つ特別なコース形式です。このコース形式では、教師が通常見つける空のスケルトン(場合によっては学生)の代わりに、最大3つのコーステンプレートが提供され、コースの作成がはるかに簡単になります。ドキュメントはここにあります
その他の寄稿されたコース形式は、モジュールおよびプラグインデータベースから入手できます。
3 関連項目
スクリーンキャスト:シングルアクティビティコース形式
学校のデモサイトでの単一のアクティビティコース形式の例:Moodle履歴クイズ(ユーザー名:親とパスワード:moodleでログイン)
Moodleコースフォーマットフォーラムの使用Moodleフォーラムディスカッションの使用:
- 投稿日:2020-10-13T14:20:42+09:00
文字列の基本について(シングルクオート、ダブルクオート、ヒアドキュメント)
文字列の扱い方
phpにおいて文字列を囲む「シングルクオート("")」「ダブルクオート(””)」「ヒアドキュメント(<<<EOF)」の使い分けを明確に理解できていますでしょうか。
それぞれを以下にまとめていきます。シングルクオート('')
シングルクオートは最も簡単な文字列の指定方法です。
特徴は指定された範囲内でのエスケープ処理、変数の展開を行わないという点です。特殊文字等はそのまま表示されます。 echo 'これは \$1,000 です。\n'; これは \$1,000 です。\n 変数は展開されません $sample = 'サンプル'; echo 'これは$sampleです。'; これは$sampleです。ダブルクオーテーション("")
ダブルクオーテーションはシングルクオーテーションとは異なり、
指定された範囲内でのエスケープ処理、変数の展開を行うという特徴を持ちます。特殊文字等は変換されます。 echo "これは \$1,000 です。\n"; これは \$1,000 です。[改行] 変数は展開されます $sample = 'サンプル'; echo "これは$sampleです。"; これはサンプルです。ヒアドキュメント(<<<EOF)
基本的にはダブルクオーテーションと同じで、指定された範囲内でのエスケープ処理、変数の展開を行うという特徴を持ちます。
さらにスペースや改行などもそのまま出力されるという特徴を持ちます。$result = <<<EOF a i u EOF; echo $result; a i uおわり
基本的な部分ではありますが意外ときっちり理解できていなかったので面白かったです。
- 投稿日:2020-10-13T13:44:03+09:00
初心者がLaravelのindex.phpを読み解いてみた(オートロード編)
動機
パーフェクトPHPの7章、8章を読んでみて、じゃあLaravelはどうなってるんだろうかと気になったので調べてみました。
環境
Laravel 8.x
PHP 7.3結論
require __DIR__.'/../vendor/autoload.php';
の1行で、クラスのオートロード設定、組み込み関数の宣言などを実行している。index.php
全体としては以下の通りです。非常に簡潔ですが、かなり内容は重いです。今回は
require __DIR__.'/../vendor/autoload.php';
までみていきたいと思います。public/index.php<?php use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request; define('LARAVEL_START', microtime(true)); if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) { require __DIR__.'/../storage/framework/maintenance.php'; } require __DIR__.'/../vendor/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php'; $kernel = $app->make(Kernel::class); $response = tap($kernel->handle( $request = Request::capture() ))->send(); $kernel->terminate($request, $response);名前空間の利用
こちらは問題ないかと思います。詳細は、名前空間の利用に関するドキュメントをご参照ください。
public/index.phpuse Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request;メンテナンスモード
メンテナンスモードの場合は、
storage/framework/maintenance.php
が生成され、エラーレスポンス503を返すようになります。
通常時はファイルが存在しないのでfalse
になります。メンテナンスモードの詳細はLaravelのドキュメントをご参照ください。
file_exists
:引数のファイルが存在するかどうかを判定する関数。
__DIR__
:ファイルのディレクトリを返す。public/index.phpif (file_exists(__DIR__.'/../storage/framework/maintenance.php')) { require __DIR__.'/../storage/framework/maintenance.php'; }オートロード
ここから本題になります。オートロードに関しては、galluさんのnoteが非常に参考になりました。
それでは、以下の一行の詳細をみていきたいと思います。public/index.phprequire __DIR__.'/../vendor/autoload.php';まず、読み込んでいるファイルをみにいくと、以下のようになっています。
また、違うファイルを読み込んだ後に、クラスメソッドのgetLoader()
を実行しています。vendor/autoload.php<?php require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a::getLoader();
getLoader()
は、以下のようになっています。長いのでいくつかに分けてみていきます。vendor/composer/autoload_real.php<?php // autoload_real.php @generated by Composer class ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a { private static $loader; /** * @return \Composer\Autoload\ClassLoader */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file); } return $loader; } }classLoaderのインスタンス化
まず、
self::loader
はprivate static $loader;
と宣言しただけなので、null
となります。そのため、はじめのif
文はスルーします。
その後、spl_autoload_register()
でオートロードの設定をしています。
spl_autoload_register()
は、配列を与えると、クラスメソッドを登録できます。
ここでは、loadClassLoader()
をオートロードしています。すなわち、今後使おうとしたクラスが宣言されてなかったら、loadClassLoader()
が実行されます。オートロードの設定後、新しいインスタンスを生成して、オートロードの設定を解除しています。
vendor/composer/autoload_real.phpif (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a', 'loadClassLoader'));
loadClassLoader
をみてみると、以下のようになっています。
引数には、クラスの読み込みでエラーがあったときのクラス名が入っています。
読み込めなかったのがComposer\Autoload\ClassLoader
だったら、ファイルを読み込んでください、となります。vendor/composer/autoload_real.phppublic static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } }
ClassLoader
をみてみると、クラスが宣言されています。ここではrequire
しているだけなので、今すぐ何かが実行されるわけではないので、クラスを読み込んでるんだな、というくらいの理解に留めます。vendor/composer/ClassLoader.phpclass ClassLoader { // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); ...
$loader
の初期化それでは、
autoload_real.php
の続きをみていきます。vendor/composer/autoload_real.php$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } }まずは1行目をみていきます。
PHP_VERSION_ID
は、PHPのバージョンが定数になっており、50600はPHP5.6を意味します。今回はPHP7.3のためtrue
となります。
defined
は引数の定数が宣言済みかどうかを検証します。
HHVM_VERSION
は実行環境がHHVMの場合に定義される定数で、通常はHHVMを使用していないので、定義されていません。
zend_loader_file_encoded()
はZend Guardを使用している場合に、定義される関数のようです。vendor/composer/autoload_real.php$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());まとめると、以下のようになっているので、
$useStaticLoader
はtrue
が入ります。true && true && (true || false)となると、実行されるのは以下の部分だけとなります。ファイルを読み込んで、クラスメソッドを実行しています。
call_user_func()
は引数で指定したコールバックを実行します。
今回の場合、普通に実行する場合と何が違うんだと思って、call_user_func()
を使わないで実行してみると、エラーになりました。このあたりは理解できていません。。。vendor/composer/autoload_real.phpif ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::getInitializer($loader)); }読み込んでいるファイルは以下の通りです。クラスの宣言になります。中身は非常に長いですが、クラスプロパティに配列を代入しています。
vendor/composer/autoload_static.phpnamespace Composer\Autoload; class ComposerStaticInitbee6542d79f53acb601ba3c7d134558a { public static $files = array ( 'ec07570ca5a812141189b1fa81503674' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php', ...では、クラスメソッドを見ていきたいと思います。
\Closure:bind()
の中の無名関数の中で、プロパティが代入されています。
右辺の値は長くて分かりにくいですが、上で示したような配列が代入されたクラスプロパティとなります。/vendor/composer/autoload_static.phppublic static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$prefixesPsr0; $loader->classMap = ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$classMap; }, null, ClassLoader::class); }問題となるのが左辺で、
$loader
にはClassLoader
のインスタンスが入っています。vendor/composer/autoload_real.phpself::$loader = $loader = new \Composer\Autoload\ClassLoader();
ClassLoader
のプロパティをみてみると、以下のように代入しているプロパティは全てprivate
となっています。なぜComposerStaticInitbee65...
クラスの中で、ClassLoader
のprivate
プロパティに代入できるんだろうか、となると思うのですが、それを解決するのが\Clousure::bind()
となります。vendor/composer/ClassLoader.php// PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); // PSR-0 private $prefixesPsr0 = array(); private $classMap = array();
\Closure::bind()
は簡単にまとめると、関数を好きなスコープ内で実行することができます。これに関しては、公式ドキュメントだけだと分かりにくいと思いますので、galluさんのnoteも参照すると非常にいいと思います。
今回の場合は、bind
の引数の無名関数を、第三引数のClassLoader::class
のスコープにあるとして、実行しています。そのためprivate
なプロパティも代入できています。オートロードの設定
1行だけですが、詳しくみていきます。
vendor/composer/autoload_real.php$loader->register(true);
register
メソッドは以下のようになっています。
$this
はClassLoader
のインスタンスが入っています。ここではClassLoader
のloadClass
メソッドを、オートロードの設定の対象としています。vendor/composer/ClassLoader.phppublic function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); }
loadClass()
メソッドをみていきます。if
文の条件式でfindFile()
メソッドが呼ばれています。もしファイルが見つかれば、includeFile()
を実行してファイルを読み込んでいます。これが先ほどの一時的なものとは異なり、通常のオートロードになります。
今後はクラスが使用されると、まずこのメソッドが実行されることになります。vendor/composer/ClassLoader.phppublic function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } }findFile()
findFile()
メソッドは以下の通りです。ファイルを探して、そのファイルを返しています。
引数の$class
にはクラスの名前(名前空間+クラス名)が入ります。vendor/composer/ClassLoader.phppublic function findFile($class) { if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }findFile 前半部分
まず、はじめの
if
文をみていきます。$this->classMap
には、vendor/composer/autoload_static.php
で代入した値が入っています。もしあらかじめ登録しておいたクラスであれば、指定したファイルを返します。vendor/composer/ClassLoader.phpif (isset($this->classMap[$class])) { return $this->classMap[$class]; }
$this->classMap
の例をみていきます。いくつかあるのですが、馴染みのありそうなのものをピックアップしました。Illuminate
が何故vendor/laravel/framework/src/Illuminate
を指すのか今まで分かっていませんでしたが、ここでようやく分かりました。vendor/composer/autoload_static.phppublic static $classMap = array ( 'Illuminate\\Http\\Request' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Http/Request.php', 'Illuminate\\Support\\Facades\\Auth' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Facades/Auth.php', 'Illuminate\\Support\\Str' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/Str.php',
findFile()
の、次のif
文をみていきます。
$this->classMapAuthoritative
は、基本的にはfalse
となります。
$this->missingClasses[$class]
は、以前にもクラスが見つからなかった場合のみ、true
が代入されています。
ここは、$this->classMap
に登録されていない場合の検索が無効であったり、既に見つかってないことが分かっている場合にfalse
を返します。vendor/composer/ClassLoader.phpif ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; }念のため、
classMapAuthoritative
をみてみると、まず初期値はfalse
となっています。
setClassMapAuthoritative()
を使用してtrue
に設定すると、上述のif
文の条件がtrue
になり、findFile()
がfalse
を返すようになります。すなわち、$this->classMap
に登録されていないクラスは、検索が無効になります。
setClassMapAuthoritative()
は、検索してみても見つからなかったので、$this->classMapAuthoritative
は基本的にはfalse
と考えていいと思います。vendor/composer/ClassLoader.phpprivate $classMapAuthoritative = false; ... /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; }
findFile()
の、次のif
文をみていきます。$this->apcuPrefix
は調べてみても理解できませんでした。キャッシュドライバにAPCを利用している場合に値がsetされている、と思っていますが定かではありません。今回は、null
が入っていたので、この文はスキップします。vendor/composer/ClassLoader.phpif (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } }findFileWithExtension()
findFile()
の次の1行になります。インスタンスメソッドがでてきたので見ていきます。vendor/composer/ClassLoader.php$file = $this->findFileWithExtension($class, '.php');引数の
$class
にはクラス名、$ext
には拡張子の.php
という文字列が入っています。非常に長いので、ここも少しずつみていきます。vendor/composer/ClassLoader.phpprivate function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; }PSR-4 lookup
はじめの1行をみていきます。
strtr()
は、文字列を置換する組み込み関数になります。
クラス名は、各階層がバックスラッシュ\
で区切られているので、それをDIRECTORY_SEPARATOR
(自分の環境ではスラッシュ/
)に置換しています。その後、拡張子と結合することでファイルのパスを作成しています。vendor/composer/ClassLoader.php// PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;次の
if
文ですが、まず$first
にはクラス名の1文字目を代入しています。
その文字が$this->prefixLengthsPsr4
に代入されていた場合に処理が走ります。
ちなみに、var_dump
で確認したところ、Symfony\Component\Translation\TranslatorInterface
が呼ばれているようですので、具体例として採用して見ていきたいと思います。vendor/composer/ClassLoader.php$first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { ... }まず
$this->prefixLengthsPsr4
はvendor/composer/autoload_static.php
で既に代入されており、以下のような配列になっています。
Symfony\Component\Translation\TranslatorInterface
の1文字目はS
なので、S
がキーとなる値はあるかどうか確認してみます。以下の通り、S
がキーとなる配列が存在しているので、上述のif
文はtrue
となります。vendor/composer/autoload_static.phppublic static $prefixLengthsPsr4 = array ( 'v' => array ( 'voku\\' => 5, ), ... 'S' => array ( 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Php70\\' => 23, ... ),続きをみていきます。まず
$subPath
に値を代入しています。
strrpos
は、文字列中に、指定した文字が最後に現れる場所を返します。
今回の場合、Symfony\Component\Translation\TranslatorInterface
で、\
が最後に現れるのは、29文字目となるので、29を返します。
したがって条件式はfalse !== 29
となり、while
文は実行されます。vendor/composer/ClassLoader.php$subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { ... }
while
文の中身をみていきます。
substr
は、第一引数の文字列の、第二引数から第三引数までの部分を返します。今回はSymfony\Component\Translation\TranslatorInterface
からSymfony\Component\Translation
を取り出します。
$search
では末尾に\
を追加して、$this->prefixDirsPsr4
にあるかどうか確認しています。vendor/composer/ClassLoader.phpwhile (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } }ディレクトリは登録されているので、
true
を返すことが分かります。vendor/composer/autoload_static.phppublic static $prefixDirsPsr4 = array ( ... 'Symfony\\Component\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation', ), ... );
if
文の中身をみていきます。
$logicalPathPsr4
の中身はSymfony/Component/Translation/TranslatorInterface.php
となっており、
substr($logicalPathPsr4, $lastPos + 1)
は$lastPos + 1
の場所から後ろ全部の文字列を取り出しますので、TranslatorInterface.php
を返します。
したがって、$pathEnd
には/TranslatorInterface.php
が入ります。
$this->prefixDirsPsr4[$search]
は複数のディレクトリが入っている場合もあるので、foreach
で各ディレクトリごとにファイルがあるかどうかを確認しています。
ファイルが見つかれば、そのファイルを返します。
ちなみに、vendor/composer/../symfony/translation/TranslatorInterface.php
は見つからないので、次に進みます。vendor/composer/ClassLoader.phpif (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } }PSR-4 fallback dirs
findFileWithExtension()
の次の文になりますが、$this->fallbackDirsPsr4
は設定されていないので、処理は行われません。vendor/composer/ClassLoader.php// PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } }PSR-0 lookup
$pos
は先ほどみたとおり29
となるので、条件式はtrue
となります。
ここでのstrtr()
は文字列中に_
が含まれる場合は/
に変換しています。substr($logicalPathPsr4, $pos + 1)
にはTranslatorInterface.php
が入っているので、特に変換することなく文字列を結合しています。
結果、$logcalPathPsr0
にはSymfony/Component/Translation/TranslatorInterface.php
が代入されます。vendor/composer/ClassLoader.php// PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { ... }その後、
$this->prefixesPsr0[$first]
に登録されているかどうか確認しますが、登録されていないので処理は行われません。vendor/composer/ClassLoader.phpif (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } }ちなみに、中身は以下の通りです。
vendor/composer/autoload_static.phppublic static $prefixesPsr0 = array ( 'M' => array ( 'Mockery' => array ( 0 => __DIR__ . '/..' . '/mockery/mockery/library', ), ), 'H' => array ( 'Highlight\\' => array ( 0 => __DIR__ . '/..' . '/scrivo/highlight.php', ), 'HighlightUtilities\\' => array ( 0 => __DIR__ . '/..' . '/scrivo/highlight.php', ), ), );PSR-0 fallback dirs
$this->fallbackDirsPsr0
は登録されていないので、処理は行われません。vendor/composer/ClassLoader.php// PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } }PSR-0 include paths
$this->useIncludePath
の初期値はfalse
となります。特に設定しなければfalse
のため、ここも処理は行われません。どうやら、Symfony\Component\Translation\TranslatorInterface
のファイルは見つからないので、false
を返すようです。vendor/composer/ClassLoader.php// PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; }findFile 後半部分
ようやく
findFile()
に戻ってこれました。
$file
には$loader->classMap
に登録されているものはファイルのパスが返され、Symfony\Component\Translation\TranslatorInterface
はfalse
が返されています。
はじめのif
文ですが、$file
にファイルのパスが返ってきていても、右辺がfalse
となるので、条件式はfalse
となります。
次の$this->apcuPrefix
もnull
となります。
次のif
文で、Symfony\Component\Translation\TranslatorInterface
はみつからなかったので、missingClasses
に代入して、次からは検索する前にfalse
を返すようにしています。
最後に、$file
を返しています。vendor/composer/ClassLoader.phppublic function findFile($class) { ... $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }includeFile
ようやく
loadClass()
メソッドに戻ってきました。$this->findFile($class)
はファイルのパス or falseが格納されています。つまり、ファイルのパスが見つかった場合はincludeFile()
を実行し、見つからなかった場合はなにもしません。
オートロードでこのような関数になっているため、クラスが使用されると、このloadClassa()
が実行され、ファイルのパスが見つかれば読み込んで、見つからない場合はエラーとなります。vendor/composer/ClassLoader.phppublic function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } }
includeFile()
は以下の通りです。ファイルを読み込んでいるだけです。vendor/composer/ClassLoader.phpfunction includeFile($file) { include $file; }組み込み関数の読み込み
長かった
$loader->register(true);
も終わり、autoload_real.php
に戻ってきました。
$useStaticLoader
はtrue
なので、$includeFiles
に値が代入されます。
その値をforeach
でループして、composerRequire...()
という関数を実行しています。
最後に$loader
を返して終わりです。vendor/composer/autoload_real.phpif ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitbee6542d79f53acb601ba3c7d134558a::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file); } return $loader; }
$files
プロパティは以下の通りです。数字の羅列のキーに対して、ファイルのパスが値となっています。vendor/composer/autoload_static.phpclass ComposerStaticInitbee6542d79f53acb601ba3c7d134558a { public static $files = array ( 'ec07570ca5a812141189b1fa81503674' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php', 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', ...
composerRequire...()
という関数は以下のようになっています。
$GLOBALS['__composer_autoload_files']
は、上述の数字の羅列をキーとする配列となっており、まだファイルを読み込んでいない場合はファイルを読み込んでtrue
を代入しています。vendor/composer/autoload_real.phpfunction composerRequirebee6542d79f53acb601ba3c7d134558a($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } }例として、以下のファイルをみてみたいと思います。
vendor/composer/autoload_static.php'f0906e6318348a765ffb6eb24e0d0938' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Foundation/helpers.php',ファイルをみてみると、多くの関数が定義されています。他のファイルも同様の構造となっており、Laravelで使える関数が定義されています。
なかでも利用頻度の高そうなview()
をみてみたいと思います。ここで定義しているから、コントローラからreturn view();
とできることが分かります。
処理の内容はサービスコンテナの理解が必要で、とても難しいので、また別の機会に。。。vendor/laravel/framewordk/src/Illuminate/Foundation/helpers.php<?php use Illuminate\Contracts\View\Factory as ViewFactory; ... if (! function_exists('view')) { /** * Get the evaluated view contents for the given view. * * @param string|null $view * @param \Illuminate\Contracts\Support\Arrayable|array $data * @param array $mergeData * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory */ function view($view = null, $data = [], $mergeData = []) { $factory = app(ViewFactory::class); if (func_num_args() === 0) { return $factory; } return $factory->make($view, $data, $mergeData); } }
return $loader;
の後
getLoader()
が$loader
を返すところまでみてきたので、autoload.php
に戻ってきました。
ここでは、$loader
をそのままreturn
しています。vendor/autoload.php<?php require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitbee6542d79f53acb601ba3c7d134558a::getLoader();
$loader
を返された先のindex.php
ですが、特に$loader
の値を取得することはなく、autoload.php
のファイル読み込みで終わっています。非常に長かったですが、今回は
これで終わりです。public/index.phprequire __DIR__.'/../vendor/autoload.php';まとめ
require __DIR__.'/../vendor/autoload.php';
の1行で、クラスのオートロード設定、組み込み関数の宣言などを実行していることが分かりました。
また、フレームワークの中身を調べていくことで、自分が知らないPHPの組み込み関数や、いいコードの書き方など、非常に勉強になりました。
次の1行の$app = require_once __DIR__.'/../bootstrap/app.php';
も非常に難しいので、読み解いていきたいと思います。参考サイト
- 投稿日:2020-10-13T12:46:46+09:00
LambdaでLaravelを動作させようと思ったけど、少しコケたのでメモしとく
私はPHPerなのですが、最近、仕事関係でLambdaの話がちょくちょく出てきたので、ちょっと勉強がてら調査することに。
今更なのですが Lambda でPHPを動かせるようになっていたということに驚いた。
で、調べてみるとどうやらbrefというライブラリを使えば、簡単にLambda上でLaravelも動かせるらしい。
Bref - Serverless PHP made simpleわざわざ Lambda で PHPを使う必要があるのかと思うかもだけど、一緒にお仕事しているチームの構成を考えるとメンテナンスも含めてLaravelが使えるというのはとても助かるのです。
で、さくっと試してみようと思ったところデプロイで躓いちゃったので、メモ残しておきます。
環境
- Windows 10 Home
- Docker Toolbox
- PHP7.4
- Laravel 8
IAM ユーザーを作成する
Lambda を利用するにあたり必要な IAM ユーザーを作成。
Creating AWS access keys - Bref
とりあえず試したいだけだから、まんまガイドに従って作成。
簡単、簡単。serverless framwork のインストール
後述しますが、本当は docker で serverless が動作する環境を作ろうと思っていたのですが、なぜかメモリ不足というエラーのため動作せず。。。
なので、Windows に直接 serverless をインストール。
Serverless Getting Started Guide
docker で環境作りに悩んでいたのが馬鹿みたいに、サクッと環境ができた。
credential の設定もガイドに従って設定。私、この環境を作るまでChocolateyというソフトを存じ上げませんでした。
いや~、ダサいというイメージの Windows ですが、少しずつクールになってきていると思っているのは私だけ?wLaravel 環境を docker で作る
毎回 docker 自分で作るの面倒だなぁと思っていたら、多くのLGTMがついてた下記で環境作り。
最強のLaravel開発環境をDockerを使って構築する【新編集版】 - Qiita
いや~、ありがたい。
bref をインストール
公式ドキュメント通り。
Serverless Laravel applications - Bref
ガイドに従って、
.env
の設定も行いましたよ。.env#LOG_CHANNEL=stack LOG_CHANNEL=stderr #SESSION_DRIVER=file SESSION_DRIVER=array VIEW_COMPILED_PATH=/tmp/storage/framework/views楽ですね~
いざ deploy したら、動かなかった。。。
ここまで、ガイドに従うだけなので、超簡単に環境構築できていました。
serverless deploy
でデプロイもすんなり成功!が、、、エンドポイントにアクセスしたらエラー発生。。。
Exception The /var/task/bootstrap/cache directory must be present and writable. ---- /var/task/vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php ---- return []; } return json_decode(file_get_contents( $this->basePath.'/composer.json' ), true)['extra']['laravel']['dont-discover'] ?? []; } /** * Write the given manifest array to disk. * * @param array $manifest * @return void * * @throws \Exception */ protected function write(array $manifest) { if (! is_writable($dirname = dirname($this->manifestPath))) { throw new Exception("The {$dirname} directory must be present and writable."); } $this->files->replace( $this->manifestPath, '<?php return '.var_export($manifest, true).';' ); } } ---- Arguments "The /var/task/bootstrap/cache directory must be present and writable."で結論からいうと、エラーの通り
/var/task/bootstrap/cache
に書き込もうとしてエラーが発生しているので、これを直す。
Lambda で書き込み権限あるのは、/tmp
配下だけらしいので、以下を.env
に追加。.envAPP_SERVICES_CACHE=/tmp/services.php APP_PACKAGES_CACHE=/tmp/packages.php APP_CONFIG_CACHE=/tmp/config.php APP_ROUTES_CACHE=/tmp/routes.php APP_EVENTS_CACHE=/tmp/events.phpで、再度デプロイして、アクセスすると200が返ってきましたよ~
上記の設定をする前にググってたら、$app->useStoragePath()
を設定すると良いよとか書いてあったので、試したりしたのですがうまくいかず、上記の記述を追加するまでにまぁまぁな時間がかかりました。。というわけで、どなたか同じ状況になったときの参考にしてもらえれば幸いです。
(番外)dockerにserverless環境を作ったけど、deploy できなかった。。
前述の docker 環境に以下のような serverless 用のコンポーネントを作った。
docker-compose.ymlserverless: build: context: . dockerfile: ./infra/docker/serverless/Dockerfile args: - AWS_ACCESS_KEY_ID=<key> - AWS_SECRET_ACCESS_KEY=<secret> tty: true stdin_open: true image: serverless working_dir: /app volumes: - ./backend:/app container_name: serverless./infra/docker/serverless/DockerfileFROM python:3.7-alpine ARG AWS_ACCESS_KEY_ID ARG AWS_SECRET_ACCESS_KEY ENV NODE_PATH /usr/lib/node_modules/ # install nodejs RUN apk update \ && apk add --no-cache nodejs npm # install aws-cli RUN pip install awscli # install serverless framework RUN npm install -g serverless serverless-plugin-existing-s3 # set aws key RUN sls config credentials --provider aws --key $AWS_ACCESS_KEY_ID --secret $AWS_SECRET_ACCESS_KEY # change work directory RUN mkdir -p /app WORKDIR /appほんでもって、docker内で
serverless deploy
を実行したところ、なぜか以下のエラー。consoleServerless: Setting up AWS... root@02e9b3d52dbe:/work/backend# serverless deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Error -------------------------------------------------- Error: ENOMEM: not enough memory, open '/work/backend/vendor/mockery/mockery/library/Mockery/Matcher/NoArgs.php' For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable. Get Support -------------------------------------------- Docs: docs.serverless.com Bugs: github.com/serverless/serverless/issues Issues: forum.serverless.com Your Environment Information --------------------------- Operating System: linux Node Version: 12.19.0 Framework Version: 2.5.0 Plugin Version: 4.0.4 SDK Version: 2.3.2 Components Version: 3.2.1メモリは十分にあると思うのに理由がわからない。
散々悩んだあげく、諦めたのさ~
- 投稿日:2020-10-13T10:34:27+09:00
オブジェクト指向ベストプラクティス(個人学習用)
初めに
ズブズブの素人の私ががクラスをを設計できる様に学習を進めていきます。
オブジェクト思考を理解しながら、独立性の高いクラスを構築を目指す。
個人学習目的なのであまり参考にはならない可能性があります。目指すクラス設計とは
- 直感的に理解しやすいもの
- メンテナンスが少ない設計また、変更点があっても他のクラスに影響がない設計
- 凝集度は高く
- 結合度は低く
このことから依存関係が少ないクラス設計が望ましいと考えられます。1つのクラスをメンテナンスをすると他のクラスに影響が及ぼしていてはメンテナンスの困ってしまう訳です。
スパゲッティコードを防げ!!→キーワードは「カプセル化」と「多様性」classの中身を隠蔽化する
例えばお寿司屋さんとお客様がいるとします。
ここではお寿司屋さんの対象がclassとし、お客さんがメインルーティンとします。
お客さんは寿司ネタのオーダーをすると、寿司屋の大将がお寿司を握ってくれる(method)お寿司を提供してくれます。
お客さんはオーダーさえすれば、後は握り終えてお寿司の提供を待つだけです。
この時、お客さんはこの「ネタは握り方をこうしてくれ」、「酢飯の酢の割合はこの程度にしてくれ」とオーダーされても大将は受け付けてくれません。それは大将の独自の経験や技術があるからです。その為、お客さんのオーダーは聞きますが、作成方法などは要求は聞きません。ここまでで振り返ると
お寿司をオーダーする→publicメソッド
握り方 →privateメソッド
酢の割合 →privateメソッド
という風に情報や振る舞いを隠蔽かしているのです。ここではアクセスして欲しくないものを隠蔽していますが、これらを一つのclassのまとめたものがカプセル化です。
カプセル化がうまいとコードの見通しがよくなる
実際の例をみてみると見通しが良い例 $sushiya = new Sushiya(); $sushiya = make_sushi('Tuna');見通しが悪い例 $sushiya = new Sushiya(); $sushiya = createSumeshi(); $sushiya = cutFish(); $sushiya = holdSushi();この様に目的に着目しておらず、手段に重点が置かれている。これではコードの肥大化をうむ。また今回は単純な内容ですが、これでは何を目的にしているのかがわかりにくいです。その点良い例では一目で目的がわかります。これが後者が隠蔽化ができていないことが原因となります。これをアクセス修飾子を調整することで理想的なカプセル化ができるはずです。
classの凝縮度
classがclassに与えられた役割にどれだけ集中しているかを示す尺度を凝集度という。
凝集度が高ければclass間の依存関係は低くなります。
class名と関連するmethodのmissmachをなくそうということです。
例えば、MailSendクラスがあり、そこにsemd(),checkMailAddress(),resaizeImage()があったとします。
この場合send()はメール送信、checkMailAddress()はバリデーション機能,resaizeImage()は画像をリサイズする機能となっておりMailSenderクラスに本質的にはcheckMailAddress(),resaizeImage()は関連しているとはいえません。
それならばそれに関連するImageCoverクラスやValidaterクラスを作成し、そこに上記のメソッドを格納して上げる方が、役割分担が明確でクラスでのメソッドやプロパティの関連性も明確になります。凝集度が高いとメリットがたくさんる
例えばNetRadioクラスがありそこにプロパティとして現在時刻、再生中のチャンネル、文字色、背景色 メソッドとして再生する、時刻表示するがあったとするとここでクラスに関連し本質は「ラジオ放送を聞くということ」なので上記と一緒でクラスの切り分けがを行うことが良いでしょう。
切り分けを行い凝縮度を高めたいきます。そうすると今度はMyNetRaidoAppと行ったクラスを作成しカスタマイズができます。
- MyNetRaidoAppクラスの機能更新がしやすくなる。また時計機能がを削除しても他の機能やクラスに影響がない
- MyNetRaidoApp以外のクラスにも機能を移植できる。
- NetRaido ,Clock, DesignRTmplateクラスは独立しているので1つのクラスに対する変更があっても影響がない。
この様にクラスの凝縮度を高めると保守性が高まりますが、過度にクラスを細分化しすぎるとプログラムファイルを探すにの時間がかかってしまったりするので、ある程度の匙加減は必要です。
classの結合度
クラス間の結合度は低い方が良いです。結合度が高ければ、1つのクラスに何か修正を行った場合関連するクラスに影響を及ぼすからです。
結合度が高くクラス間の依存関係が強い場合
クラスの関連性が循環しているパターン
終わりに
上記のことを意識してクラス設計を行っていきます。
また今後もアップデートしていきます。
- 投稿日:2020-10-13T10:26:10+09:00
Laravel8 1対1 DBのリレーションを定義しよう
目的
- LaravelにおけるDBの1対1のリレーション定義方法をまとめる
実施環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2 GHz クアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする Laravel バージョン 8.6.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 前提条件
- 実施環境に記載されている環境と近い環境が構築されていること。
前提情報
下記コマンドを実行してrelationアプリを作成した。
$ laravel new relation --authAuth認証で使用されるusersテーブルのIDとリンクするuser_idカラムを有するphone_numbersテーブルがあるとする。両テーブルのカラム情報を下記に記載する。
usersテーブル
Field Type Null Key Default Extra id bigint unsigned NO PRI NULL auto_increment name varchar(255) NO NULL 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 phonesテーブル
Field Type Null Key Default Extra id bigint unsigned NO PRI NULL auto_increment user_id bigint unsigned NO NULL number varchar(255) NO NULL created_at timestamp YES NULL updated_at timestamp YES NULL 両テーブルのカラムに注目するとわかるようにusersテーブルとphone_numbersテーブルは1対1の関係になっている。(usersテーブルのidとphone_numbersテーブルのuser_id)
前述した1対1のリレーションを定義する。
概要
- usersテーブルからphonesテーブルに対するモデルファイルへの定義記載
- phonesテーブルからusersテーブルに対するモデルファイルへの定義記載
詳細
usersテーブルからphonesテーブルに対するモデルファイルへの定義記載
アプリ名ディレクトリで下記コマンドを実行してUserモデルファイルを開く。
$ vi app/Models/User.php下記のようにリレーションを定義する。
アプリ名ディレクトリ/app/Models/User.php<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; // 下記を追記 /** * ユーザの電話番号を取得 * * @return void */ public function phone() { return $this->hasOne('App\Models\Phone'); } // 上記までを追記 }アプリ名ディレクトリで下記コマンドを実行してtinkerを起動する。
$ php artisan tinkertinkerで下記を実行してnull以外が帰ってくることを確認する。(usersテーブルとphonesテーブルにそれぞれデータが格納されているものとする。スペルミスがないのにnullが帰ってきてしまうときはtinkerを再起動する。)
use App\Models\User; User::find(1)->phone;phonesテーブルからusersテーブルに対するモデルファイルへの定義記載
アプリ名ディレクトリで下記コマンドを実行してUserモデルファイルを開く。
$ vi app/Models/Phone.php下記のようにリレーションを定義する。
アプリ名ディレクトリ/app/Models/Phone.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Phone extends Model { use HasFactory; // 下記を追記 public function user() { return $this->belongsTo('App\Models\user'); } // 上記までを追記 }アプリ名ディレクトリで下記コマンドを実行してtinkerを起動する。
$ php artisan tinkertinkerで下記を実行してnull以外が帰ってくることを確認する。(usersテーブルとphonesテーブルにそれぞれデータが格納されているものとする。スペルミスがないのにnullが帰ってきてしまうときはtinkerを再起動する。)
use App\Models\Phone; User::find(1)->user;超簡単なまとめ
- 1対1のリレーションはメインテーブル→サブテーブルのときはhasOne(サブテーブルに紐づくモデル名)を使用し、サブテーブル→メインテーブルのときはbelongsTo(メインテーブルに紐づくモデル名)と記載する。
参考文献
- 投稿日:2020-10-13T06:56:09+09:00
Moodle 3.9 マニュアル - コース設定
教師またはコース設定の更新ケイパビリティを持つ他のユーザーは、[管理]> [コース管理]> [設定の編集]でコース設定を変更できます。
内容
1 一般
1.1 コースのフルネーム
1.2 略称
1.3 コースカテゴリ
1.4 コース開始日
1.5 コース終了日
1.6 セクション数から終了日を計算する
1.7 コースの可視性
1.8 コースID番号
2 説明
2.1 コースの概要
2.2 コース画像
3 コース形式
3.1 フォーマット
3.2 非表示セクション
3.3 コースレイアウト
4 外観
4.1 テーマの強制
4.2 強制言語
4.3 発表数
4.4 成績表を生徒に表示する
4.5 アクティビティレポートを表示する
5 ファイルとアップロード
5.1 アップロードサイズ
6 完了追跡
7 グループ
7.1 グループモード
7.2 強制
7.3 デフォルトのグループ化
8 役割の名前変更
9 タグ
10 コースのカスタムフィールド
11 サイト管理設定
12 教師がコース設定を編集できないようにする1 一般
1.1 コースのフルネーム
これがコースの名前です。フロントページ、ダッシュボード、レポートのコースリストにリンクとして表示されます。コースを表示するときに、ブラウザのタイトルバーでも使用されます。
ケイパビリティ moodle/course:changefullname は、ユーザーがコースのフルネームを編集できるかどうかを制御します。
1.2 shortname
多くの教育機関では、BP102やCOMMSなどのコースを簡単に参照できます。コースにそのような名前がまだない場合でも、ここで作成してください。ナビゲーションブロックなど、長い名前が適切でないいくつかの場所で使用されます。
ケイパビリティ moodle/course:changeshortname は、ユーザーがショートネームフィールドを編集できるかどうかを制御します。
デフォルトでは、コースのフルネームのみがコースのリストに表示されます。ただし、管理者は、必要に応じて、[管理]> [サイト管理]> [外観]> [コース]のチェックボックスをオンにすることで、短い名前を表示することもできます。
1.3 コースカテゴリー
サイト管理者は、教師と生徒がコースを簡単に見つけられるようにコースカテゴリを作成している場合があります。コースカテゴリは、ナビゲーションブロックに反映される場合があります。
ケイパビリティ moodle/course:changecategory は、ユーザーがコースカテゴリを編集できるかどうかを制御します。
1.4 コース開始日
この設定は、ログの表示と毎週の形式のトピックの日付に影響します。
「毎週」のコース形式を使用する場合、開始日はコースの最初のセクションに表示されます。たとえば、7月27日を選択すると、最初のセクションに「7月27日-8月2日」と表示されます(そのセクションでデフォルトの表示が選択されている場合)。
この設定は、ログの表示に影響します。これは、ログアクティビティが表示される最も早い日付になります。
この設定は、「ソーシャル」または「トピック」形式を使用するコースには影響しません。
ヒント:教育機関が毎週のスケジュールで運営されている場合は、月曜日などの週の最初の日にコースの開始日を設定することを検討してください。 「週の最初の日」は、言語のlangconfigファイルによって設定され、英語の言語パックのデフォルトの月曜日とは異なる場合があることに注意してください。
ヒント:通常、コースに実際の開始日がない場合は、日付を昨日に設定し、空き状況の設定を使用してコースを学生に公開します。
ヒント:学生が特定の日時より前にコースに参加できないようにするには、自己登録コースの設定を参照してください。1.5 コース終了日
コース終了日は、コースをユーザーのコースリストに含めるかどうかを決定するために使用されます。終了日が過ぎると、コースはナビゲーションブロック/ドロワーに表示されなくなり、学生のダッシュボードのコース概要に過去として表示されます。
コース終了日は、データが削除されるまでの保存期間の計算にも使用されます。また、カスタムレポートで使用することもできます。コース期間中のアクティビティの報告。
ユーザーは終了日後もコースに参加できます。言い換えれば、日付はアクセスを制限しません。
1.6 セクション数から終了日を計算する
週単位のコースの場合のみ、コースの終了日は、コースの開始日とセクション数に基づいて自動的に計算される場合があります。コースセクション(週)が追加または削除されると、コースの日付が自動的に変更されます。 「セクション数から終了日を計算する」にチェックマークが付いている場合、コース終了日を手動で設定することはできません。
1.7 コースの可視性
ここで、コースを完全に「非表示」にすることができます。管理者、コース作成者、教師、および非表示のコースを表示するケイパビリティを持つその他のユーザーを除いて、コースのリストには表示されません。学生がコースのURLに直接アクセスしようとしても、入力することはできません。
コースの非表示/表示ケイパビリティは、ユーザーがコースを非表示にできるかどうかを制御します。
1.8 コースID番号
ID番号は英数字フィールドです。いくつかの潜在的な用途があります。通常、学生には表示されません。ただし、このコースをコースカタログIDとして外部システムのIDと照合するために使用したり、証明書モジュールで印刷フィールドとして使用したりすることができます。
ケイパビリティ moodle/course:changeidnumber は、ユーザーがID番号を編集できるかどうかを制御します。
2 説明
2.1 コースの概要
概要はコースリストページに表示されます。このフィールドは、コースを検索するときに検索され、コース/サイトの説明ブロックにも表示されます。
ケイパビリティ moodle/course:changesummary は、ユーザーがコース概要を編集できるかどうかを制御します。
2.2 コース画像
画像(および管理者によって許可されている場合は他のファイルタイプ)がコースの概要に添付される場合があります。コース名や概要と同じように、コース外から誰でもアクセスできます。 ケイパビリティ moodle/course:changesummary を持つユーザーのみがコースサマリーファイルをアップロード/変更できます。管理者がこれを許可していない場合、コース概要ファイルをアップロードするためのボックスは表示されないことに注意してください。
デフォルトでは、jpg、gif、およびpngファイルタイプのみがコースサマリーファイルとして許可されます。管理者は、[管理]> [サイト管理]> [外観]> [コース]で許可されるファイルの種類を変更できます。
3 コース形式
3.1 フォーマット
コース形式を見る
3.2 非表示セクション
このオプションを使用すると、コースの非表示セクションを学生にどのように表示するかを決定できます。デフォルトでは、非表示のセクションがどこにあるかを示す小さな領域(折りたたまれた形式、通常は灰色)が表示されますが、非表示のアクティビティやテキストは実際には表示されません。これは、週単位の形式で特に役立ちます。これにより、クラス以外の週が明確になります。または、クイズがある場合は、生徒に見せたくありません。
ヒント:必要に応じて、これらの利用できないアイテムを完全に非表示にすることができるため、学生はコース内のセクションやアクティビティが非表示になっていることすらわかりません。
3.3 コースレイアウト
コースレイアウト設定は、コース全体を1ページに表示するか、複数ページに分割して表示するかを決定します。この設定は現在、トピックと毎週のコアコース形式に適用され、折りたたまれたトピックのコース形式にのみ適用されます。
教師は、ドロップダウンから、使い慣れたスクロール形式で「すべてのセクションを1ページに表示する」か、「ページごとに1つのセクションを表示する」かを選択します。
ページごとに1つのセクションが選択されている場合、コースページは個々のセクションへのリンクのリストに省略されます。個々のセクションが表示されている場合は、セクションの上下のリンクから次のセクションと前のセクションにアクセスできます。
ページごとに1つのセクションを使用するコースのコースページコースレイアウト
ページごとに1つのセクションを使用するコースの個々のセクションコースレイアウト学校のデモサイトコーススポーツの種類にアクセスして、「ページごとに1つのセクションを表示」コースのレイアウトを確認してください。
4 外観
(これらの設定はデフォルトで折りたたまれています。)
4.1 テーマの強制
サイト管理者が教師にコーステーマの設定を許可している場合、このプルダウンメニューが表示され、サイトのテーマのリストが表示されます。教師はこれを使用して、Moodleサイトの他の部分とは異なるコースの外観を選択できます。
4.2 言語を強制する
コースで言語を強制すると、学生が自分の個人プロファイルで別の優先言語を選択した場合でも、このコースのMoodleのインターフェースはこの特定の言語になります。
デフォルトでは、編集教師とマネージャーはケイパビリティ moodle/course:forcelanguage を備えており、生徒の言語を選択して強制することができます。管理者は、コースの教師/マネージャーにこれを行わせたくない場合は、この機能を削除できます。
4.3 発表数
アナウンスフォーラムからの最近のアナウンスの数を最新のアナウンスブロックに表示する必要があります。
コースでアナウンスフォーラムが必要ない場合は、この設定をゼロに設定する必要があります。
4.4 成績表を生徒に表示する
ここで、学生が管理ブロックの成績へのリンクを表示できるようにするかどうかを決定できます。コースで段階的なアクティビティを使用しない場合は、これを無効にすることをお勧めします。成績が使用され、このリンクが無効になっている場合でも、生徒は課題などの実際のアクティビティ自体から自分の成績を確認できます。
4.5 アクティビティレポートを表示する
ここで、生徒にアクティビティレポートを表示するかどうかを決定できます。ただし、そうするとサーバーに負荷がかかるため、デフォルトで設定が無効になっています。
5 ファイルとアップロード
(これらの設定はデフォルトで折りたたまれています。)
5.1 最大アップロードサイズ
ここで、学生がコースにアップロードできるファイルの最大サイズを決定できます。サイト管理者は、教師が選択できるサイズを決定できます。
編集教師または他のユーザーは、ケイパビリティ moodle/course:ignorefilesizelimitsを与えることにより、最大サイズよりも大きいファイルをアップロードできる場合があります
6 完了追跡
(これらの設定はデフォルトで折りたたまれています。)
アクティビティを完了するには、完了追跡を有効にする必要があります。コース完了基準は、アクティビティの設定にあるアクティビティ完了値に基づく場合もあります。
7 グループ
(これらの設定はデフォルトで折りたたまれています。)
7.1 グループモード
ここでは、プルダウンメニューを使用してコースレベルでグループモードを定義できます。 グループなし、個別のグループ、表示可能なグループが選択肢です。選択した設定は、そのコース内で定義されたすべてのアクティビティのデフォルトのグループモードになります。グループ設定は、ユーザーが参加者リストに表示する内容と、アクティビティで対話できるユーザーに影響を与える可能性があります。
7.2 強制
グループモードがコースレベルで「強制」されている場合、この特定のグループモードはそのコースのすべてのアクティビティに適用されます。これにより、特別なグループ設定を持つ可能性のあるすべてのアクティビティが上書きされます。
7.3 デフォルトのグループ化
グループ化(https://docs.moodle.org/39/en/Groupings)が有効になっている場合、コースアクティビティとリソースのデフォルトのグループ化が設定される場合があります。
8 役割の名前変更
(これらの設定はデフォルトで折りたたまれています。)
コースで使用されている役割の名前を変更できます。たとえば、教師の役割の名前を「ファシリテーター」、「チューター」、または「ガイド」に変更したい場合があります。これらの新しい役割名はコース内に表示されます。たとえば、参加者とオーバーライド許可ページ。
サイト管理者が名前を変更したか、新しい役割を追加した可能性があることに注意してください。これらの名前が表示され、教師は名前を変更できます。
「教師」の単語をサイトのすべてのコースで異なるものにする場合、サイト管理者は、[管理]> [サイト管理]> [ユーザー]> [権限]> [役割の定義]で教師と非編集教師の役割を編集し、カスタムフルを変更できます。そこに名前を付けます。
9 タグ
教師はここに、新しいタグまたは公式タグのいずれかのコースタグを追加できます。詳細については、タグの使用を参照してください。
10 コースのカスタムフィールド
管理者がサイト管理/コース/コースカスタムフィールドで有効にした場合、教師が構成できるフィールドがここで利用可能になる場合があります。
11 サイト管理設定
管理者は、[管理]> [サイト管理]> [コース]> [コースのデフォルト設定]でコースのデフォルト設定を設定できます。
サイト上のすべてのコースに最大週数/トピックを設定できます。デフォルト値は52です。
コースカスタムフィールドは、サイト管理/コース/コースカスタムフィールドから追加して、教師がコース設定で構成することができます。
12 教師がコース設定を編集できないようにする
次のフィールドのいずれか/すべて(コースのフルネーム、ショートネーム、ID番号とカテゴリ、概要)は、教師が編集できないようにロックされている場合があります。そうするために:
- [サイト管理]> [ユーザー]> [権限]> [役割の定義]にアクセスします。
- 教師の役割の反対側にある編集アイコンをクリックします。
- moodle/course:changefullname、moodle/course:changeshortname、moodle/course:changeidnumber、moodle/course:changecategory、moodle/course:changesummaryのいずれか/すべてのケイパビリティを許可から未設定に変更します。
- ページ下部の[変更を保存]ボタンをクリックします。
- 投稿日:2020-10-13T01:15:29+09:00
[Codewars] Find The Parity Outlier
概要
Codewarsの問題
Find The Parity Outlier
の回答の復習とベストプラクティスをまとめる個人メモです。問題
You are given an array (which will have a length of at least 3, but could be very large) containing integers. The array is either entirely comprised of odd integers or entirely comprised of even integers except for a single integer N. Write a method that takes the array as an argument and returns this "outlier" N.
examples
[2, 4, 0, 100, 4, 11, 2602, 36] Should return: 11 (the only odd number) [160, 3, 1719, 19, 11, 13, -21] Should return: 160 (the only even number)回答
配列をそれぞれ用意し、奇数と偶数のチェック後リストの大きさが1の方を返す処理です。
function find($integers) { $odd_list = []; $even_list = []; for ($i=0;$i<count($integers);$i++){ if ($integers[$i] % 2 != 0){ array_push($odd_list, $integers[$i]); }else{ array_push($even_list, $integers[$i]); } } return count($odd_list) === 1 ? $odd_list[0] : $even_list[0]; }ベストプラクティス
処理自体はほぼ同じです。
foreach
を使用した方が簡潔に書けるので次から意識していきたいです。function find($a) { $odds = []; $evens = []; foreach ($a as $n) { if ($n % 2) array_push($odds, $n); else array_push($evens, $n); } return count($evens) === 1 ? $evens[0] : $odds[0]; }function find($i) { foreach ($i as $x) $x % 2 == 0 ? $even[] = $x : $odd[] = $x; return $odd < $even ? $odd[0] : $even[0]; }