"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"laravel/sanctum": "^4.0",
"require-dev": {
"nunomaduro/collision": "^8.1",
廃止パッケージの削除
laravelcollective/html などは廃止となったので削除します
"laravelcollective/html": "^6.4",
composer update
composer require spatie/laravel-html
https://readouble.com/laravel/11.x/ja/upgrade.html#updating-dependencies
app/Http/Controllers/Api/JsonResponseTrait.php
<?php
namespace App\Http\Controllers\Api;
trait JsonResponseTrait
{
/**
* JSON_UNESCAPED_UNICODE オプションを使用して JSON レスポンスを返す
*
* @param mixed $data
* @param int $code
* @return \Illuminate\Http\JsonResponse
*/
public function unescapedJsonResponse($data, $code = 200)
{
return response()->json(
$data,
$code,
['Content-Type' => 'application/json;charset=UTF-8', 'Charset' => 'utf-8'],
JSON_UNESCAPED_UNICODE
);
}
}
// trait
use JsonResponseTrait;
public function show(string $id)
{
$event = Event::inActive()->findOrFail($id);
// return response()->json($event); この行を以下に変更 ↓
return $this->unescapedJsonResponse($event);
}
https://github.com/askdkc/breezejp
composer require askdkc/breezejp --dev
php artisan breezejp
langディレクトリも作ってくれた上でその中に以下のファイルが追加されます。
lang
├── ja
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
└── ja.json
また .env の 以下も書き換えてくれます
APP_TIMEZONE=Asia/Tokyo
APP_LOCALE=ja
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=ja_JP
composer require laravel/breeze
php artisan breeze:install
# npm install && npm run dev
composer require laravel/ui
php artisan ui bootstrap --auth
# npm install && npm run dev
デフォルトでは、Laravelはutf8mb4文字セットを使用します。5.7.7リリースより古いバージョンのMySQLまたは10.2.2リリースより古いMariaDBを実行している場合、MySQLがそれらのインデックスを作成するために、マイグレーションによって生成されるデフォルトの文字カラム長を手作業で設定する必要が起きます。
変更ファイル : app\Providers\AppServiceProvider.php:22
public function boot()
{
// ↓ この行を追加
\Illuminate\Support\Facades\Schema::defaultStringLength(191);
}
変更ファイル : .env:8
APP_TIMEZONE=UTC
↓
APP_TIMEZONE=Asia/Tokyo
変更ファイル : .env:5
APP_LOCALE=en
↓
APP_LOCALE=ja
変更ファイル : config/filesystems.php
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
// ===== permissionsを追加 =====
'permissions' => [
'dir' => [
'public' => 0777 ,
'private' => 0777 ,
],
'file' => [
'public' => 0666 ,
'private' => 0666,
],
],
// ===== / permissionsを追加 =====
],
変更ファイルconfig/logging.php
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
// ===== permissionsを追加 =====
'permission' => 0666,
// ===== / permissionsを追加 =====
],
変更ファイル: config/cache.php:18
'default' => env('CACHE_STORE', 'file'),
変更ファイル: config/cache.php
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
// ===== permissionsを追加 =====
'permission' => 0666,
// ===== / permissionsを追加 =====
],
umaskも設定する必要がある場合は次のようにします
設定ファイル : app/Providers/AppServiceProvider.php
public function boot()
{
// umask設定
umask(0);
}
APP_URL=https://your.site.com
合わせてエディタも設定しておきます。(エラー時に鉛筆のアイコンをクリックすると設定したエディターで開くことができます)
IGNITION_EDITOR="vscode"
後 DB 関連も書き換えておきましょう
・Laravelのログを日別でローテーションさせる( +自動削除 )|プログラムメモ
・Laravel でユーザーがログインしたログを取得する|プログラムメモ
・LaravelでSQLのログとスロークエリログを取得する|プログラムメモ
・Laravelで任意のログチャンネルを追加してログを出力する|プログラムメモ
必要であれば以下のようなパッケージをインストールしておきます
# renatomarinho/laravel-page-speed (最終的に生成されるhtmlをminifyする)
composer require renatomarinho/laravel-page-speed
# helper
composer require laravel/helpers
# form ヘルパー(Laravel Collective)
composer require laravelcollective/html
# yaml
composer require symfony/yaml
# Debug Bar
composer require barryvdh/laravel-debugbar
# DB Backup
composer require spatie/laravel-db-snapshots
# Laravel 6 7 で Auth を使う場合
composer require laravel/ui
Laravel で DB のバックアップを簡単にとる (laravel-db-snapshots)|プログラムメモ
TrimStringsはフォーム入力の無駄な前後スペースを取り除きます。
不要ならコメントアウトしましょう app/Http/Kernel.php
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
// OFF \App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
Laravelでエラー画面からワンクリックでソースコードのエラー箇所に移動する|プログラムメモ
composer require inertiajs/inertia-laravel
npm i react react-dom @inertiajs/inertia @inertiajs/inertia-react
npm i -D @vitejs/plugin-react
npm i -D typescript @types/react @types/react-dom
./node_modules/.bin/tsc --init --jsx react
vi tsconfig.json
以下の内容で保存
{
"compilerOptions": {
"target": "esnext",
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"types": ["vite/client"]
}
}
vite.config.js の設定を変更する
vi vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { renameSync } from 'fs';
export default defineConfig({
plugins: [
react(),
{
name: 'move-manifest',
closeBundle() {
// .viteフォルダ内のmanifest.jsonをpublic/buildに移動
const oldPath = resolve(__dirname, 'public/build/.vite/manifest.json');
const newPath = resolve(__dirname, 'public/build/manifest.json');
try {
renameSync(oldPath, newPath);
} catch (error) {
console.error('Failed to move manifest.json:', error);
}
},
},
],
build: {
outDir: 'public/build',
manifest: true,
rollupOptions: {
input: 'resources/ts/app.tsx',
},
assetsDir: 'vite', // アセットディレクトリを`vite`に指定
},
server: {
host: true, // これにより、開発サーバーがネットワーク上でアクセス可能になります
},
});
mv resources/js resources/ts
vi resources/ts/app.tsx
import "./bootstrap";
import "../css/app.css";
import React from "react";
import { createRoot } from "react-dom/client";
import { createInertiaApp } from "@inertiajs/inertia-react";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
createInertiaApp({
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.tsx`,
import.meta.glob("./Pages/**/*.tsx")
),
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />);
},
});
routes/web.php
/inertia というエンドポイントを作成する
Route::get('/inertia', function () {
return view('inertia');
});
まず、コントローラーの中でArtisanファサードをインポートします。
use Illuminate\Support\Facades\Artisan;
public function runCustomCommand()
{
$exitCode = Artisan::call('your:command', [
'name' => 'value',
]);
return response()->json(['message' => 'Custom command executed successfully.', 'exitCode' => $exitCode]);
}
古いLaravelアプリを 最新 Laravelへアップグレードする場合、おすすめの方法は
composer create-project laravel/laravel my_app
自動的に最新バージョンがインストールされます。
また、最新の環境でインストールできないものも教えてくれます。
composer require <パッケージ名>
# ● インストールNG パッケージ
chumper/zipper
# ● 廃止パッケージ
fideloper/proxy
# ● Laravel11で廃止されるパッケージ
laravelcollective/html → spatie/laravel-html
・モデル app/Models
・コントローラー app/Http/Controllers/Admin/ConvertController.php
・ビュー app/resources
・DBマイグレーションファイル app/databases
以下のような str_limit() 関数で「Call to undefined function str_limit()」エラーになることがあります
str_limit($publication->name_zh, 50, '...')
↓
パッケージ laravel/helpers をインストールします
composer require laravel/helpers
protected $dates = ['posted_date'];
↓
protected $casts = [
'posted_date' => 'immutable_datetime'
];
kusanagi ratelimit status <プロファイル名>
kusanagi ratelimit off <プロファイル名>
nginx.conf
こちらを適宜変更します。
limit_req_zone $remote_addr$http_x_forwarded_for zone=one:10m rate=100r/s;
nginx 再起動
nginx -s reload
sudo vi /etc/opt/kusanagi/php-fpm.d/www.conf
こちらを適宜変更します。
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
php-fpm再起動
systemctl status php-fpm.service
systemctl restart php-fpm.service
composer require kreait/firebase-php
composer require kreait/laravel-firebase
php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config
vi tests/Unit/LaravelFirebaseTest.php
'ServiceProvider' => Kreait\Laravel\Firebase\ServiceProvider::class,
# Firebase設定
FIREBASE_CREDENTIALS=firebase.json
ダウンロードしたファイルを firebase.json としてプロジェクトトップへアップロードします。
vi tests/Unit/LaravelFirebaseTest.php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Kreait\Firebase\Exception\Auth\RevokedIdToken;
use InvalidArgumentException;
class LaravelFirebaseTest extends TestCase
{
/**
* @testdox JWTトークン取得
*/
public function test_verify_firebase_auth_jwt()
{
// テストしたいトークン
$token = "ey....................................";
// @see vendor/kreait/laravel-firebase/src/ServiceProvider.php
$auth = app('firebase.auth');
try {
// @see https://github.com/kreait/firebase-php/blob/7.x/src/Firebase/Contract/Auth.php
$parsedToken = $auth->verifyIdToken($token, $checkIfRevoked = true);
} catch (InvalidArgumentException $e) {
return $e->getMessage();
} catch (RevokedIdToken $e) {
return $e->getMessage();
}
dump($parsedToken->claims());
$this->assertSame($parsedToken->claims()->get('name'), "<Firebaseに登録しているユーザー名>");
}
}
php artisan test --testdox tests/Unit/LaravelFirebaseTest.php
https://github.com/kreait/firebase-php/blob/7.x/src/Firebase/Contract/Auth.php
vi app/Http/Middleware/Firebase.php
app/Http/Middleware/Firebase.php
<?php
namespace App\Http\Middleware;
use Kreait\Firebase\Contract\Auth;
use Kreait\Firebase\Exception\Auth\FailedToVerifyToken;
class Firebase
{
private Auth $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function handle($request, \Closure $next)
{
$idTokenString = $request->headers->get('authorization');
$token = trim(str_replace('Bearer', '', $idTokenString));
try {
$verifiedIdToken = $this->auth->verifyIdToken($token);
} catch (FailedToVerifyToken $e) {
echo 'The token is invalid: ' . $e->getMessage();
}
$user_id = $verifiedIdToken->claims()->get('user_id');
$email = $verifiedIdToken->claims()->get('email');
$request->attributes->set('firebase_data', [
'user_id' => $user_id,
'email' => $email,
]);
return $next($request);
}
}
vi app/Http/Kernel.php
app/Http/Kernel.php
protected $middlewareAliases = [
........
// 次の行を追加
'firebase' => \App\Http\Middleware\Firebase::class,
];
vi routes/api.php
Route::get('/ping', function () {
return 'ping ok!!!';
});
まずはトークンなしでアクセスします(ブラウザからでok) http://YOUR-SERVER/api/ping
vi routes/api.php
先程のルートにミドルウェアを追加します
Route::group(['middleware' => 'firebase'], function () {
Route::get('/ping', function () {
return 'ping ok!!!';
});
});
PostmanやVS Code Thunder Client などでアクセスします。
vscode の Intelephense で以下のようなエラーが出る時があります。
$disk = Storage::disk($disk_driver);
$disk->path("tmp"); // ここで path というメソッドがないと怒られる
そのような時は以下のようにしてローカル変数の方を明示しておきましょう
/** @var Storage::disk */
$disk = Storage::disk($disk_driver);
$disk->path("tmp"); // エラーでない
https://github.com/ytake/Laravel-Aspect
https://www.slideshare.net/KenjiroKubota/laravel-aspect
引用 : https://camp.trainocate.co.jp/magazine/whats-aop-programming/
①アドバイス(Advice) | 処理内容を記述したものを「アドバイス」と呼びます。 |
②ジョインポイント(JoinPoint) | アドバイスを注入する場所を「ジョインポイント」と呼びます。 アドバイスを結合(ジョイン)する場所(ポイント)と覚えましょう。 |
③ポイントカット(PointCut) | アドバイスをジョインするポイントにおいて、処理を実行する条件などを指定したい場合もあるでしょう。 その指定方法を「ポイントカット」と呼びます。 |
アドバイスとジョンポイント、ポイントカットの覚え方 アドバイスとジョインポイント、ポイントカットはセットで覚える必要があるので注意しましょう。 覚え方のコツとしては、【ポイントカット】の場合、【アドバイス】を【ジョインポイント】へ注入するというように、ひとつの文章として覚えておくとよいでしょう。 |
|
④アドバイザ(Advisor) | 実際にAOPを実装する際には、アドバイスとポイントカットはセットで記述することになります。 アドバイスとポイントカットの両方をもつモジュールクラスを作成する場合、そのクラスを「アドバイザ」と呼びます。 |
⑤インターセプタ(Interceptor) | Interceptは「割り込み」の意味をもちます。 インターセプタは、本来はAOPの用語ではないため注意しましょう。 ただし、インターセプタの目的は「特定の処理に対して共通処理を注入する」であり、AOPにおけるアドバイザと同じような意味合いで利用されます。 ちなみに、AOPに非対応でもインターセプタをサポートしているフレームワークも多く存在します。 それらにあわせて、アドバイザのクラス名を「~Interceptor」とする場合もありますので、インターセプタの存在自体は覚えておいて損はないでしょう。 |
⑥プロキシとターゲット(Proxy , Target) | プロキシは「アドバイスをもつオブジェクト」、ターゲットは「プロキシされる(アドバイスが注入される)オブジェクト」を指します。 プロキシとターゲットは対になる単語ですので、こちらもセットで覚えておきましょう。 |
resources/views/app.blade.php に記述します
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- ここに記述します -->
<link href="/assets/css/my.css" rel="stylesheet">
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">
<!-- Scripts -->
@routes
@vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
@inertiaHead
</head>
<body class="font-sans antialiased">
@inertia
</body>
</html>
composer create-project --prefer-dist laravel/laravel laravel10-app dev-master
cd laravel10-app
php artisan -V
cat package.json
npm install
npm update
npm i @vitejs/plugin-vue
cat package.json
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue'; // ← この行を追加
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
vue(), // ← この行を追加
],
});
resources/js/app.js を以下のようにします
import './bootstrap';
// 追加
document.getElementById('app').textContent = 'Hello Vite !';
resources/views/welcome.blade.php をごっそり以下のように書き換えます
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div id="app"></div>
</body>
</html>
サーバーを2つ起動します。
npm run dev
mamp などのサーバーを起動するか、 laravel sailを起動します。
(laravel sail を起動する場合は以下の sail インストールを実行後に次のコマンドを実行します)
php artisan serv
composer require laravel/sail --dev
php artisan sail:install
laravel側 ( composer )
composer require inertiajs/inertia-laravel
フロントエンド側 ( node.js/npm )
npm install @inertiajs/inertia @inertiajs/inertia-vue3 --save-dev
routes/web.php に以下を追加
// inertiテスト
Route::get('inertia-sample', function () {
return Inertia::render('InertiaSample', ['message' => 'hello Inertia !!!',]);
});
resources/js/Pages/InertiaSample.vue
<template>
<h1>InertiaSample</h1>
<div>{{ props.message }}</div>
</template>
<script lang="ts" setup>
type Props = {
message: string;
};
const props = defineProps<Props>();
console.log("● props");
console.log(props);
</script>
フロントアプリをビルドするため次のコマンドを実行します
npm run dev
(一度起動すると常駐してファイル変更のたびにビルド(開発用ビルド)します。)
これでアクセスしてみます。
http://localhost/InertiaSample
a タグの代わりに Link コンポーネントを使用します。
<script lang="ts" setup>
import { Link } from "@inertiajs/vue3";
</script>
<template>
<h1>navi</h1>
<Link href="/home">ホームへ移動</Link>
</template>
npm install primevue@^3 --save
npm install primeicons --save
PHP用のいろいろなパッケージがありますが、firebase / php-jwt を使用してみます
https://github.com/firebase/php-jwt
インストール
composer require firebase/php-jwt
composer require guzzlehttp/guzzle
vi tests/Unit/VerifyFirebaseTokenTest.php
tests/Unit/VerifyFirebaseTokenTest.php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Firebase\JWT\JWT as FirebaseJWT;
use Firebase\JWT\Key;
use Illuminate\Support\Facades\Http;
class VerifyFirebaseTokenTest extends TestCase
{
/**
* @testdox JWTトークンが正しい場合 verifyJWT() メソッドは true を返す
*/
public function test_verify_firebase_auth_jwt()
{
$verify_result = $this->verifyJWT("<検証したいjwtをここに貼り付ける>");
$this->assertTrue($verify_result);
}
/**
* Firebase Auth の jwtを検証する(公開鍵はgoogleサーバから取得する)
* @param string $accessToken
* @returns boolean
*/
public function verifyJWT($accessToken)
{
$kid = $this->getKidFromJwtHeader($accessToken);
$publicKey = $this->getPublicKeyFromFirebase('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', $kid);
$key = new Key($publicKey, 'RS256');
$decoded = null;
try {
$decoded = FirebaseJWT::decode($accessToken, $key);
return ($decoded) ? true : false;
} catch (\Throwable $th) {
// throw new Error('error 発生');
return false;
}
}
/**
* jwtからヘッダの中のkidを取得
* @param string $token
* @returns string
*/
public function getKidFromJwtHeader($token)
{
$jwtHeader = $this->getJwtHeader($token);
$obj = json_decode($jwtHeader, true);
return $obj['kid'];
}
/**
* jwtからヘッダを取得
* @param string $token
* @returns string
*/
public function getJwtHeader($token)
{
$tokenHeader = preg_split('/\./', $token)[0];
$jwtHeader = base64_decode($tokenHeader);
return $jwtHeader;
}
/**
* Firebaseのjwt検証用公開鍵を取得する
* @param string $url
* @param string $kid
* @returns string
*/
public function getPublicKeyFromFirebase($url, $kid)
{
$response = Http::get($url);
$body = $response->body();
$publicKeys = json_decode($body, true);
$key = $publicKeys[$kid];
return $key;
}
}
php artisan test --testdox tests/Unit/VerifyFirebaseTokenTest.php
公式の Userモデルを参考に Userモデルにカラム「uid_google」を追加する
php artisan make:migration change_userss_table_add_uid_google --table=users
マイグレーションファイルを以下の内容で保存します
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('uid_google',128)->nullable()->after('id')->comment('UID(Firebase)');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('uid_google');
});
}
};
php artisan migrate
protected $listen = [
// .....
// 追記する
// 追記
\App\Events\TenantDatabaseCreate::class => [
\App\Listeners\TenantDatabaseCreateListner::class,
],
ネームスペースは 先頭の バックスラッシュから記述します ( \App... )
php artisan event:generate
これで次の2ファイルが自動生成されます。
app/Events/TenantDatabaseCreate.php
php artisan make:event TenantDatabaseCreate
php artisan make:listener TenantDatabaseCreateListner --event=TenantDatabaseCreate
以下のような名前のローカルパッケージを使用したいとします
myname/mypackage
localPackage/myname/mypackage
"repositories": [
{
"type": "path",
"url": "./localPackage/myname/mypackage",
"options": {
"symlink": true
}
}
],
"require": {
"myname/mypackage": "@dev",
}
composer install
こちらのサイトから Docker Desktop ダウンロードしてインストールします
https://www.docker.com/get-started/
インストールしたいディレクトリに移動してターミナルから以下のコマンドを実行します
アプリ名 : my-app でインストールします
curl -s "https://laravel.build/my-app" | bash
このコマンドでは
mysql
mailhog
meilisearch
redis
selenium
が自動でインストールされます。
インストールしたいアプリケーションを限定する場合は以下のように明示的に示します
アプリ名 : my-app 、インストールアプリ : mysqlでインストールします
curl -s "https://laravel.build/my-app?with=mysql" | bash
指定できるオプションは以下の通りです
mysql, pgsql, mariadb, redis, memcached, meilisearch, minio, selenium
インストールが完了したら次のコマンドで起動します
cd my-app
./vendor/bin/sail up
.bash_profile
# sail
alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'
トップディレクトリ直下に .env があるのでそれの1番下に以下のように設定します
.env
# ポート変更
APP_PORT=20080 # http://localhost:20080/
FORWARD_DB_PORT=23306 # MySQLのポート
FORWARD_REDIS_PORT=26379 # Redisのポート
ポート番号を変えた場合は、アプリケーションのURLのポート番号も変更します
.env
APP_URL=http://localhost:20080
./vendor/bin/sail artisan
./vendor/bin/sail composer
docker ps
NAMES のところを見てコンテナ名を取得します
docker exec -i -t コンテナ名 bash
./vendor/bin/sail artisan sail:install --devcontainer
以下のように聞かれますので使用しているアプリケーションをカンマで区切って入力します
Which services would you like to install? [mysql]:
[0] mysql
[1] pgsql
[2] mariadb
[3] redis
[4] memcached
[5] meilisearch
[6] minio
[7] mailhog
[8] selenium
> 0,3
mysqalとredisを使用する場合
実行すると ディレクトリ .devcontainer にファイルができます。
一度マシンを再起動して、次はいきなりフォルダーを vscode で開いてみましょう。
apt update; apt -y upgrade
apt-get install vim -y
https://github.com/ryoluo/sail-ssl
composer require ryoluo/sail-ssl --dev
php artisan sail-ssl:install
php artisan sail-ssl:publish
chrome://flags/#allow-insecure-localhost
次の方法で最初からインストールします。
curl -s "https://laravel.build/myapp?with=mysql" > laravel_sail_installer.sh
laravelsail/php81-composer:latest \
↓
laravelsail/php74-composer:latest \
myapp となっているところを全て作成したいアプリ名に変更します
sh laravel_sail_installer.sh
cd <アプリ名>
./vendor/bin/sail up
下記のコードは $post_collection が null の時にエラーとなります。
foreach ($post_collection as $post) {
....
}
こちらのエラーが出る
Invalid argument supplied for foreach()
foreach ( optional($post_collection) as $post) {
....
}
if ($post_collection !== null) {
foreach ($post_collection as $post) {
....
}
}
Laravel Eloquent でDBからデータ取得後に再取得するには次の2つのメソッドがあります。
$newPost = $post->fresh();
$post->refresh();
エラーになってしまう場合は手動で再取得するといいでしょう
$newPost = \App\Post::find($post->id);
withCredentials: true を有効にして post します。
const axiosPost = axios.create({
withCredentials: true
});
axiosPost.put(`http://localhost:8000/api/posts/${params.id}`, data
).then(res => {});
app/Http/Kernel.php
'api' => [
\App\Http\Middleware\EncryptCookies::class, // 追加
\Illuminate\Session\Middleware\StartSession::class, // 追加
\App\Http\Middleware\VerifyCsrfToken::class, // 追加
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
.env
# ● COR設定
SESSION_DOMAIN=localhost
config/cors.php
'allowed_origins_patterns' => ['/(localhost):?[0-9]*/'], // ● localhost:3000 追加
'supports_credentials' => true, // ● trueに変更
config/session.php
'secure' => false,
ターミナルから以下のコマンドを実行して失敗することを確認する
(URL は適宜書き換えてください。以下の例では postsテーブルの ID =2のデータを削除しに行きます)
curl -X DELETE http://localhost:8000/api/posts/2
<title>Page Expired</title>
が返ってくる場合 csrf プロテクションが有効です
わざと csrf エラーを出したい場合は
const axiosPost = axios.create({
withCredentials: true
});
を使わないようにするとエラーが出ます
app/Providers/AppServiceProvider.php
public function boot()
{
// AWSロードバランサー対応
if (request()->isSecure()) {
\URL::forceScheme('https');
}
}
app/Http/Middleware/TrustProxies.php
protected $proxies = '*';
前提条件
フロントエンドとサーバーサイドで別サーバーを立てる場合は同じトップレベルドメインにつ所属している必要があります。
つまりフロントエンドだけ http://127.0.0.1 といった環境では sanctum の認証はうまく動作しません。
token error (The MAC is invalid) となります。
うまくいく例:
サーバーサイド : api.test.com
フロントエンド : local.test.com
Laravel Sanctum には 「1.APIトークン認証」「2. SPA認証(セッション+クッキー)」の2つの認証機能があります。
今回は 2. SPA認証を実装してテストしてみます
composer create-project laravel/laravel my_app
cd my_app
# 以下 sanctum のインストール
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
これにより /api/xxxxx のすべてのURLに認証チェックが入ることになります。
app/Http/Kernel.php:42
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // ● Laravel Sanctum 追加
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
config/cors.php 次の設定を変更します
'paths' => ['api/*', 'sanctum/csrf-cookie', 'api-register', 'api-login', 'api-logout'], // ● 3つのエンドポイントを追加
'allowed_origins_patterns' => ['/localhost:?[0-9]*/'], // ● localhost:3000 追加
'supports_credentials' => true, // ● trueに変更
config/session.php:158 次の設定を確認します
'domain' => env('SESSION_DOMAIN', null),
.envファイルの SESSION_DOMAIN の値を読み取る設定となっているので、 .env の設定を変更します。
サブドメインに対応するために先頭を . にします
例: dev.myhost.com の場合
.env
# config/session.php の SESSION 設定
SESSION_DOMAIN=".myhost.com"
config/sanctum.php:16 の次の設定を確認します
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
これは次のホスト名をファーストパーティーとして認識させます
localhost
localhost:3000
127.0.0.1
127.0.0.1:8000
::1
env('APP_URL')
開発や実際に動作させるサーバー名がこちらのリスト↑ にない場合は追加するか、 .env の5行目の APP_URL を変更します
app/Http/Controllers/ApiAuthController.php を以下の内容で作成
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Models\User;
use \Symfony\Component\HttpFoundation\Response;
class ApiAuthController extends Controller
{
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required'
]);
if ($validator->fails()) {
return response()->json('validation error', Response::HTTP_UNPROCESSABLE_ENTITY);
}
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
return response()->json('User registration completed', Response::HTTP_OK);
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
// ● cookie
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return new JsonResponse(['message' => 'ログインしました']);
}
if ( env('APP_ENV') === 'local' ){
$email = $request->get('email');
$password = $request->get('password');
return response()->json("User Not Found or password don't match. (email:{$email})(password:{$password}) ", Response::HTTP_INTERNAL_SERVER_ERROR);
}
else {
return response()->json("User Not Found or password don't match.", Response::HTTP_INTERNAL_SERVER_ERROR);
}
// ● token
// if (Auth::attempt($credentials)) {
// $user = User::whereEmail($request->email)->first();
// $user->tokens()->delete();
// $token = $user->createToken("login:user{$user->id}")->plainTextToken;
// return response()->json(['token' => $token], Response::HTTP_OK);
// }
// return response()->json("User Not Found or password don't match.", Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
routes/web.php に以下を追加
// (SPAクッキー認証)ユーザー登録 / ログイン / ログアウト
Route::post('/api-register', [\App\Http\Controllers\ApiAuthController::class, 'register']);
Route::post('/api-login' , [\App\Http\Controllers\ApiAuthController::class, 'login']);
Route::post('/api-logout' , [\App\Http\Controllers\ApiAuthController::class, 'logout']);
公開フォルダーに検証用のhtml , js ファイルを置きます。 Vue.js と axios を使用して検証します。
public/test/test-login.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<mycomponent></mycomponent>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>
<script src="./test-api.js"></script>
<script>
new Vue({
el: '#app'
});
</script>
</body>
</html>
public/test/test-api.js
Vue.component('mycomponent', {
data: function () {
return {
user: {},
userState: 'ログインチェック前'
}
},
mounted: function () {
var self = this;
// ログインチェック実行
self.getUser();
},
methods: {
doLogin: function (event) {
var self = this;
const instance = axios.create({
withCredentials: true
})
const formdata = {};
formdata.email = event.target.elements.email.value;
formdata.password = event.target.elements.password.value;
instance.get('https://YOUR-SERVER.COM/sanctum/csrf-cookie/')
.then(function (response) {
instance.post('https://YOUR-SERVER.COM/api-login', formdata)
.then(function (response) {
console.log('● api-login result');
console.log(response);
alert('ログインしました');
// ログインチェック実行
self.getUser();
})
.catch(function (error) {
alert('api-login エラー');
});
});
},
getUser: function () {
var self = this;
this.userState = '問い合わせ中 ...............';
const instance = axios.create({
withCredentials: true
})
setTimeout(() => {
instance.get('https://YOUR-SERVER.COM/api/user/')
.then(function (response) {
console.log('● ログイン中のユーザー情報');
console.log(response.data);
self.user = response.data;
self.userState = `ログイン中 ( ${response.data.name} / ${response.data.email} )`;
})
.catch(function (error) {
self.userState = '未ログイン';
self.user = {};
});
}, 500);
},
doLogout: function (event) {
var self = this;
const instance = axios.create({
withCredentials: true
})
const formdata = {};
instance.post('https://YOUR-SERVER.COM/api-logout', formdata)
.then(function (response) {
alert('ログアウトしました');
// ログインチェック実行
self.getUser();
})
.catch(function (error) {
alert('api-logout エラー');
});
}
},
template: `
<div>
<form action="/api-login/" @submit.prevent="doLogin">
<h5>ログイン</h5>
<input type="text" name="email" value="">
<input type="text" name="password" value="">
<button type="submit">Vue.jsによるログイン実行</button>
<hr>
<h5>ログインユーザーの取得</h5>
<button type="button" @click="getUser">ユーザー情報取得</button>
<div style="display:inline-block"> → {{userState}}</div>
<div v-if="user.id">
<h5>ログインユーザーのログアウト</h5>
<button type="button" @click="doLogout">ログアウト</button>
</div>
</form>
</div>
`
});
tinker を起動して以下のコードでユーザを作成します。
php artisan tinker
\DB::table("users")->insert([
'name' => 'テストユーザー' ,
'email' => 'test@user.com' ,
'password' => \Hash::make('1234') ,
]);
これで以下の情報でログインすることができます
ID : test@user.com
PASSWORD : 1234
こちらのURLにウェブブラウザでアクセスします
https://YOUR-SERVER.COM/test/test-login.html
Vue.js を通して axios から以下のエンドポイントへxhrを投げます
/sanctum/csrf-cookie/ (getメソッド)(チェック無し) CSRF-TOKEN を暗号化した XSRF-TOKEN を取得します。
/api-login (postメソッド)(1.csrfチェック)ログインの実行
/api-logout (postメソッド)(1.csrfチェック)ログインの実行
/api/user (getメソッド)(1.csrfチェック無し 2.sanctumログインチェック)ログインの実行
/api/ で始まるURLのみ「2.sanctumログインチェック」が入ります。
/api/ 以外のURLのpostメソッドのみ「1.csrfチェック」が入ります。
testディレクトリをローカルマシンの適当なところにダウンロードしてきてそこにExpressサーバーを起動するserver.jsを作成します
server.js ( local.myhost.com )は適宜読み替えてください。
'use strict';
const express = require('express');
const serveIndex = require('serve-index');
const fs = require('fs');
// ==============================サーバ名とポートをセット
const host = 'local.myhost.com';
const port = 3000;
// ==============================サーバ名とポートをセット
const app = express();
const server = require('https').createServer({
key: fs.readFileSync('./privatekey.pem'),
cert: fs.readFileSync('./cert.pem'),
}, app)
app.use(express.static('.'));
app.use(serveIndex('.', {icons: true}));
// app.listen(port, host);
server.listen(port, host, () => console.log(`Server Started https://${host}:${port}`))
openssl req -x509 -newkey rsa:2048 -keyout privatekey.pem -out cert.pem -nodes -days 365
npm init -y
npm i -S express serve-index
node server.js
でローカルサーバーを起動します
こちらからも同様にアクセスができるかどうか検証しましょう。
httpsサーバについてはこちらも参考にしてください
Macのローカルマシンの Express で https:// なサーバを立ち上げて Google Chromeからアクセスする|プログラムメモ
0.0.0.0 local.myhost.com
と書き換えて、ローカルマシンを騙します。 これで https://local.myhost.com:3000 ローカルのマシンにアクセスできます
config/cors.php を確認しましょう
'paths' => ['api/*', 'sanctum/csrf-cookie', 'api-register', 'api-login', 'api-logout'], // ● 3つのエンドポイントを追加
'allowed_origins_patterns' => ['/localhost:?[0-9]*/'], // ● localhost:3000 追加
'supports_credentials' => true, // ● trueに変更
.env を本番環境とlocalhost では切り替える必要があります。
ローカルからアクセスする場合の .env
( myhost.com は適宜読み替えてください)
# config/session.php の SESSION 設定 (サブドメインを除いたドット始まりで記述する。 api.myhost.com の場合 .myhost.com と記述する)
SESSION_DOMAIN=".myhost.com"
# (●local) フロントエンドをローカルマシンにする場合は必ず設定。ドメインとポート番号を記述すること。
# フロントエンドのマシンが https://front.myhost.com:3000 の場合 front.myhost.com:3000 と記述すること
SANCTUM_STATEFUL_DOMAINS=local.myhost.com:3000
# (●local) SESSION_SAME_SITE は次のうちから選択 ("lax", "strict", "none", null ) デフォルトは "lax"
SESSION_SAME_SITE=none
# (●local) http:// なサイトから xhr で送受信するときにCookieをやり取りしたい場合は false をセットしてアンセキュアにする デフォルトは true
# local,本番いずれも 特に変更しなくてデフォルトの true のままで良い
SESSION_SECURE_COOKIE=true
Laravelのヘッドレスな認証の仕組みとして(Laravel Sanctum / Laravel Fortify)があります。
違いをざっくりと認識しておきましょう。
https://readouble.com/laravel/8.x/ja/sanctum.html
Laravel Sanctum(サンクタム、聖所)は、SPA(シングルページアプリケーション)、モバイルアプリケーション、およびシンプルなトークンベースのAPIに軽い認証システムを提供します。
・APIトークン
1つ目にSanctumは、OAuthの複雑さなしに、ユーザーにAPIトークンを発行するために使用できるシンプルなパッケージです。この機能は、「パーソナルアクセストークン」を発行するGitHubやその他のアプリケーションに触発されています
・SPA認証
SanctumはLaravelの組み込みのクッキーベースのセッション認証サービスを使用します。通常、SanctumはLaravelの「web」認証ガードを利用してこれを実現します。
composer create-project laravel/laravel sanctum_app
cd sanctum_app
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan route:list
https://readouble.com/laravel/8.x/ja/fortify.html
Laravel Fortifyは、Laravelのフロントエンドにとらわれない認証バックエンドの実装です。Fortifyは、ログイン、ユーザー登録、パスワードのリセット、メールの検証など、Laravelの認証機能をすべて実装するために必要なルートとコントローラを登録します。
基本的にLaravel Fortifyは、Laravel Breezeのルートとコントローラを持っており、ユーザーインターフェイスを含まないパッケージとして提供しています。
composer create-project laravel/laravel fortify_app
cd fortify_app
composer require laravel/fortify
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
php artisan route:list
基本となるログイン(Rate Limmiting機能付き)の仕組みにプラスして、
以下のFeatureがON/OFFできるようになっています。(config/fortify.php)
---------------------------
登録機能(registration)
パスワードリセット機能(resetPasswords)
メール認証機能(emailVerification)
プロフィール情報の更新機能(updateProfileInformation)
パスワードの更新機能(updatePasswords)
2段階認証機能(twoFactorAuthentication)
---------------------------
Route::apiResource('news', App\Http\Controllers\NewsController::class);
作成されるルーティング
| GET|HEAD | api/news | news.index | App\Http\Controllers\NewsController@index | api |
| POST | api/news | news.store | App\Http\Controllers\NewsController@store | api |
| GET|HEAD | api/news/{news} | news.show | App\Http\Controllers\NewsController@show | api |
| PUT|PATCH | api/news/{news} | news.update | App\Http\Controllers\NewsController@update | api |
| DELETE | api/news/{news} | news.destroy | App\Http\Controllers\NewsController@destroy | api |
| GET|HEAD | api/user | | Closure | api |
Route::apiResource('news', App\Http\Controllers\NewsController::class)->only(['index','show']);
作成されるルーティング
| GET|HEAD | api/news | news.index | App\Http\Controllers\NewsController@index | api |
| GET|HEAD | api/news/{news} | news.show | App\Http\Controllers\NewsController@show | api |
composer require rorecek/laravel-ulid
$table->bigIncrements('id');
↓
$table->char('id', 26)->primary();
use HasUlid;
public $incrementing = false;
protected $keyType = 'string';
以上です。
引用 : https://qiita.com/mitashun/items/90891c4f9e95bfabfe17
(例として News.php モデルのプライマリキーを変更してみます)
以下のプロパティーを追加します
app/Models/News.php
// uuidなのでインクリメントOFF
public $incrementing = false;
// uuidなので string型
protected $keyType = 'string';
同じくモデルファイルに以下を追加します
app/Models/News.php
protected static function boot()
{
parent::boot();
self::creating(function(News $model) {
$model->id = \Str::orderedUuid();
});
}
database/migrations/xxxxxxxx_create_news_table.php
// $table->increments('id'); // コメントアウト
$table->uuid('id')->primary(); // uuidに変更
マイグレーションファイルをいちどロールバックして再実行します
php artisan migrate:rollback
php artisan migrate
composer require symfony/uid:5.4.21
use Symfony\Component\Uid\Ulid;
$ulid = new Ulid(); // e.g. 01AN4Z07BY79KA1307SR9X4MV3
.env にエディタ VS Code を設定する
IGNITION_EDITOR="vscode"
他にも以下のエディターが選択可能です
phpstorm
vscode
vscode-insiders
vscodium
textmate
emacs
sublime
atom
nova
macvim
idea
netbeans
xdebug
ここをクリックするだけで自分の好きなエディターでエラー箇所へ移動することができます。
ダンプメソッドddの代わりに ddd を使用することもできます。 dddを使用した場合は同じような画面が表示されます。
dd( $test );
↓
ddd( $test );
php artisan vendor:publish
選択画面になるので次のクラスを選択します
Facade\Ignition\IgnitionServiceProvider
次の2ステップの作業のみでVS CodeやPhpStormでコード補完(インテリセンス)が効くようになります。
https://github.com/barryvdh/laravel-ide-helper
composer require --dev barryvdh/laravel-ide-helper
Facade のコードの補完をできるようにする
こちらのコマンドを実行すると _ide_helper.php ファイルを生成します
php artisan ide-helper:generate
Model のプロパティ補完をできるようにする
こちらのコマンドを実行すると _ide_helper_models.php ファイルを生成します
モデルファイルの格納ディレクトリ を --dir で指定します
php artisan ide-helper:models -N --dir="app/Models"
以上でOKです。
拡張機能を「@builtin php」で検索して「PHPの基本言語サポート」をオフにします
生成される2つのファイルは開発のファイルなので gitから除外しておきましょう
.gitignore
# ide-helperのファイルは除外する
/_ide_helper.php
/_ide_helper_models.php
参考 : https://qiita.com/PruneMazui/items/74034913bcd4af7a4eaf
composer で何かしらのエラーが出る場合はファイル _ide_helper.php _ide_helper_models.php を削除してから Composer コマンドを実行します。 実行後に再度生成します。
大きく分けると次の2つの方法があります
● 1. サービスプロバイダーを使用する方法 (おすすめです)
● 2. バリデーションルールを使用する方法
php artisan make:provider ValidatorServiceProvider
app/Providers/ValidatorServiceProvider.php が自動生成されます
public function boot()
{
\Validator::extend(
'mytext',
function ($attribute, $value, $parameters, $validator) {
return preg_match('/^[0-9]{3}-?[0-9]{4}$/', $value);
}
);
}
config/app.php
/*
* Application Service Providers...
*/
App\Providers\ValidatorServiceProvider::class,
これだけでOKです!
php artisan make:rule Hankaku
app/Rules/Hankaku.php が自動作成されます
バリデーションがOKの時に true を返すように記述します
public function passes($attribute, $value)
{
return preg_match('/^[a-zA-Z0-9]+$/', $value);
}
public function message()
{
return ':attribute は半角英数字で入力してください';
}
formRequest.php
public function rules(): array
{
return [
'zipcode' => ['required', new Hankaku],
];
}
配列で記述します。パイプ記法では使用できないみたい?
is_a($model->{$k}, 'Illuminate\Support\Carbon')
pagination ファイルを resources/views/vendor/pagination/ フォルダーにコピーする
php artisan vendor:publish --tag=laravel-pagination
これでデフォルトの pagination の bladeテンプレートファイルが resources/views/vendor/pagination/tailwind.blade.php になります。
app/Providers/AppServiceProvider.php に以下のコードを追加する
public function register()
{
// pagination を bootstrapに変更
\Illuminate\Pagination\Paginator::useBootstrap();
}
composer create-project laravel/laravel my_app
cd my_app
composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev
app/Providers/AppServiceProvider.php を修正
.env を修正してDB設定を書き込む
php artisan migrate
これでエラーが表示されることがあるがその場合は以下のようにする
1. change "sass": "^1.33.0", to "sass": "1.32.13", in package.json.
2. package-lock.json を削除
3. node_modules フォルダを削除
4. run npm install
Tailwind CSS を利用したい場合はこちら
Laravel Breeze で シンプルな認証 ( with Tailwind CSS )|プログラムメモ
// ● データベースを変更する (config/database.php の connections.mysql_system_b を参照する)
protected $connection= 'mysql_system_b';
// ● テーブル名を変更する
protected $table = 'item_dt';
// ● プライマリキーを変更する
protected $primaryKey = 'data_id';
// ● ガードするカラムを変更する
protected $guarded = ['data_id', 'modified_date'];
composer require laravel/breeze
php artisan breeze:install
npm install && npm run dev
これで画面の右上の「Login」「Register」画面が登場します。
Tailwind CSS ではなく Bootstrapが良い場合はこちら
Laravel 8 で Bootstrap の laravel/ui を利用する|プログラムメモ
$counts = \App\Estimate::where('project_status_id', '=', 30)->count();
実際に実行されるSQL文は次のようになります
"query" => "select count(*) as aggregate from `estimates` where `project_status_id` = ?"
"bindings" => [
0 => 30
]
https://packagist.org/packages/rtconner/laravel-tagging
# ● モデルに以下を追加
例: /app/Post.php にトレイトを追加
use \Conner\Tagging\Taggable; // tag
eager loadingされます
$post = \App\Post::with('tagged')->first();
$post->tag('Gardening'); // attach the tag
$post->tag('Gardening, Floral'); // attach the tag
$post->tag(['Gardening', 'Floral']); // attach the tag
$post->untag('Cooking'); // remove Cooking tag
$post->untag(); // remove all tags
$post->retag(['Fruit', 'Fish']); // delete current tags and save new tags
$post->retag('Fruit', 'Fish');
$post->retag('Fruit, Fish');
PHP 8 & Laravel 8 以上の場合
composer require spatie/laravel-tags
PHP 7.4 & Laravel 8 以上の場合
composer require spatie/laravel-tags:3.1.0
PHP 7.2 & Laravel 5.8 以上の場合
composer require spatie/laravel-tags:2.7.2
php artisan vendor:publish
一覧が表示されるのでそこから「Provider: Spatie\Tags\TagsServiceProvider」の横に表示されている番号を入力します
Copied File [/vendor/spatie/laravel-tags/database/migrations/create_tag_tables.php.stub] To [/database/migrations/2021_08_02_160308_create_tag_tables.php]
Copied File [/vendor/spatie/laravel-tags/config/tags.php] To [/config/tags.php]
DBテーブルをマイグレーションする
php artisan migrate
// ============= Trait
use \Spatie\Tags\HasTags;
// ============= Trait
dump( $post->tagsTranslated );
laravel new project-name --jet
composer require laravel/jetstream
php artisan jetstream:install inertia
php artisan jetstream:install inertia --teams
npm install && npm run dev
php artisan migrate
routes/web.php
Route::get('/test', function () {
return Inertia::render('Test'); // Test.vue を表示させる
});
resources/js/Pages/Test.vue
<template>
<h1>Hello world!</h1>
<inertia-link href="/" class="text-sm text-blue-700 underline">back to HOME</inertia-link>
</template>
Laravel8では
・inertia.js を選択すればvue.jsで作成されたスキャフォールディングを使用する。
・Livewire を選択すれば PHP + Blade + ajax で作成されたスキャフォールディングを使用する。
・Laravel Breeze をインストールすればPHP + Bladeのみで作成されたスキャフォールディングを使用する。
composer require livewire/livewire
php artisan livewire:publish
以下のタグで livewire を読み込みます
@livewireStyles
</head>
<body>
...
@livewireScripts
</body>
</html>
composer require rappasoft/laravel-livewire-tables
php artisan make:livewire posts-table
touch app/Http/Livewire/PostsTable.php
以下の内容で作成します
welcome.blade.php
@extends('layout')
@section('content')
@yield('content__header')
@yield('content__search')
<div class="row">
<div class="col-md-12">
@livewire('posts-table')
</div>
</div>
@endsection
resources/views/livewire/posts-table.blade.php
<div>
<div class="row">
<div class="col">
<input wire:model="search" class="form-control" type="text" placeholder="Search books...">
</div>
</div>
<div class="row">
<table class="table table-hover table-condensed table-striped table-bordered table_sm">
<thead>
<tr>
<th class="sortable">ID</th>
<th class="sortable">
<a wire:click.prevent="sortBy('title')" role="button" href="#">
Title
</a>
</th>
<th class="sortable">
<a wire:click.prevent="sortBy('author_id')" role="button" href="#">
Author
</a>
</th>
</tr>
</thead>
<tbody>
@foreach ($posts as $post)
<tr>
<td>{{ $post->id }}</td>
<td>{{ $post->name }}</td>
<td>{{ $post->content_name }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
app/Http/Livewire/PostsTable.php
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class PostsTable extends Component
{
use \Livewire\WithPagination;
public $perPage = 10;
public $search = '';
public $sortField = 'id';
public $sortAsc = true;
public function render()
{
$posts = \App\Post::search($this->search)
->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')
->paginate($this->perPage);
return view('livewire.posts-table', ['posts' => $posts]);
}
}
https://github.com/imliam/awesome-livewire
livewire では vue.js や react と異なり、ajaxでhtmlコードを返します。
検索はサーバーサイド( DBによる再select )を使用します。
composer require rap2hpoutre/fast-excel
コントローラーから次のように呼び出します
use Rap2hpoutre\FastExcel\FastExcel;
return (new FastExcel(User::all()))->download('file.xlsx');
コントローラーから次のように呼び出します。 (拡張子をCSVにするとCSVでダウンロードされます)
use Rap2hpoutre\FastExcel\FastExcel;
return (new FastExcel(User::all()))->download('file.csv');
↑ このコードでは
・\App\User モデルの全データが出力
・\App\User モデルの全カラムが出力
・文字コード 「UTF-8 With BOM」
・CSVのダブルクォーテーション ““ での 囲みは自動(値にクォーテーションを含むなど必要な時だけクォーテーションがつく。)
でダウンロードされます。
return (new FastExcel(User::all()))->download('file.csv', function ($user) {
return [
'メールアドレス' => $user->email,
'姓' => strtoupper($user->lastname),
'名' => $user->firstname,
];
});
このようにすると3つのカラムのみ出力します。
use Rap2hpoutre\FastExcel\FastExcel;
return (new FastExcel(User::all()))->configureCsv(',', '"', 'UTF-8', false)->download('file.csv');
パラメーターは次の通りです
configureCsv($delimiter = ',', $enclosure = '"', $encoding = 'UTF-8', $bom = false)
・LaravelアプリケーションでエクセプションがThrowされたときに、Adminモデルの管理者宛にメールを通知してみます
/app/Admin.php
class Admin extends Authenticatable
{
use \Illuminate\Notifications\Notifiable;
ExceptionEmail の名前は適宜変更可能です
php artisan make:notification ExceptionEmail
app/Notifications/ExceptionEmail.php が自動生成されます
エラー内容 Exception を受け取れるようにメンバ変数を設定します。
コンストラクタのタイミングでエラー内容を受け取ります
private $ex;
public function __construct( \Exception $ex )
{
$this->ex = $ex;
}
また toEmail() メソッドを次のように修正します
public function toMail($notifiable)
{
return (new MailMessage)
->greeting('ERRORが発生しています')
->line( \Request::fullUrl() )
->line( 'Exceptionクラス名 (' . get_class($this->ex) .')' )
->line( "{$this->ex->getFile()}:{$this->ex->getLine()}" )
->line( "{$this->ex->getMessage()}");
}
app/Exceptions/Handler.php
public function report(Throwable $exception)
{
parent::report($exception);
}
↓ このメソッドに管理者へメール通知する命令を追記します。(この例ではIDが1の管理者へメールを送信しています)
public function report(Throwable $exception)
{
// 管理者にメール通知する
$admin = \App\Admin::where('id','=',1)->first();
// AuthenticationException|AuthorizationException|HttpException|ModelNotFoundException|ValidationException 以外の場合は 通知する
if ( ! preg_match("/(AuthenticationException|AuthorizationException|HttpException|ModelNotFoundException|ValidationException)/", get_class($exception)) ){
$admin->notify(new \App\Notifications\ExceptionEmail($exception));
}
// 管理者にメール通知する
parent::report($exception);
}
正規表現で次の Exception クラスの場合はメールを送信しないようにしています
\Illuminate\Auth\AuthenticationException
\Illuminate\Auth\Access\AuthorizationException
\Symfony\Component\HttpKernel\Exception\HttpException
\Illuminate\Database\Eloquent\ModelNotFoundException
\Illuminate\Validation\ValidationException
以上です。
とても簡単ですね。
これでエラーが発生すると管理者宛にこのようなメールが到着します
tmp/my_folder 内で 1日以上経過したテンポラリファイルを削除する
// 1日経過したテンポラリファイルを削除
collect(\Storage::disk('local')->listContents('tmp/my_folder', true))
->each(function($file) {
if ($file['type'] == 'file' && $file['timestamp'] < now()->subDays(1)->getTimestamp()) {
// dd( "{$file['path']} を削除する" , \Storage::disk('local')->path($file['path']) );
\Storage::disk('local')->delete($file['path']);
}
});
sudo yum install -y jpegoptim
または
sudo dnf install -y jpegoptim
インストールの確認
jpegoptim --version
jpegoptim --strip-all sample.jpg
jpegoptim --max=80 sample.jpg
jpegoptim --max=80 **/*.jpg
実行例 (40.82% サイズが小さくなりました)
sample_02.jpg 1400x1194 24bit N JFIF [OK] 369121 --> 218432 bytes (40.82%), optimized.
jpegoptim --max=75 sample.jpg
実行例 (46.37% サイズが小さくなりました)
sample_03.jpg 1400x1194 24bit N JFIF [OK] 369121 --> 197954 bytes (46.37%), optimized.
圧縮率 75% 〜 85% あたりがおすすめです。
jpegoptim --size=50% sample.jpg
実行例 (50.48% サイズが小さくなりました)
sample_04.jpg 1400x1194 24bit N JFIF [OK] 369121 --> 182787 bytes (50.48%), optimized.
composer require spatie/laravel-image-optimizer
php artisan vendor:publish --provider="Spatie\LaravelImageOptimizer\ImageOptimizerServiceProvider"
// app/Http/Kernel.php
protected $routeMiddleware = [
...
'optimizeImages' => \Spatie\LaravelImageOptimizer\Middlewares\OptimizeImages::class,
];
use ImageOptimizer;
// the image will be replaced with an optimized version which should be smaller
ImageOptimizer::optimize($pathToImage);
// if you use a second parameter the package will not modify the original
ImageOptimizer::optimize($pathToImage, $pathToOptimizedImage);
<meta name="description" content="{!! addslashes($description) !!}" />
↓
<meta name="description" content="{!! preg_replace("{\\\'}", "'", addslashes($description) ) !!}" />
https://github.com/lazychaser/laravel-nestedset
routes/web.php
必ずページの1番下に 次のように記述します
// =============== fallback 404 ===============
Route::fallback(function () {
return redirect('/my_error.php');
});
上記の方法を実行しても、findOrFail() メソッドなどを実行したときのリダイレクト先は並べる独自の404ページになってしまいます そこで 404ページを作成してリダイレクトさせます。(強引ですが。)
/resources/views/errors/404.blade.php にファイル下記の内容で作成します。
<script>window.location = "/my_error.php";</script>
app/Http/Middleware/VerifyCsrfToken.php
class VerifyCsrfToken extends Middleware
{
protected $except = [];
}
↓
class VerifyCsrfToken extends Middleware
{
protected $except = [ '/front_ajax/*' ];
}
@php
$k = 'vaccination_flag';
$class_name = 'form-controlx';
$style = 'width:70px;';
$input_values_array = ['1' => '有り' , '0' => '無し'];
$_form_value = old('vaccination_flag',@$user[$k]);
@endphp
@foreach ( $input_values_array as $input_k => $input_v )
<label class="{{$class_name}}" style="{{$style}}"><input name="{{$k}}" type="radio" value="$input_v" @if( $_form_value===$input_v ) checked @endif> {{$input_v}}</label>
@endforeach
一番シンプルなやり方はコントローラーに次のように記述します
public function confirm( Request $request )
{
$validation_rule = [
"name_sei" => 'required' ,
];
// バリデーション(エラーがある場合は前の画面に戻ります)
$this->validate( $request, $validation_rule );
}
1. パイプでつなぐ記法
'item_id' => 'required|integer',
2. 配列記法 (正規表現で | を使用したい時はこちら。)
'item_id' => ['required', 'integer'] ,
'name' => ['required','regex:{^[^/]+$}'] ,
'hogehoge_date' => 'nullable|date', // 日付( null を許す )
'hogehoge_time' => 'date_format:H:i', // 時間( 12:40 や 07:05 などのフォーマット)
// regex を使用する時は array() を使用しましょう。 | が正規表現の文字として使えるようになります。
'hogehoge_time2' => array( 'regex:{((0?[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))}' ) , // 分( 00 〜 59 または 0 〜 59 )
'hogehoge_id' => 'nullable|integer', // 数字( null を許す )
'hogehoge_tax_no' => 'required_if:withtax_flg,0', // 条件( withtax_flg が 0 の時は hogehoge_tax_no 必須 )
'price_initial_no' => 'required_unless:mt_payment_id,21', // 条件( mt_payment_id が 21以外 の時は price_initial_no 必須 )
'grade_no' => 'required_with:school_id', // 条件( school_id に値がある時 grade_no 必須。条件となるカラムはカンマ区切りで複数指定可 )
'send_flg' => 'integer|in:0,1' , // 0 または 1 の数値
'year_month_name' => ['required','regex:{^[0-9]+/[0-9]+$}'] , // 正規表現 2019/12 のような表記にマッチするか?
'email' => 'required|email|confirmed', // 必須 , メールアドレス , 「email_confirmation」にも同じ値が入っているかチェック
'password' => 'nullable|confirmed', // nullを許す, 「password_confirmation」にも同じ値が入っているかチェック
'name' => ['required','regex:{^[^/]+$}'] , // 入力文字列にスラッシュを含めない
'password' => 'required|min:4', // 4文字以上
// rule
$validation_rule['confirmations'] = [ 'required', function($attribute, $value, $fail) use ($confirmations_count) {
if ( $confirmations_count !== count($value) ){
return $fail('確認事項全てにチェックをつけてください');
}
}
];
// message
$validation_message = [
'confirmations.required' => '確認事項にチェックをつけてください',
];
チェックが一つも入っていない時は required のエラー「確認事項にチェックをつけてください」が表示されます
全てにチェックが入っていない時は クロージャー のエラー「確認事項全てにチェックをつけてください」が表示されます
テーブル users の中に同じ email で登録がある場合はバリデーションエラーとしたい、場合は次のように記述します
'email' => 'unique:users'
↑ 上の記述は不完全で、データ更新(update)の時に自分自身のメールアドレスもエラーとしてしまいます。
そこで、自分自身は除外するように次のように記述します。
// update時の validation
$this->validation_column['email'] = [ 'required',
\Illuminate\Validation\Rule::unique('users')->ignore($id),
];
// 同じ shop_id の中で email はユニーク
$this->validation_column['email'] = [ 'required', \Illuminate\Validation\Rule::unique('users')->where(function ($query) use ($q) {
return $query->where('shop_id', $q['shop_id']);
}) ];
// バリデーション実行
$this->validate($request, $this->validation_column);
次のように記述すると新規登録時と更新時の 登録ずみのメールアドレス除外 バリデーションをまとめて登録することができます。
// 事前にモデルの $id を取得しておきます。
$id = xxxxxxxxxxxxxxxxxx;
$this->validation_column = [
'email' => 'required|email|unique:users,email' . ($id > 0 ? ",{$id}" : ''),
'password' => ($id > 0 ? '' : 'required|') . 'min:4|confirmed',
];
独自のバリデーションルールを適用したい時、バリデーションルールを動的に変更したいときなどは FormRequest を使用すると、バリデーション部分が外に出るのでコントローラーがすっきりします。
UsersRequest という フォームリクエストを作成します
php artisan make:request UsersRequest
app/Http/Requests/UsersRequest.php が自動作成されます。
例として shop_id と email をチェックしてユニークかどうかを判定するバリデーションを記述してみます
app/Http/Requests/UsersRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UsersRequest extends FormRequest
{
/**
* 認証のロジックを記述する場合はここに記述する。
* それ以外は常に true を返すように記述しておかないとバリデーションが動作しないので return true; とする。
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* バリデーションルールを記述
*
* @return array
*/
public function rules()
{
return [
"shop_id" => 'required' ,
"email" => [ 'required', 'email', 'confirmed',
\Illuminate\Validation\Rule::unique('users')->ignore($this->input('id'))->where(function($query) {
// 入力されたshop_idの値と同じ値を持つレコードでのみ検証する
$query->where('shop_id', $this->input('shop_id'));
}),
] ,
];
}
}
public function confirm( Request $request )
{
$validation_rule = [
"name_sei" => 'required' ,
];
// バリデーション(エラーがある場合は前の画面に戻ります)
$this->validate( $request, $validation_rule );
}
↓
/**
* ユーザー登録確認
*
* \App\Http\Requests\UsersRequest による自動バリデーションが行われる(エラーの場合は前の画面に戻される)
*
*/
public function confirm( \App\Http\Requests\UsersRequest $request )
{
これでOKです。コントローラーにバリデーションの記述が一切なくなりました。 コメントはどこかに記述しておくとよいでしょう。
@php
dump( $errors );
@endphp
emailパラメーターのエラーを表示させるには
@include('partial.error_message', ['param_name' => 'email'])
Bootstrapの場合
@if($errors->has($param_name))
<div class="text-danger small" style="flex-wrap: wrap;">{{ $errors->first($param_name) }}</div>
@endif
Tailwind CSSの場合
@error('email')
<div class="text-red-500 text-xs absolute mt-1">{{ $message }}</div>
@enderror
old を使用します。
<input type="text" placeholder="" name="email" required value="{{ old('email') }}">
https://twitter.com/juandmegon/status/1274134701800919040?s=21
use Illuminate\Support\Str;
/**
* バリーデーションのためにデータを準備
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug($this->slug),
]);
}
https://pgmemo.tokyo/data/archives/1952.html
$validation_rule = [
'last_name' => 'required',
];
$validation_message = [
'last_name.required' => '名前は必ず入力してください',
];
$this->validate( $request, $validation_rule, $validation_message );
1対1
1対多
多対多
Has Many Through
1対1(ポリモーフィック)
1対多(ポリモーフィック)
多対多(ポリモーフィック)
これらのうち 多対多 リレーションを操作してみます。
とても簡単です。2つのテーブルをつなぐピボットテーブルを作成すればOKです。
・users
・practitioners
というテーブルを多対多でつなぐ時は
テーブル ↓
・user_practitioner
を作成します。( practitioner_user でも良い。 アルファベット順に並べると規則がはっきりして良いですね。単数形 をアンダースコアでつなげます。)
中身は
テーブル「practitioner_user」の構造
id (primary key)
user_id (integer,unsigned)
practitioners_id (integer,unsigned)
とします。
マイグレーションファイルの作成
php artisan make:migration create_practitioner_user_table
マイグレーションファイルの up() メソッドは次のようになります。
public function up()
{
Schema::create('practitioner_user', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('practitioner_id');
$table->unsignedInteger('user_id');
$table->timestamps();
});
}
多対多リレーションある意味シンプルです。
これを両方のテーブルに(メソッド名は適宜変更して)定義します。
例)Userモデルに設定
/**
* ● 多対多 リレーション : ->practitioners でXXXXXを取得する
* ピボットテーブル「practitioner_user」
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function practitioners()
{
return $this->belongsToMany('App\Practitioner','practitioner_user');
}
/**
* ● 多対多 リレーション
* ピボットテーブル : practitioner_user
* ソート順 : id DESC
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function practitioners()
{
return $this->belongsToMany('App\Practitioner','practitioner_user')->where('is_active', '=', 1)->orderBy('id','DESC');
}
$model = User::findOrFail($id);
dump( $model->practitioners()->allRelatedIds() ); // (注意)Laravel 5.4以前は getRelatedIds() という名前でした
ユーザ1 , ユーザ2 , ユーザ3 の様な文字列を取得します
$model = User::findOrFail($id);
dump( $model->practitioners()->implode('practitioner_name' , ' , ' ) );
bladeテンプレートに記述するときは以下のようにします。
{!! $consentform->patient->managers->pluck('name')->implode('<br>') !!}
このようにも記述できます。( ↑ と同じです。)
{!! $consentform->patient->managers->implode('name','<br>') !!}
@php
$consentform->patient->managers->each(function ($item) {
$route_manager = route('admin.managers.show', $item->id);
print <<< DOC_END
<a href="{$route_manager}" target="_blank">{$item->name}</a><br>
DOC_END;
});
@endphp
$model = User::findOrFail($id);
$model->practitioners()->attach([5,6,7]); // ピボットテーブルに(すでにあるデータは消さずに)データを追加
$model->practitioners()->detach(1) // id=1の紐付けを解除する
$model = User::findOrFail($id);
$model->practitioners()->sync([11,22,33]); // ピボットテーブルを指定した配列のデータで更新(すでにあるデータを書き換え)
リレーションを一気に削除する場合は
->sync();
// 既に持っているリレーションデータをすべて取得
$tmp_all_list = $c->practitioners()->pluck('practitioners.id')->toArray(); // [1,2,3,4,5,6]
if (! in_array($row[0],$tmp_all_list) ){
array_push($tmp_all_list,$row[0]);
$c->practitioners()->sync($tmp_all_list); // ピボットテーブルを指定した配列のデータで更新(すでにあるデータを書き換え)
}
参考: pivot table に他のカラムデータを保存させる https://stackoverflow.com/questions/48323971/laravel-saving-to-pivot-table-in-many-to-many-relationship-with-extra-column
参考 : php - Laravel sync Relation with optional parameters - Stack Overflow
1対1
1対多
多対多
Has Many Through
1対1(ポリモーフィック)
1対多(ポリモーフィック)
多対多(ポリモーフィック)
これらのうち 1対1 リレーションを操作してみます。
呼び出す側に記述します。
/**
* ● 1対1リレーション
* 対象モデル「App\Doctor」
*
*/
public function doctor()
{
return $this->hasOne('App\Doctor','id','doctor_id')->withDefault();
}
/**
* 1対1リレーション(hasOne) : ->created_user で作成したユーザーの情報を取得できるようにする
*
* @return \Illuminate\Database\Eloquent\Relations\hasOne
*/
public function created_user()
{
return $this->hasOne('App\User', 'id', 'created_user_id')->withDefault();
}
呼び出される側に記述します。
/**
* ● 1対1リレーション(belongsTo) : ->mt_branch で営業支店の情報を取得できるようにする
* 対象モデル「App\MtBranch」
*
*/
public function mt_branch()
{
return $this->belongsTo('App\MtBranch','branch_id')->withDefault();
}
次のどちらもokです。
$carbon_obj = new Carbon('2019-03-11');
$carbon_obj = new Carbon('2019/03/11');
$carbon_obj = \Carbon\Carbon::createFromTimestamp( $v->getMTime() );
dd(Carbon::now()->timestamp);
echo $dt->timezone->getName();
( Asia/Tokyo )( +00:00 )のような形で返ってきます。
echo Carbon\Carbon::now('Asia/Tokyo')->toDateTimeString()
echo $dt->year;
echo $dt->month;
echo $dt->day;
echo $dt->hour;
echo $dt->minute;
echo $dt->second;
2019
5
1
10
42
28
bladeテンプレートで Carbon を呼び出すには次のように記述します。
{{ \Carbon\Carbon::now() }}
{{ \Carbon\Carbon::now()->format("Y年m月d日") }} // 2019年03月05日
{{ \Carbon\Carbon::now()->format("Y年n月j日") }} // 2019年3月5日
{{ \Carbon\Carbon::now()->format("Y年m月d日 H:i:s") }} // 2019年03月05日 12:30:59
{{ \Carbon\Carbon::now()->format("Y年n月j日 H:i") }} // 2019年3月5日 12:30
{{ \Carbon\Carbon::now()->format("Ymd_His_v") }} // ( v はミリ秒です )20190305_123059_699
format メソッドの引数は PHP の date と同じです。
http://php.net/manual/ja/function.date.php
https://carbon.nesbot.com/docs/
$dt = new \Carbon\Carbon();
$dt->setTime(0, 0, 0)
dd($dt);
$dt->setDate($dt->year, $dt->month, 15);
$dt = new \Carbon\Carbon('2019-05-31');
dump( $dt->copy()->addMonth(1) );
dump( $dt->copy()->addMonthNoOverflow(1) );
↓ 結果
date: 2019-07-01 00:00:00.0 Asia/Tokyo (+09:00)
date: 2019-06-30 00:00:00.0 Asia/Tokyo (+09:00)
となります。addMonth() メソッドでは 6月31日がないので、7月1日になるのです。
安全のため addMonth () を 使うのを今すぐやめて addMonthNoOverflow() を使用しましょう
同じく
安全のため addYear () を 使うのを今すぐやめて addYearNoOverflow() を使用しましょう
addMonthNoOverflow は addMonthsNoOverflow を呼び出しているので、ほぼラッパーみたいな感じですが、デフォルトで引数1がセットされるので、1ヶ月だけ追加する場合はaddMonthNoOverflow() だけでもokです。
public function addMonthsNoOverflow($value)
public function addMonthNoOverflow($value = 1)
// 日付の加算(addition)
$dt->addDay();
$dt->addWeek();
$dt->addMonthNoOverflow();
$dt->addYearNoOverflow();
$dt->addHour();
$dt->addMinute();
$dt->addSecond();
// 日付の減算(subtraction)
$dt->subDay();
$dt->subWeek();
$dt->subMonthNoOverflow();
$dt->subYearNoOverflow();
$dt->subHour();
$dt->subMinute();
$dt->subSecond();
// 昨日
$dt->yesterday();
// 月初
$dt->startOfMonth();
// 月末 の(00:00:00)
$dt-> lastOfMonth();
// 月末 の(23:59:59)
$dt->endOfMonth();
// その日の 00:00:00
$dt->startOfDay();
// その日の 23:59:59.999999
$dt->endOfDay();
// parseによる指定(来月末)
$dt->parse('last day of next month');
setlocale(LC_TIME,'ja_JP.utf8');
echo $dt->formatLocalized('%Y/%m/%d(%a)');
echo $dt->formatLocalized('%Y/%m/%d(%a) %H:%M:%S')
出力例
2020/08/28(金)
2020/08/28(金) 15:41:45
こちら https://qiita.com/chiyoyo/items/da32649b0e04957856c1
の DatetimeUtility.php を使わせてもらいましょう
date_default_timezone_set('Asia/Tokyo');
require_once('DatetimeUtility.php');
$dt = Carbon::parse('2019-05-01');
echo DatetimeUtility::date('JK年n月j日 H:i:s', $dt->timestamp)."\n";
echo DatetimeUtility::date('Jk年n月j日 H:i:s', $dt->timestamp)."\n";
$dt->subSecond(1);
echo DatetimeUtility::date('JK年n月j日 H:i:s', $dt->timestamp)."\n";
echo DatetimeUtility::date('Jk年n月j日 H:i:s', $dt->timestamp)."\n";
令和元年5月1日 00:00:00
令和1年5月1日 00:00:00
平成31年4月30日 23:59:59
平成31年4月30日 23:59:59
$dt = new Carbon('2000-01-01');
echo $dt->age . '才です';
テストや障害の発生確認などの時に有用です。
Carbon::setTestNow(new Carbon('2019-11-22 11:59:59'));
->withPivot('reservation_no') とすると、pivot table に持たせた値を取得することができます。
/**
* ● 多対多 リレーション(with 追加カラム reservation_no) : ->programdays であるユーザーの予約日を取得する
* ピボットテーブル「program_day_user」
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function program_days()
{
return $this->belongsToMany('App\ProgramDay','program_day_user')->withPivot('reservation_no');
}
pivot で取得できます
$model->program_days[0]->pivot;
1対1
1対多
多対多
Has Many Through
1対1(ポリモーフィック)
1対多(ポリモーフィック)
多対多(ポリモーフィック)
これらのうち 1対多 リレーションを操作してみます。
親から子を呼び出すイメージが hasMany です。
/**
* ● 1対多リレーション : ->items でショップ内の商品を取得します
*
* ソート順 : sort_no , ASC
*
*/
public function items()
{
return $this->hasMany('App\Item','shop_id')->orderBy('sort_no','ASC');
}
->items で取得できます
dump( $user->items );
後ろの where() メソッドを追加することで条件をつけることができます。
/**
* ● 1対多リレーション : ->items_active でショップ内のアクティブな商品を取得します
*
* ソート順 : sort_no , DESC
*
*/
public function items_active()
{
return $this->hasMany('App\Item','shop_id')->where('is_active','=',1)->orderBy('sort_no','ASC');
}
逆に子から親を呼び出すイメージが belongsTo です。
/**
* ● 1対多リレーション(belongsTo): ->shop で商品が所属するショップを取得します
*
* 1件のみ取得のためソートなし
*
*/
public function shop()
{
return $this->belongsTo('App\Shop');
}
whereHas を使用します。(スロークエリとなるので注意)
こちらで回避します。
https://qiita.com/mpyw/items/0761a5e44836c9bebcd5
$model->whereHas('actcategories', function($query) use ($q){
$query->where('actcategories.id', '=', $q['actcategory']);
});
Bootstrap使用
@php
$dt = new \Carbon\Carbon();
@endphp
<div class="mr-3">
@php
$k = 'birth_date__year';
$class_name = 'form-control';
$style = '';
$input_values_array = [ "" => "年" ];
for ($i = 0; $i <= 100; $i++){
$input_values_array[$dt->format("Y")] = $dt->format("Y");
$dt->subYearNoOverflow();
}
$_form_value = old($k);
@endphp
{{ Form::select($k, @$input_values_array, $_form_value, ['class' => $class_name, 'style' => $style]) }}
@error('birth_date__year')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
<div class="mr-3">
@php
$k = 'birth_date__month';
$class_name = 'form-control';
$style = '';
$input_values_array = [ "" => "月" ];
for ($i = 1; $i <= 12; $i++){
$input_values_array[$i] = $i;
}
$_form_value = old($k);
@endphp
{{ Form::select($k, @$input_values_array, $_form_value, ['class' => $class_name, 'style' => $style]) }}
@error('birth_date__month')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
<div class="mr-3">
@php
$k = 'birth_date__day';
$class_name = 'form-control';
$style = '';
$input_values_array = [ "" => "日" ];
for ($i = 1; $i <= 31; $i++){
$input_values_array[$i] = $i;
}
$_form_value = old($k);
@endphp
{{ Form::select($k, @$input_values_array, $_form_value, ['class' => $class_name, 'style' => $style]) }}
@error('birth_date__day')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
こんな感じのドロップダウンリストになります
https://github.com/Laravel-Lang/lang/tree/master/src/ja
resources/lang/ja/validation.php
に日本語の項目名を追加していく
'attributes' => [
'last_name' => '姓' ,
] ,
PHPStanについてはこちらを読むと理解が深まります
PHPの静的解析 Phan/Psalm/PHPStan の違い - ログ日記
https://github.com/nunomaduro/larastan
composer require --dev nunomaduro/larastan
vi phpstan.neon
次の内容で保存
includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app
level: 5
./vendor/bin/phpstan analyse
composer require "maatwebsite/excel"
続けてコンフィグファイルを生成します
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
config/excel.phpが生成されます。
次を変更します
'csv' => [
'enclosure' => '',
'line_ending' => "\r\n", // 改行を Windows形式にする
'use_bom' => true, // utf8 With BOM 形式にする
],
enclosure は CSVの各項目を囲む文字を設定します。 通常 “ ですが、いったん何もなしで設定しておいて blade 側で“を設定したいから無理だけセットするようにします。
ExportExcel クラスを生成します。
php artisan make:export ExportExcel
ファイル app/Exports/ExportExcel.php が自動生成されます。
次のように変更します。
<?php
namespace App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder;
class ExportExcel extends DefaultValueBinder implements FromView, WithCustomValueBinder
{
private $view;
public function __construct(View $view)
{
$this->view = $view;
}
/**
* @return View
*/
public function view(): View
{
return $this->view;
}
/**
* 文字列型をセット
*/
public function bindValue(Cell $cell, $value)
{
// 全てを文字列型で返す
$cell->setValueExplicit($value, DataType::TYPE_STRING);
return true;
}
}
$cell->setValueExplicit($value, DataType::TYPE_STRING); メソッドですべてを文字列型に設定しています。
これを設定しないと「0001112222」は「1112222」として出力されてしまいます。
ファイル名 download.xlsx でダウンロードさせます。
$view = \view('estimates.excel_index', compact('estimates_rows'));
return \Excel::download(new \App\Exports\ExportExcel($view), 'download.xlsx');
<table>
<thead>
<tr>
<th>管理ID</th>
<th>プログラム名</th>
</tr>
</thead>
<tbody>
@forelse ( $programs as $program )
<tr>
<td>{{ $program->id }}</td>
<td>"{{ str_replace('"','""',$program->name) }}"</td>
</tr>
@empty
データがありません
@endforelse
</tbody>
</table>
1カラム目は クォーテーション 無し
2カラム目は クォーテーション あり
としています。
ファイル名を download.csv のように拡張子をCSVにするだけでokです
$view = \view('estimates.excel_index', compact('estimates_rows'));
return \Excel::download(new \App\Exports\ExportExcel($view), 'download.csv');
\Excel::download(new \App\Exports\ExportExcel($view), 'download.csv');
とすると、storage/ ディレクトリ直下にファイルが保存されます。
一旦 utf-8 のCSVファイルを tmp/myfile.csv 保存し sjis に変換してダウンロードさせます (メモリ上で utf-8 sjis 変換を行なっているのでファイルサイズが大きい場合はメモリエラーになります。 ファイルを1行ずつ処理するように適宜コードを書き換えてください)
// いったんutf8で保存
$file_path = 'tmp/myfile.csv';
$filename = pathinfo($file_path, PATHINFO_BASENAME);
\Excel::store(new \App\Exports\ExportExcel($view), $file_path);
// sjisダウンロード
$disk = \Storage::disk('local');
$content = $disk->get($file_path);
// remove BOM
$bom = hex2bin('EFBBBF');
$content = preg_replace("/^{$bom}/", '', $content);
// utf-8 to sjis
$content = mb_convert_encoding($content, 'sjis', 'UTF-8');
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"'
];
return \Response::make($content, 200, $headers);
convert -version
Version: ImageMagick 6.7.8-9 2019-02-01 Q16 http://www.imagemagick.org のようにバージョンが表示されればOKです
composer require intervention/image
php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravelRecent"
// 'driver' => 'gd'
'driver' => 'imagick'
“imagick” is much faster than “gd” , even 3 times more faster!
パスから読み込み
$interv = \Image::make( storage_path('app/images/test.png') );
URLから読み込み
$interv = \Image::make( 'https://www.xxx.com/xxx/test.jpg' );
base64データから読み込み
$data = file_get_contents($path);
$data_url = 'data:image/png;base64,'. base64_encode($data);
$interv = \Image::make($data_url);
jpgフォーマットへ変換
$jpg = $interv->encode('jpg');
$interv->save($save_path);
S3へ保存するときは文字列変換をかませます
$data = $interv->__toString();
\Storage::disk($file_store_disk)->put("{$file_store_dir}/{$image_name}", $data);
$interv->width(); // 幅
$interv->height(); // 高さ
$interv->filesize(); // ファイルサイズ
$interv->mime(); // mimeタイプ
$interv->exif(); // exif情報
横幅 800px
縦幅 自動
でリサイズする
$interv->resize(800, null, function($constraint){
$constraint->aspectRatio();
});
https://github.com/spatie/laravel-image-optimizer
https://ariteku.hatenablog.com/entry/2020/08/02/082201
パッケージのインストール
composer require yadahan/laravel-authentication-log
設定ファイルの自動作成
php artisan vendor:publish --provider="Yadahan\AuthenticationLog\AuthenticationLogServiceProvider"
DBテーブル「authentication_log」の自動作成
php artisan migrate
次のトレイトを追加します
use Notifiable; // 通知
use \Yadahan\AuthenticationLog\AuthenticationLogable; // ログ
これでokです。
ログイン後にDBを確認すると、テーブル「authentication_log」へログイン ID とユーザーエージェントが記録されているのが確認できると思います。
resources/views/home.blade.php
最終ログイン
{{ @Auth::user()->lastLoginAt() }}
最終ログインIP
{{ @Auth::user()->lastLoginIp() }}
.env
DB_CHARSET=utf8
DB_COLLATION=utf8_general_ci
config/database.php
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
結果
Copied File [/vendor/spatie/laravel-backup/config/backup.php] To [/config/backup.php]
Copied Directory [/vendor/spatie/laravel-backup/resources/lang] To [/resources/lang/vendor/backup]
config/backup.php を変更します
'mail' => [
'to' => 'your@example.com',
↓
'mail' => [
'to' => array_map('trim', explode(',', env('BACKUP_MAIL_TO'))),
( .env から読み込むようにします。)
.env にメール送信先を追加
# DB Backup
BACKUP_MAIL_TO=yourname@your.dcomain.com
バックアップの実行 (データベースのみ)
php artisan backup:run --only-db
バックアップの実行 (データベースのみ, 通知機能なし)
php artisan backup:run --only-db --disable-notifications
古いバックアップファイルの削除
php artisan backup:clean
設定ファイルにどの期間まで保持するかを設定できるのでそこを好きな設定に書き換えて変更しておきます。
バックアップ先は
storage/app/<アプリ名>/2020-12-31-10-15-00.zip
のようにローカルディスクの中のアプリ名の中に日付のファイル名で保存されます。 変更する場合は設定ファイルを変更します。
app/Console/Kernel.php に次のように記述します
通知が不要な場合は --disable-notifications を後ろにつけます。
// DBの定期バックアップ
$schedule->command('backup:clean')->daily();
$schedule->command('backup:run --only-db')->daily();
->daily() をつけることで、デイリーで実行しています。 外すと 1分ごとにバックアップが実行されます。(Laravelの設定によりますが。)
php artisan backup:list
+---------+-------+-----------+---------+--------------+---------------+--------------+
| Name | Disk | Reachable | Healthy | # of backups | Newest backup | Used storage |
+---------+-------+-----------+---------+--------------+---------------+--------------+
| my-app | local | ✅ | ✅ | 8 | 0.00 (2分前) | 1021.23 KB |
+---------+-------+-----------+---------+--------------+---------------+--------------+
というふうに表示されます。
https://github.com/spatie/browsershot
npm install puppeteer
こちらのコマンドでうまくいかないときは以下のコマンドでインストールします
npm install puppeteer --global
composer require spatie/browsershot
php artisan tinker
use Spatie\Browsershot\Browsershot;
$timestamp = \Carbon\Carbon::now()->format("Ymd_His_v");
$file_path = "C:/Users/hogehoge/test_puppeteer_{$timestamp}.png";
Browsershot::url('https://www.google.com/?hl=ja')->setOption('args', ['--no-sandbox','--disable-web-security'])->save($file_path);
testpuppeteer<実行日時>.png が作成されれば成功です
use Spatie\Browsershot\Browsershot;
$timestamp = \Carbon\Carbon::now()->format("Ymd_His_v");
$file_path = "C:/Users/hogehoge/test_puppeteer_{$timestamp}.png";
Browsershot::url('https://www.google.com/?hl=ja')
->setOption('args', ['--no-sandbox','--disable-web-security'])
->save( $file_path );
$file = file_get_contents( public_path( $file_path ) );
return response($file, 200)
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="' . $file_path . '"');
例
/home/kusanagi/.anyenv/envs/nodenv/versions/18.13.0/bin
の場合
->setNodeBinary('/home/kusanagi/.anyenv/envs/nodenv/versions/18.13.0/bin/node')
->setNpmBinary('/home/kusanagi/.anyenv/envs/nodenv/versions/18.13.0/bin/npm')
setNodeBinary でnodeのパスを明示的に指定します
Browsershot::url('https://www.google.com/?hl=ja')
->setNodeBinary('/home/kusanagi/.anyenv/envs/nodenv/versions/18.13.0/bin/node')
use Spatie\Browsershot\Browsershot;
$html = "<h1>TEST</h1>";
Browsershot::html( $html )
->setOption('args', ['--no-sandbox','--disable-web-security'])
->save('./test.pdf');
$file = file_get_contents( public_path('./test.pdf') );
return response($file, 200)
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="' . './test.pdf' . '"');
puppetter の 共有ライブラリが不足しています。
・足りない共有ライブラリを調べる
cd /YOUR-PATH-TO-PUPPETEER/puppeteer/.local-chromium/linux-818858/chrome-linux
ldd chrome
これで not found と言われている .so をインストールする必要があります。
yumコマンドでインストールしましょう。
<link rel="stylesheet" href="{{ url('/assets/css_pdf/pdfprint.css') }}">
↓ 次のようにして絶対パスに書き換えます
<link rel="stylesheet" href="{{ public_path('/assets/css_pdf/pdfprint.css') }}">
vendor/spatie/browsershot/Browsershot.php
protected function callBrowser(array $command)
{
$fullCommand = $this->getFullCommand($command);
$process = Process::fromShellCommandline($fullCommand)->setTimeout($this->timeout);
// ● この行を追加 ↓
$process->setEnv(array('LD_LIBRARY_PATH' => "/PATH/TO/YOUR/puppeteer_lib64" ));
$process->run();
こちらのパッケージを利用するともっと簡単にPDF出力することができます
composer require verumconsilium/laravel-browsershot
return \VerumConsilium\Browsershot\Facades\PDF::loadHtml('<h1>TEST印刷</h1>')
->setNodeBinary('/Users/hogehoge/.anyenv/envs/nodenv/shims/node')
->setNpmBinary('/Users/hogehoge/.anyenv/envs/nodenv/shims/npm')
->inline();
composer require h4cc/wkhtmltopdf-amd64 0.12.x
composer require barryvdh/laravel-snappy
php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"
config/snappy.php が生成されます
binary を以下のように変更します。
'pdf' => [
'enabled' => true,
'binary' => base_path('vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64'),
'timeout' => false,
'options' => [],
'env' => [],
],
web.php
Route::get('/hello', function () {
$pdf = \PDF::loadHTML('<h1>日本語の表示テストです</h1><style>h1{color:red;}</style>');
return $pdf->setOption('encoding', 'utf-8')->inline();
});
/hello でアクセスします。
これがどういう値かと言うと
「ログインを記憶する」にチェックをつけずにログインをした時にログインが有効な時間
と言い換えることができます。
変更箇所
.env
# デフォルトでは2時間
SESSION_LIFETIME=120
↓
# 4時間に変更する
SESSION_LIFETIME=240
Laravelではメール認証を行う時、すでにログインをされた状態 である必要があります
ログインしていなくてもメール認証のみ実行するように変えてみましょう
php artisan route:list
Method | URI | Name | Action | Middleware |
---|---|---|---|---|
GET|HEAD | email/verify/{id}/{hash} | verification.verify | App\Http\Controllers\Auth\VerificationController@verify | web auth signed throttle:6,1 |
ミドルウェアに auth が入っています。これがあると、ログインしていない場合は、ログイン画面へ飛ばされます。
auth ミドルウェアを外します。
app/Http/Controllers/Auth/VerificationController.php
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
↓ auth ミドルウェアをコメントアウト
public function __construct()
{
// $this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
app/Http/Controllers/Auth/VerificationController.php
/**
*
* メールアドレス確認(メソッドのオーバーライド)
*
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
*/
public function verify(Request $request)
{
$user = \App\User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new \Illuminate\Auth\Events\Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
これでokです。
例えば次のようなWHERE句に ANY を使ったサブクエリ は
SELECT
*
FROM `mail_table`
WHERE
'2020-01-15 23:59:59' > ANY ( select update_date from `sub_table` WHERE main_id = main_table.id )
↓
Laravelでは 次のように記述します。
// サブクエリ
$queryBuilder = $queryBuilder->where(function($query) {
$query->whereRaw("'2020-01-15 23:59:59' > ANY ( select update_date from `sub_table` WHERE main_id = main_table.id )");
});
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class ResetPasswordNotification extends Notification
{
public $token;
public function __construct($token)
{
$this->token = $token;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new MailMessage)
->subject('パスワードの再発行')
->line("パスワード再発行のリクエストを受け付けました。こちらのボタンを押してパスワードを再設定してください。")
->action('パスワードを再発行する', route('password.reset', $this->token))
->line('もしこのメールに心当たりがない場合はこのメールを破棄してください。');
}
}
/app/User.php
/**
* オリジナルのパスワード再発行メールを送信する
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new \App\Notifications\ResetPasswordNotification($token));
}
以上です。簡単ですね。
Laravelのマイグレーションでカラムのを追加するには、
今既にあるマイグレーションファイルは 変更せずに置いておいて、変更を記述したマイグレーションファイルを新規に作成します。
テーブル名 | カラム名 | 型 |
---|---|---|
catalogs | start_date | datetime |
↓ (例)こちらの2カラムを追加するとします。
テーブル名 | カラム名 | 型 |
---|---|---|
catalogs | start_date | datetime |
catalogs | start_dat_2 | datetime |
catalogs | start_dat_3 | datetime |
マイグレーションファイル名はなんでもいいですが、クラス名(1番目の引数)が被ってしまうとエラーになるので、注意して命名してください。
php artisan make:migration change_catalogs_table_add_2columns --table=catalogs
クラス名(class ChangeConsentformsDelBiko extends Migration)でファイルが作成されます。
成功すると 次のようなファイルが生成されます
2019_07_08_180737_change_catalogs_table_add_2columns
次のように記述します。
(複数カラムを追加する時は追加する順番に注意しましょう)
after('start_date') で start_date カラムの後ろに追加しています。
before() メソッドはうまく動作しないことがあるので、after を使いましょう。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeCatalogsTableAdd2columns extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('catalogs', function (Blueprint $table) {
$table->datetime('start_date_3')->nullable()->after('start_date')->comment('開始時間_3(開始日時_3)');
});
Schema::table('catalogs', function (Blueprint $table) {
$table->datetime('start_date_2')->nullable()->after('start_date')->comment('開始時間_2(開始日時_2)');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('catalogs', function (Blueprint $table) {
$table->dropColumn('start_date_3');
});
Schema::table('catalogs', function (Blueprint $table) {
$table->dropColumn('start_date_2');
});
}
}
コマンド | 説明 |
---|---|
$table->bigIncrements('id'); |
符号なしBIGINTを使用した自動増分ID(主キー) |
$table->bigInteger('votes'); |
BIGINTカラム |
$table->binary('data'); |
BLOBカラム |
$table->boolean('confirmed'); |
BOOLEANカラム |
$table->char('name', 100); |
オプションの文字長を指定するCHARカラム |
$table->date('created_at'); |
DATEカラム |
$table->dateTime('created_at'); |
DATETIMEカラム |
$table->dateTimeTz('created_at'); |
タイムゾーン付きDATETIMEカラム |
$table->decimal('amount', 8, 2); |
有効(全体桁数)/小数点以下桁数指定のDECIMALカラム |
$table->double('amount', 8, 2); |
有効(全体桁数)/小数点以下桁数指定のDOUBLEカラム |
$table->enum('level', ['easy', 'hard']); |
ENUMカラム |
$table->float('amount', 8, 2); |
有効(全体桁数)/小数点以下桁数指定のFLOATカラム |
$table->geometry('positions'); |
GEOMETRYカラム |
$table->geometryCollection('positions'); |
GEOMETRYCOLLECTIONカラム |
$table->increments('id'); |
符号なしINTを使用した自動増分ID(主キー) |
$table->integer('votes'); |
INTEGERカラム |
$table->ipAddress('visitor'); |
IPアドレスカラム |
$table->json('options'); |
JSONフィールド |
$table->jsonb('options'); |
JSONBフィールド |
$table->lineString('positions'); |
LINESTRINGカラム |
$table->longText('description'); |
LONGTEXTカラム |
$table->macAddress('device'); |
MACアドレスカラム |
$table->mediumIncrements('id'); |
符号なしMEDIUMINTを使用した自動増分ID(主キー) |
$table->mediumInteger('votes'); |
MEDIUMINTカラム |
$table->mediumText('description'); |
MEDIUMTEXTカラム |
$table->morphs('taggable'); |
符号なしINTERGERのtaggable_id と文字列のtaggable_type を追加 |
$table->multiLineString('positions'); |
MULTILINESTRINGカラム |
$table->multiPoint('positions'); |
MULTIPOINTカラム |
$table->multiPolygon('positions'); |
MULTIPOLYGONカラム |
$table->nullableMorphs('taggable'); |
NULL値可能なmorphs() カラム |
$table->nullableTimestamps(); |
timestamps() メソッドの別名 |
$table->point('position'); |
POINTカラム |
$table->polygon('positions'); |
POLYGONカラム |
$table->rememberToken(); |
VARCHAR(100)でNULL値可能なremember_token を追加 |
$table->smallIncrements('id'); |
符号なしSMALLINTを使用した自動増分ID(主キー) |
$table->smallInteger('votes'); |
SMALLINTカラム |
$table->softDeletes(); |
ソフトデリートのためにNULL値可能なdeleted_at TIMESTAMPカラム追加 |
$table->softDeletesTz(); |
ソフトデリートのためにNULL値可能なdeleted_at タイムゾーン付きTIMESTAMPカラム追加 |
$table->string('name', 100); |
オプションの文字長を指定したVARCHARカラム |
$table->text('description'); |
TEXTカラム |
$table->time('sunrise'); |
TIMEカラム |
$table->timeTz('sunrise'); |
タイムゾーン付きTIMEカラム |
$table->timestamp('added_on'); |
TIMESTAMPカラム |
$table->timestampTz('added_on'); |
タイムゾーン付きTIMESTAMPカラム |
$table->timestamps(); |
NULL値可能なcreated_at とupdated_at カラム追加 |
$table->timestampsTz(); |
タイムゾーン付きのNULL値可能なcreated_at とupdated_at カラム追加 |
$table->tinyIncrements('id'); |
符号なしTINYINTを使用した自動増分ID(主キー) |
$table->tinyInteger('votes'); |
TINYINTカラム |
$table->unsignedBigInteger('votes'); |
符号なしBIGINTカラム |
$table->unsignedDecimal('amount', 8, 2); |
有効(全体桁数)/小数点以下桁数指定の符号なしDECIMALカラム |
$table->unsignedInteger('votes'); |
符号なしINTカラム |
$table->unsignedMediumInteger('votes'); |
符号なしMEDIUMINTカラム |
$table->unsignedSmallInteger('votes'); |
符号なしSMALLINTカラム |
$table->unsignedTinyInteger('votes'); |
符号なしTINYINTカラム |
$table->uuid('id'); |
UUIDカラム |
$table->year('birth_year'); |
YEARカラム |
php artisan migrate
php artisan migrate:rollback
ロールバックする時にDBカラムが存在しないとエラーとなります。
そこで存在チェックを入れましょう
$table->dropColumn('text_name');
↓ ( newsテーブルに text_name カラムが存在するなら削除する)
if (Schema::hasColumn('news', 'text_name')){
$table->dropColumn('text_name');
}
php artisan queue:table
php artisan queue:failed-table
php artisan migrate
QUEUE_DRIVER=database # キュードライバを DB にする
PDF ファイルに透かしを入れるジョブを作成してみます
php artisan make:job CreatePdfWaterMark
/app/Jobs/CreatePdfWaterMark.php に実際のロジックを記述します
php artisan queue:work
常駐キューワーカの実行
nohup php artisan queue:work --daemon &
または
nohup php artisan queue:work --daemon > /dev/null 2>&1 &
nohup php artisan queue:work --daemon > ./storage/logs/laravel.log &
常駐キューワーカーの削除
ps -ef |grep artisan
次のようにプロセス番号が分かるのでプロセス番号からkillします
username 20423 17965 0 12:01 pts/0 00:00:00 php artisan queue:work --daemon
kill 20423
パッケージ laravel-sendgrid-driver をインストールすることで、
通常のSMTPサーバーを使ったメール送信からSendGridAPIを使ったメール送信に簡単に切り替えたりまた戻すことができます。
composer require s-ichikawa/laravel-sendgrid-driver
config/mail.php へ以下を追加
'mailers' => [
// ● 追加
'sendgrid' => [
'transport' => 'sendgrid',
],
config/services.php へ以下を追加
// ● 追加
'sendgrid' => [
'api_key' => env('SENDGRID_API_KEY'),
],
.env へ以下を追加
# ● SendGrid設定
MAIL_DRIVER=sendgrid
MAIL_MAILER=sendgrid
SENDGRID_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.env の以下をコメントアウト
# MAIL_MAILER=smtp
これだけで ok です。
vendor/laravel/framework/src/Illuminate/Mail/MailManager.php:404
protected function getConfig(string $name)
{
// Here we will check if the "driver" key exists and if it does we will use
// the entire mail configuration file as the "driver" config in order to
// provide "BC" for any Laravel <= 6.x style mail configuration files.
return $this->app['config']['mail.driver']
? $this->app['config']['mail']
: $this->app['config']["mail.mailers.{$name}"];
}
つまり .env の MAIL_DRIVER を見ているので、切り替えたい場合はここを変更しましょう。
SendGridを使用する場合
MAIL_DRIVER=sendgrid
SMTPサーバを使用する場合
MAIL_DRIVER=smtp
addTo() や addTos() メソッドを使用するとあるユーザに送ったメールに他のユーザのメールアドレスも見えてしまいます。
Personalizationを使用しましょう
// To (全員のアドレスが見えてしまう)
// foreach ($to_emails as $to) {
// $email->addTo( $to );
// }
// Personalization (全員のアドレスを隠す)
foreach ( $to_emails as $email_address ) {
$personalization = new Personalization();
$personalization->addTo( new To( $email_address ) );
$email->addPersonalization( $personalization );
}
// グローバルスコープを外す(クラスの場合)
User::withoutGlobalScope(AgeScope::class)->get();
// グローバルスコープを外す(クロージャで指定したスコープの場合)
User::withoutGlobalScope('myscope')->get();
// 全てのグローバルスコープを外す
User::withoutGlobalScopes()->get();
// 複数のグローバルスコープを外す
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
/app/User.php
class User extends Authenticatable
{
use \Illuminate\Notifications\Notifiable;
(実はデフォルトでこのトレイトは記述されています。何もしなくても OK です)
php artisan make:notification <クラス名>
例 (掲示板にデータがポストされた通知「BoardsPosted」を作成してみます )
php artisan make:notification BoardsPosted
/app/Notifications/BoardsPosted.php が自動作成されます。
コンストラクタに通知に使用するデータ受け取りを記述します。 (後でここで受け取ったデータを加工して使用します。)
private $board;
public function __construct( $board )
{
$this->board = $board;
}
テスト的にメール送信で通知してみます
例としてログインするたびに自分自身にメールを送信するようにしてみます。
/app/Http/Controllers/HomeController.php
public function index()
{
// ログインユーザー自身に通知する
$user = \Auth::user();
$user->notify(new \App\Notifications\BoardsPosted($user));
return view('home');
}
なお、全ユーザーに通知するには次のようにします。
// 全ユーザーに通知する
$users = \App\User::get();
\Notification::send($users, new \App\Notifications\BoardsPosted(null));
.env に smtp の設定も記述します
MAIL_MAILER=smtp
MAIL_HOST=xxx.you-server.com
MAIL_PORT=587
MAIL_USERNAME=your@host.com
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=your@host.com
これだけでログインしてホーム画面に行くだけで自分自身にメールが送信されます。 とても簡単ですね
php artisan notifications:table
php artisan migrate
このようなテーブルが作成されます
(DatabaseChannelを追加します) app/Notifications/BoardsPosted.php
public function via($notifiable)
{
return ['mail',\Illuminate\Notifications\Channels\DatabaseChannel::class];
}
チャンネルはデフォルトでは3つ「BroadcastChannel」「DatabaseChannel」「MailChannel」があります。
DBに通知するときに好きなデータを保存できます。
public function toArray($notifiable)
{
return [
'invoice_id' => 111,
'amount' => 123456,
];
}
とすると、notificationsテーブルの「data」カラムに
{"invoice_id":111,"amount":123456}
のような json が保存されます。
notificationsテーブルは次のようなレイアウトです。
morphtoMany が設定されていますね。 これは
vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php
trait Notifiable
{
use HasDatabaseNotifications, RoutesNotifications;
}
vendor/laravel/framework/src/Illuminate/Notifications/HasDatabaseNotifications.php
public function notifications()
{
return $this->morphMany(DatabaseNotification::class, 'notifiable')->orderBy('created_at', 'desc');
}
で設定されているようです。 全ての通知を notifications テーブルに集約する事ができます。 とてもとても簡単ですね。
参考 : https://laravel.com/docs/7.x/notifications#database-prerequisites
// 自分宛の通知を表示する
$user = \Auth::user();
foreach ($user->unreadNotifications as $notification) {
dump( $notification );
}
あるユーザー宛の全ての通知 と 未読の通知
$user = \Auth::user();
$notifications = $user->notifications; // あるユーザー宛の全ての通知
$notifications = $user->unreadNotifications; // あるユーザー宛の未読の通知
1件のデータに紐づいている全ての通知
$notifications = $model->notifications; // 1件のデータに紐づいている全ての通知
$notification->markAsRead(); // 既読にする
未読の通知を既読にするには次のようにしてもOKです。
if ( $notification->read_at === null ){
$notification->update(['read_at' => now()]); // 既読日時をセット
}
else {
dump( '既に既読です' );
}
bladeで通知数を表示する
{{ @Auth::user()->unreadNotifications->count() }} 件のお知らせがあります。
Blade だと次のように記述します。
<h3>{{ count($notifications) }}件の通知があります</h3>
@foreach ($notifications as $notifications)
{{$notifications->id}} : {{$notifications->type}} <br>
@endforeach
引用 : https://bit.ly/3ewBWzT
にあるように、notificationsテーブルは拡張しておきましょう。
php artisan make:migration change_notifications_table_add_2columns --table=notifications
マイグレーションファイルの編集 database/migrations/2020_xx_xx_xxxxx_change_notifications_table_add_2columns.php
public function up()
{
Schema::table('notifications', function (Blueprint $table) {
$table->unsignedBigInteger('resource_id')->after('type');
$table->string('resource_type')->after('type');
});
}
public function down()
{
Schema::table('notifications', function (Blueprint $table) {
$table->dropColumn('resource_id');
$table->dropColumn('resource_type');
});
}
app/Notifications/MyDatabaseChannel.php
DatabaseChannel を拡張してオリジナルの MyDatabaseChannel を作成します。
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Channels\DatabaseChannel;
class MyDatabaseChannel extends DatabaseChannel
{
protected function buildPayload($notifiable, Notification $notification)
{
return [
'id' => $notification->id,
'type' => get_class($notification),
'data' => $this->getData($notifiable, $notification),
'resource_type' => $notification->resource->getMorphClass() ?? null,
'resource_id' => $notification->resource->getKey() ?? null,
'read_at' => null,
];
}
}
app/Notifications/BoardsPosted.php も変更します。
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class BoardsPosted extends Notification
{
use Queueable;
public $resource;
public function __construct( $resource )
{
$this->resource = $resource;
}
public function via($notifiable)
{
return [\App\Notifications\MyDatabaseChannel::class];
}
public function toMail($notifiable)
{
return (new MailMessage)
->line('システムからのお知らせです。')
->action('Notification Action', url('/'))
->line('それではどうぞよろしくお願いいたします');
}
public function toArray($notifiable)
{
return [];
}
}
データに対して
「unread_notifications」で未読の通知が、
「notifications」で全ての通知が
取得できます。
app/Notification.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Notification extends Model
{
protected $table = 'notifications';
protected $guarded = ['id', 'created_at', 'updated_at'];
protected static function boot()
{
parent::boot();
}
}
通知対象のモデル側 app/Board.php に「フック」と「リレーション」と「メソッド」を追加
protected static function boot()
{
parent::boot();
/**
* ● フック : deleted() 削除時にモデルに紐づく全ての「通知」を削除する
*/
static::deleted(function ($model) {
foreach ($model->notifications as $notification) {
$notification->delete();
}
});
}
/**
* ● (Notification) 1対多 ポリモーフィックリレーション : ->unread_notifications で書き込みに紐づく「現在ログイン中ユーザーが未読の通知」を取得します。
*
* ソート順 : sort_no , DESC
*/
public function unread_notifications()
{
return $this->morphMany('App\Notification', 'resource')->where('read_at','=',null)->where('notifiable_type','=','App\User')->where('notifiable_id','=',optional(\Auth::user())->id)->orderBy('created_at','ASC');
}
/**
* ● (Notification) 1対多 ポリモーフィックリレーション : ->notifications で書き込みに紐づく全ての通知を取得します
* ソート順 : sort_no , DESC
*/
public function notifications()
{
return $this->morphMany('App\Notification', 'resource')->orderBy('created_at','ASC');
}
/**
* ● メソッド : ->is_notified('App\User',1) id=1 のユーザーに対して通知を完了したかどうかを返します。
*/
public function is_notified( $class_name, $id )
{
foreach ($this->notifications as $k => $v) {
if ( $v->notifiable_type === $class_name && $v->notifiable_id === $id ){
return true;
}
}
return false;
}
これでコントローラーから次のようにして「ある書き込み」が「あるユーザーに対して」通知済みかどうかを知る事ができます。
またモデルのデータを削除したときにそれに紐づく全てのDB通知も削除されます。
// id = 26 の board
$board = \App\Board::find(26);
if ( $board->is_notified('App\User',1) ){
dump( '1 のユーザーには通知済み' );
}
if ( $board->is_notified('App\User',2) ){
dump( '2 のユーザーには通知済み' );
}
作成する場所はお好きなところどこでもOKです。
Config.php
<?php
namespace Mysettings;
class Config
{
public $name = "アイウエオ";
}
composer.json へ パス "./" を追加します
"autoload": {
"classmap": [
"./"
],
設定ファイルの再出力
composer dump-autoload
シングルトンとして登録するだけです。
app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(
'Illuminate\Contracts\Auth\Registrar',
'App\Services\Registrar'
);
// この辺りに追記
$this->app->singleton('\Mysettings\Config', function()
{
return new \Mysettings\Config();
});
}
// 設定情報を取得
$config = app()->make('Mysettings\Config');
dump( $config );
$config->clinic_name = '001';
dump( $config );
// シングルトンであることの確認
$config2 = app()->make('Mysettings\Config');
dd( $config2 );
以上です。 Laravelだと簡単ですね!
$dt_from = new \Carbon\Carbon();
$dt_from->startOfMonth();
$dt_to = new \Carbon\Carbon();
$dt_to->endOfMonth();
$reports = Report::whereBetween('report_date', [$dt_from, $dt_to])->get();
$queryBuilder->whereRaw( "last_checkup_date >= DATE_SUB( CURDATE(),INTERVAL 10 DAY )" );
( hyn/multi-tenant ( ★ 2296 )の後継 )
https://packagist.org/packages/tenancy/tenancy
https://packagist.org/packages/stancl/tenancy
✔️ No model traits to change database connection
✔️ No replacing of Laravel classes (Cache, Storage, ...) with tenancy-aware classes
✔️ Built-in tenant identification based on hostname (including second level domains)
ルート名でリダイレクトします。
/reports/999/edit のようなURLへリダイレクトします。
return redirect()->route('reports.edit', ['report'=>$report->id, '_back_uri'=>$q['_back_url']]);
Group::with('users')->get();
↓
Group::with('users:id,name')->get();
idは必ず含める必要があります
Group::with('users:id,name as user__name')->get();
@php
$email = "test@user.com";
$email2 = preg_replace_callback('/./', function($m) {
return '&#'.ord($m[0]).';';
}, $email );
@endphp
<a href="mailto:{!! $email2 !!}" target="_blank">{{ $email }}</a>
(あまりインストールやSTARが多くないパッケージなので慎重に。)
composer require saeed/laravel-login-log
php artisan vendor:publish --tag=Login-log
php artisan migrate
テーブル「LoginLog 」が作成されます。
https://packagist.org/packages/mnabialek/laravel-sql-logger
laravel-sql-loggerのインストール
composer require mnabialek/laravel-sql-logger
laravel-sql-loggerのインストール(開発環境のみインストールする場合は後ろに --dev をつけます)
composer require mnabialek/laravel-sql-logger --dev
SQL_LOGGER_DIRECTORY="logs/sql"
SQL_LOGGER_USE_SECONDS=false
SQL_LOGGER_CONSOLE_SUFFIX=
SQL_LOGGER_LOG_EXTENSION=".sql"
SQL_LOGGER_ALL_QUERIES_ENABLED=true
SQL_LOGGER_ALL_QUERIES_OVERRIDE=false
SQL_LOGGER_ALL_QUERIES_PATTERN="#.*#i"
SQL_LOGGER_ALL_QUERIES_FILE_NAME="[Y-m-d]-log"
SQL_LOGGER_SLOW_QUERIES_ENABLED=true
SQL_LOGGER_SLOW_QUERIES_MIN_EXEC_TIME=100
SQL_LOGGER_SLOW_QUERIES_PATTERN="#.*#i"
SQL_LOGGER_SLOW_QUERIES_FILE_NAME="[Y-m-d]-slow-log"
SQL_LOGGER_FORMAT_NEW_LINES_TO_SPACES=false
SQL_LOGGER_FORMAT_ENTRY_FORMAT="/* [origin]\\n Query [query_nr] - [datetime] [[query_time]] */\\n[query]\\n[separator]\\n"
npm install --save laravel-echo socket.io-client
npm install -g laravel-echo-server
SSL鍵ファイルの場所を調べておく(nginx の場合)
cat /etc/nginx/conf.d/[YOUR-DOMAIN-NAME]_ssl.conf | grep cert
初期化 (Laravelのプロジェクトトップに移動してから初期化します)
cd [Laravelのプロジェクトトップ]
laravel-echo-server init
次のように質問に答えます( YOUR-SERVER.COM は適宜読み替えてください。 )
? Do you want to run this server in development mode? Yes
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. https://YOUR-SERVER.COM/
? Will you be serving on http or https? https
? Enter the path to your SSL cert file. /etc/letsencrypt/live/YOUR-SERVER.COM/fullchain.pem
? Enter the path to your SSL key file. /etc/letsencrypt/live/YOUR-SERVER.COM/privkey.pem
? Do you want to generate a client ID/Key for HTTP API? No
? Do you want to setup cross domain access to the API? No
? What do you want this config to be saved as? laravel-echo-server.json
完了すると laravel-echo-server.json が作成されます。
サーバーの6001ポートのファイアーウォールを空けておきます。
sudo yum install -y epel-release
sudo yum install -y redis
redisのバージョン確認
redis-server --version
redisの起動
redis-server
/opt/eff.org/certbot/venv/bin/letsencrypt certonly -a webroot --webroot-path=[site document root] --agree-tos --email [your email] --config-dir="/home/[user]/letsencrypt/etc" --domains [site domain]
redis-server
laravel-echo-server init
laravel-echo-server start
App\Providers\BroadcastServiceProvider::class, // ON
BROADCAST_DRIVER=log
↓ 変更
BROADCAST_DRIVER=redis
php artisan make:event PublicEvent
app/Events/PublicEvent.php が自動生成されますので以下の内容で保存します。
app/Events/PublicEvent.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PublicEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct()
{
}
public function broadcastOn()
{
return new Channel('public-event');
}
public function broadcastWith()
{
return [
'message' => 'PUBLIC',
];
}
}
routes/web.php
Route::get('/test-public-event', function(){
broadcast(new \App\Events\PublicEvent);
return 'test-public-event';
});
import Echo from "laravel-echo"
window.io = require('socket.io-client')
window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'http://' + window.location.hostname + ':6001'
})
//購読するチャネルの設定
window.Echo.channel('public-event')
.listen('PublicEvent', (e) => {
console.log(e);
});
npm install
npm run dev
composer require owen-it/laravel-auditing
config/app.php
'providers' => [
// ...
OwenIt\Auditing\AuditingServiceProvider::class, // laravel-auditing
],
php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="config"
php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="migrations"
php artisan migrate
class Memo extends Model
↓
use OwenIt\Auditing\Contracts\Auditable;
class Memo extends Model implements Auditable
{
// ========== Trait ==========
use \OwenIt\Auditing\Auditable;
// ========== Trait ==========
implements Auditable を追加し use \OwenIt\Auditing\Auditable でトレイトを追加します、 これだけで自動的に更新履歴がテーブル audits に保存されます。
// Get first available Article
$memo = Article::first();
// Get all associated Audits
$all = $memo->audits;
// Get first Audit
$first = $memo->audits()->first();
// Get last Audit
$last = $memo->audits()->latest()->first();
// Get Audit by id
$audit = $memo->audits()->find(4);
Laravel で HTMLをminifyして高速化する laravel-minify , Laravel HTMLMinを使用する
composer require fahlisaputra/laravel-minify
php artisan vendor:publish --provider="Fahlisaputra\Minify\MinifyServiceProvider"
app/Http/Kernel.php
protected $middleware = [
.....
// laravel-minify
\Fahlisaputra\Minify\Middleware\MinifyCss::class,
\Fahlisaputra\Minify\Middleware\MinifyJavascript::class,
\Fahlisaputra\Minify\Middleware\MinifyHtml::class,
];
composer require htmlmin/htmlmin
config/app.php の aliases に 以下を追加
'HTMLMin' => HTMLMin\HTMLMin\Facades\HTMLMin::class, // Laravel HTMLMin
php artisan vendor:publish
リストが表示されるので HTMLMin の番号を押す(以下の例の場合は6をタイプします。)
[6 ] Provider: HTMLMin\HTMLMin\HTMLMinServiceProvider
config/htmlmin.php が生成されていればOKです。
.env に以下を追加する
HTMLMIN_ENABLED=true # HTMLMinを(有効/無効)にする
config/htmlmin.php を以下のように書き換える
'blade' => false,
↓
'blade' => env('HTMLMIN_ENABLED'),
HTMLMinはキャッシュファイルを作成しますので、設定を変えたときはキャッシュをクリアする必要があります。
php artisan view:clear;
call メソッドから呼ぶことができます。
\Illuminate\Support\Facades\Artisan::call('hoge:fuga');
\Illuminate\Support\Facades\Artisan::call('hoge:fuga',['param1'=> 'value1','param2'=>'value2']);
<meta name="csrf-token" content="{{ csrf_token() }}">
postメソッドを次のように書き換えます
axios.post(url, postdata)
↓
axios.post(url, postdata,{
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
})
app/Http/Kernel.php
$middleware に以下を追加します
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
........
\App\Http\Middleware\EncryptCookies::class, // \Auth::user() を取得するために追加
\Illuminate\Session\Middleware\StartSession::class, // \Auth::user() を取得するために追加
];
1対1
1対多
多対多
Has Many Through
1対1(ポリモーフィック)
1対多(ポリモーフィック)
多対多(ポリモーフィック)
これらのうち 多対多ポリモーフィック・リレーションを操作してみます。
「チームデータ」を複数のテーブルにつける。のをイメージするととても簡単です。 まずは2つのテーブルをつなぐピボットテーブルを作成すればOKです。 さらに、ポリモーフィック・リレーションの場合はテーブル名(テーブルのクラス名)も保存します。 (これにより「チームデータ」は色々なテーブルと多対多リレーションさせることができます。)
・posts(テキスト投稿データ)
id - integer
name - string
・videos(ビデオ投稿データ)
id - integer
name - string
・teams(チームデータ)
id - integer
name - string
というテーブルを多対多ポリモーフィック・リレーションでつなぐ時は
テーブル ↓
・teamaccessible
team_id - integer
teamaccessible_id - integer
model_name - string
を作成します。
マイグレーションファイルの作成
php artisan make:migration create_teamaccessible_table
マイグレーションファイルの up() メソッドは次のようになります。
public function up()
{
Schema::create('teamaccessibles', function (Blueprint $table) {
$table->integer('team_id');
$table->integer('teamaccessible_id');
$table->string('teamaccessible_type');
});
}
多対多リレーションの時は belongsToMany でしたが、 多対多ポリモーフィック・リレーションの場合は morphToMany を設定します。
これを トレイトに 定義して、複数のテーブルから使用します。
例) app/TeamAccessibleTrait.php に設定
/**
* ● 多対多ポリモーフィック・リレーション
*
* ->teams でモデルに紐づけられたチーム一覧を取得します。
*
* ピボットテーブル「teamaccessible」
*
* @return \Illuminate\Database\Eloquent\Relations\morphToMany
*/
public function teams()
{
return $this->morphToMany('App\Team', 'teamaccessible');
}
モデルでトレイトを呼び出します
use \App\TeamAccessibleTrait; // チームでアクセス可能なデータに限定する
1.
use Illuminate\Console\DetectsApplicationNamespace;
↓ 削除する
// use Illuminate\Console\DetectsApplicationNamespace; // コメントアウトまたは削除
2.
// use DetectsApplicationNamespace;
↓ 削除する
// use DetectsApplicationNamespace; // コメントアウトまたは削除
3.
$this->getAppNamespace()
↓ 次のように置換
\App::getNamespace()
php artisan make:controller Api/CategoryController --api
app/Http/Controllers/API/CategoryController.php が自動生成されます。
routes/api.php
// API Category
Route::group(['middleware' => ['api']], function () {
Route::resource('articles', 'Api\CategoryController');
});
php artisan route:list
| GET|HEAD | api/categories | categories.index
| POST | api/categories | categories.store
| GET|HEAD | api/categories/create | categories.create
| GET|HEAD | api/categories/{category} | categories.show
| PUT|PATCH | api/categories/{category} | categories.update
| DELETE | api/categories/{category} | categories.destroy
| GET|HEAD | api/categories/{category}/edit | categories.edit
app/Http/Controllers/API/CategoryController.php
index()
public function index()
{
$categories = \App\Category::all();
return $categories;
}
return $categories; のところは次のようにしてもいいでしょう
return $categories->toJson(JSON_UNESCAPED_UNICODE);
store()
public function store(Request $request)
{
$category = new \App\Category;
$category->fill( $request->all() )->save();
return $category;
// return response('store OK', 200);
}
どのような値を返すのかはフロントエンドの実装によります。 上の例では作成したモデルをjsonで返しています。
show()
public function show($id)
{
$category = \App\RakutenCategory::find($id);
return $category;
}
update()
public function update(Request $request, $id)
{
$category = \App\RakutenCategory::findorFail($id);
$category->fill( $request->all() )->save();
return $category;
// return response('update OK', 200);
}
destroy()
public function destroy($id)
{
$category = \App\RakutenCategory::findorFail($id);
$category->delete();
return $category;
// return response('delete OK', 200);
}
これで完成です。 あとはフロントエンド( Angular / React / Vue.js )からガリガリ操作してください。
<table border="0"><tr><td style="width:200px; height:200px;"></td></tr></table>
<table style="padding:10px;">
とすると、テーブル内のセルに対して一括で padding を指定することができます。
逆に言うとこれ以外の方法では指定ができないようです。
composer create-project --prefer-dist laravel/laravel my_app 5.4
cd my_app
php artisan -V
/bootstrap/cache/config.php を削除する
php artisan config:cache を行うと キャッシュファイル /bootstrap/cache/config.php が生成され .envファイルを読み込まなくなります 。
なので、これを削除する事でエラーは回避できます。
env()はconfigファイル内でしか使わない事
コントローラーや Console コマンドでは使わないようにしましょう。
laravel-cors はLaravel のバージョン7からデフォルトでインストールされるようになりましたのでわざわざインストールしなくても OK です
barryvdh/laravel-cors のインストール
composer require barryvdh/laravel-cors
app/Http/Kernel.php の $middleware に以下を追加
api のみ適用したい!と思いますが、 middleware に入れてしまって、後から設定で URL を絞り込みます。
protected $middleware = [
// ...
\Fruitcake\Cors\HandleCors::class,
];
namespace が なぜか \Fruitcake\Cors\HandleCors ですが、これで okです。
設定ファイルを以下のコマンドで自動作成する。
php artisan vendor:publish --tag="cors"
config/cors.php
'paths' => ['api/*'],
'allowed_origins_patterns' => ['/localhost:?[0-9]*/'],
allowed_origins_patterns は preg_match に渡す、正規表現用文字列です。 上記の設定では localhost や localhost:8080 などを通すようにしています。
これで /api/ 以下のみ クロスドメインアクセス が有効になりました。
config/cors.php
// cor-test を追加
'paths' => ['cor-test', 'api/*'],
routes/web.php
// COR-TEST
Route::get('/cor-test', function () {
return response()->json([
'message' => 'cor-test-ok'
]);
});
これを設定して、 axios などからアクセスします。
axios.get('/cor-test', { withCredentials: true })
.then(response => {
console.log('cor-test ok:', response.data);
})
.catch(error => {
console.log('cor-test error:', error);
});
Laravel6 で ユーザー認証(Auth)機能を作成する|プログラムメモ
composer require laravel/passport
php artisan migrate
こちらのテーブルが作成されます。
php artisan passport:install
次のような結果が返ってきます
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client secret: S37VP14Txxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Password grant client created successfully.
Client ID: 2
Client secret: MCAW4tmxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
「client_id: 1」が、「5. Personal Access Client」
「client_id: 2」が、「2. Password Grant Client」
です。
Laravel Passportの認証には次の種類があるようです。
・1. OAuth2 with authorization codes (Auth Code)→SNS認証でよく使うやつ(確認画面あり・Code発行あり)
・2. Password Grant Token(確認画面無し。Codeなし。ID, PWでTokenを発行)
・3. Implicit Grant Token(いきなりTokenを発行。安全性に問題?)
・4. Client Credentials Grant Token(マシン間通信向け。個人の認証無し)
・5. Personal Access Token(その名の通り、個人・内部利用向け)
引用 : http://bit.ly/2G9or9H
http://bit.ly/2Gb9WSJ
app/User.php
// ===== Trait =====
use HasApiTokens, Notifiable;
// ===== Trait =====
// Laravel Passport 追加
\Laravel\Passport\Passport::routes();
// Laravel Passport 追加
config/auth.php
'api' => [
// Laravel Passport へ変更 token → passport
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
php artisan config:cache
php artisan tinker
App\User::create(['name' => 'test', 'email' => 'test@test.com', 'password' => bcrypt('test')]);
トークン作成を管理者が行う場合はこのようにバックエンドで作成するだけでOKです。
php artisan tinker
(id=1 の User のトークンを作成します。)
echo \App\User::find(1)->createToken('my_token')->accessToken;
トークンが表示されるのでコピーします。 なお、一番最後に改行コードがついてくるので、改行コードはコピーしないよう 注意してください。
Postmanでは以下のようにjsonを送信します。
成功すると json でトークンが帰ってきます
これらのクライアントを使用するといいでしょう。
https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm
Headers を次のようにして GET でアクセスします
KEY | VALUE |
---|---|
Accept | application/json |
Authorization | Bearer <取得したトークン> |
Bearer の後にスペースをつけて トークンを入力して GET でアクセスします。
成功すると次のようなjsonが取得できます
{
"id": 1,
"name": "test",
"email": "test@test.com",
"email_verified_at": null,
"created_at": "2020-01-20 21:18:21",
"updated_at": "2020-01-20 21:18:21"
}
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
public function login(Request $request) {
$credentials = $request->only('email', 'password');
if(auth()->attempt($credentials)) {
$user = auth()->user();
$token = $user->createToken('my_token')->accessToken;
return ['access_token' => $token];
}
return response([
'message' => 'Unauthenticated.'
], 401);
}
}
// 最後に追加
Route::post('/login', 'Api\AuthController@login');
Params を次のようにして POST でアクセスします
KEY | VALUE |
---|---|
emai | test@test.com |
password | test |
成功すると、トークンが帰ってきます。
php artisan passport:client --personal
実行すると以下を聞かれます
1. クライアント名
What should we name the personal access client? [Laravel Personal Access Client]:
> myclient
Personal access client created successfully.
Client ID: 3
Client secret: mI5Q25xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
php artisan passport:client --password
Windows IE11 でも文字化けしないようにutf-8 エンコードします。
$content に データを入れておきます。mime_type も取得しておきます。
$disk = \Storage::disk('local');
$file_path = 'tmp/myfile.pdf';
$content = $disk->get( $file_path );
$mime_type = $disk->mimeType( $file_path );
日本語ファイル名をセットしてダウンロードさせます。
$filename = '日本語pdfファイル名.pdf';
$headers = [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename*=UTF-8\'\''.rawurlencode($filename)
];
return \Response::make($content, 200, $headers);
$content に データを入れます
$content = <<< DOC_END
==================================
テキスト内容
==================================
DOC_END;
$filename = 'サンプルテキスト.txt';
$headers = [
'Content-type' => 'text/plain',
'Content-Disposition' => sprintf('attachment; filename="%s"', $filename),
];
return \Response::make($content, 200, $headers);
composer require webpatser/laravel-uuid
マイグレーションファイルに次のように uuid 型のカラムを追加します
( 実際には CHAR(36)として作成されます )
ユニーク制約をつける場合
$table->uuid('uuid')->after('id')->unique()->comment('uuid');
nullを許可する場合
$table->uuid('uuid')->after('id')->nullable()->comment('uuid');
/app/UuidTrait.php
<?php
namespace App;
use Webpatser\Uuid\Uuid;
trait UuidTrait
{
public static function bootUuidTrait()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate()->string;
});
}
}
モデルファイルに以下を追加
use \App\UuidTrait;
Route::get("posts/share/{uuid}", "MemoController@share")->name('posts.share')->where([ 'id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' ]);
/posts/share/05eface0-edc911eb-bfda-2f6c633e3f11 というようなURLでアクセスすることができます
public function rules()
{
$ignore_email_array = [ 'aaa.com', 'bbb.com', 'ccc.com' ];
return [
'email' => [
'required', 'email', 'confirmed',
// ===== 独自ルール(指定したドメインは除外する)
function ($attribute, $value, $fail) use ($ignore_email_array) {
foreach ($ignore_email_array as $ignore_domain) {
$pattern = '/' . preg_quote($ignore_domain, '/') . '/';
if (preg_match($pattern, $value) !== 0) {
return $fail("{$ignore_domain} ドメインのメールアドレスはご登録いただけません");
}
}
} ,
// ===== 独自ルール(指定したドメインは除外する)
],
];
}
// DBのTimezoneを変更
\DB::select("SET time_zone = '+09:00'");
app/Providers/AppServiceProvider.php に 記述します。
public function boot()
{
// DBのTimezoneを変更
\DB::select("SET time_zone = '+09:00'");
}
開始タイマーと終了タイマー二つを作成します
( DBのカラム view_start_date が 現在時刻を超えたデータのみに限定する )
/**
* ● ローカルスコープ : ->withinStartTime() で 「表示タイマー開始」を現在時刻が過ぎた slide に限定する
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithinStartTime( \Illuminate\Database\Eloquent\Builder $query )
{
return $query->whereNull('view_start_date')
->orWhere(function($query) {
$query->whereNotNull('view_start_date')
->where('view_start_date','<=', \DB::raw('NOW()') );
});
}
( DBのカラム view_end_date が 現在時刻を超えていないデータのみに限定する )
/**
* ● ローカルスコープ : ->withinEndTime() で 「表示タイマー終了」を現在時刻が過ぎていない slide に限定する
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithinEndTime( \Illuminate\Database\Eloquent\Builder $query )
{
return $query->whereNull('view_end_date')
->orWhere(function($query) {
$query->whereNotNull('view_end_date')
->where('view_end_date','>',\DB::raw('NOW()') );
});
}
表示タイマー内のデータに限定する
$data_loop = $model->withinStartTime()->withinEndTime()->get();
$collection = collect();
$collection_hash = [
'id' => 1 ,
'name' => 'テスト太郎' ,
];
// Collectionになければ追加
if ( ! $collection->contains( $collection_hash ) ){
$collection->push( $collection_hash );
}
$collection->map(function ($d) {
dump($d->name);
});
composer require tightenco/collect
代わりに php artisan ui vue --auth コマンドが追加されました。
composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev
composer require laravel/ui
npm install
php artisan ui vue --auth
npm install && npm run dev
Laravel で最初にやっておいた方が良い初期設定|プログラムメモ
php artisan migrate
https://YOUR-SERVER.TLD/login
app/User.php
class User extends Authenticatable
{
↓
class User extends Authenticatable implements MustVerifyEmail
{
routes/web.php
Auth::routes();
↓
Auth::routes([
'verify' => true, // メール確認機能(※5.7系以上のみ)
'register' => true, // デフォルトの登録機能ON
'reset' => true, // メールリマインダー機能ON
]);
auth だと本登録しなくてもログインできてしまうので authの後に verified を追加します
Route::group(['middleware' => ['auth']], function () {
Route::get("file/downloadlocalfile/", "FrontFileController@downloadlocalfile")->name("file.downloadlocalfile");
});
↓ auth verified に変更する
Route::group(['middleware' => ['auth', 'verified']], function () {
Route::get("file/downloadlocalfile/", "FrontFileController@downloadlocalfile")->name("file.downloadlocalfile");
});
app/Http/Controllers/Auth/LoginController.php
/**
* ユーザーが認証された後の処理
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(\Illuminate\Http\Request $request, $user)
{
if( $user->email_verified_at === null ){
return redirect()->route('verification.notice', ['message'=>'メールアドレス認証が完了していません']);
}
}
app/Http/Controllers/Auth/VerificationController.php
public function resend( \Illuminate\Http\Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return $request->wantsJson()
? new Response('', 204)
: redirect($this->redirectPath());
}
$request->user()->sendEmailVerificationNotification();
return $request->wantsJson()
? new Response('', 202)
: back()->with('resent', true);
}
また、確認メールを送信したかどうかは ビューファイル resources/views/auth/verify.blade.php の中で
@if (session('resent') == true )
確認メールを送信しました。(再送信する場合はクリック)
@else
確認メール再送信
@endif
のようにして取得できます。
zip -r cms_app.zip cms_app --exclude=*/.git/* --exclude=*/___bak/* --exclude=*/___* --exclude=cms_app/storage/debugbar/* --exclude=cms_app/storage/app/public/* --exclude=cms_app/storage/app/*
unzip.php を使用する
<?php
ini_set("max_execution_time", 300);
ini_set("max_input_time", 300);
$zip_file = 'cms_app.zip'; // 展開するzipファイルを指定
$zip = new ZipArchive;
if ($zip->open("$zip_file") === true) {
$zip->extractTo('./');
$zip->close();
echo 'zip解凍に成功しました。';
} else {
echo 'zip解凍に失敗しました。';
}
if ( is_file($zip_file) ){
unlink($zip_file) or die(" / ファイル {$zip_file} を削除できませんでした。");
echo " / ファイル {$zip_file} を削除しました。";
}
https://qiita.com/takaday/items/b992c7d8cd69343b6626
こちらの方法がとても良いです。
bootstrap/app.php
switch ($_SERVER['SERVER_NAME'] ?? 'localhost') {
case 'development.co.jp':
$app->loadEnvironmentFrom('.env.dev');
break;
case 'staging.co.jp':
$app->loadEnvironmentFrom('.env.stg');
break;
case 'production.co.jp':
$app->loadEnvironmentFrom('.env.prod');
break;
}
news_date が format() メソッドを使えるように、モデルファイルに
protected $dates = ['news_date'];
を記述してから、
// 方法 1.
$model = \App\News::inActive();
$all_loop = $model->get();
$all_loop->map(function ($v) {
$v['year'] = optional($v->news_date)->format("Y");
});
$group_news_loop = $all_loop->groupBy('year');
最下層のオブジェクトは Eloquentモデルではなく配列になります。
なのでこの方法はあまりお勧めしません。
$all_loop = \DB::table('news')
->select(\DB::raw("*,DATE_FORMAT( news_date ,'%Y') as year"))
->get();
$group_news_loop = $all_loop->groupBy('year');
dd( $group_news_loop );
Laravel6 の 場合は helpers をインストールする必要があります。
composer require laravel/helpers
public.html?{{ str_random(8) }}
↓
public.html?yX4R50rl
とある事情によりキャッシュファイルの権限を 0777 にする必要があった時の変更方法
app/Providers/AppServiceProvider.php
public function boot()
{
// ↓ この行を追加
umask(0);
}
です。 少し力技ですが。。。
\Route::currentRouteName();
blade内では次のように確認します。
@php
$now_route = \Route::currentRouteName();
dump( $now_route );
@endphp
@php
$current_url = Request::url();
dump( $current_url );
@endphp
http://localhost/hoge/fuga?id=12345 の時 http://localhost/hoge/fuga が返ります(クエリパラメータは帰ってきません)
routes/web.php に info.show という名前をつける
// お知らせ
Route::get("info/{id}", "FrontInfoController@show")->name("info.show");
// $info = \App\Info::findOrFail(999);
<a href="{{ route('info.show', $info->id) }}" >お知らせのタイトル</a>
マイグレーションファイル名はなんでもいいですが、クラス名(1番目の引数)が被ってしまうとエラーになるので、注意して命名してください。
php artisan make:migration change_catalogs_add_title_index --table=catalogs
クラス名(class ChangeConsentformsDelBiko extends Migration)でファイルが作成されます。
成功すると 次のようなファイルが生成されます
2019_09_28_180737_change_catalogs_add_title_index
このファイルを次のように記述します。
public function up()
{
Schema::table('consentforms', function (Blueprint $table) {
$table->index('title'); // この行を追加
});
}
public function down()
{
Schema::table('consentforms', function (Blueprint $table) {
$table->dropIndex(['title']); // この行を追加(配列で渡す)
});
}
マイグレーションの実行
php artisan migrate
実行するとインデックス catalogs_title_index が作成されます。
MySQLのTEXT型の場合はインデックスのサイズを指定する必要があります。(最大255 bytes)
テーブル : artists
カラム : yomi_name
の場合
Schema::table('artists', function (Blueprint $table) {
DB::statement('CREATE INDEX artists_yomi_name_index ON artists (yomi_name(100));');
});
とします。
composer require jenssegers/agent
config/app.php
Jenssegers\Agent\AgentServiceProvider::class, // providerに追加
'Agent' => Jenssegers\Agent\Facades\Agent::class, // aliasに追加
$agent->is('Windows');
$agent->is('Firefox');
$agent->is('iPhone');
$agent->is('OS X');
$agent->isAndroidOS();
$agent->isNexus();
$agent->isSafari();
$agent->isMobile();
$agent->isTablet();
$agent->match('regexp');
ユーザー(User)モデルなどのパスワードを変えたいけれど、まだインターフェースがない場合は、Tinkerからパスワードのハッシュを生成します。
php artisan tinker
ある、ユーザーのパスワードを hogehoge にしたい場合は次のように入力します
echo Hash::make('hogehoge');
これで ハッシュ化されたパスワードが表示されますのでこれを直接データベースに登録します。
app/Http/Middleware/VerifyCsrfToken.php に追加します。
( admin/apidebug/* ) のルートでは CSRF チェックをオフにします。
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
protected $addHttpCookie = true;
protected $except = [
'stripe/*',
'admin/apidebug/*', // ここに追加
];
}
JSON で API データを渡す時などにパスワードなどを含めたくないということが多々あります。
Laravelでは一撃で書くことができます。
$json = $model->toJson();
↓ makeHiddenメソッドを挟みます。
$json = $model->makeHidden(['password','remember_token'])->toJson();
$json = $model->toJson();
↓ toJsonメソッドにオプションを渡します。
$json = $model->toJson(JSON_UNESCAPED_UNICODE);
簡単ですね!
Laravelではフックというとても便利な機能があります。
これはデータベースのデータに更新や削除などデータ操作があった際に好きな 処理を挟み込むことができます。
またトレイトも使用できますのでコントローラーから分離することもできます。
https://laravel.com/docs/5.8/eloquent#events
(イベント)の項目を参照
https://readouble.com/laravel/5.8/ja/eloquent.html
Eloquentモデルは多くのイベントを発行します。creating、created、updating、updated、saving、saved、deleting、deleted、restoring、restored、retrievedのメソッドを利用し、モデルのライフサイクルの様々な時点をフックすることができます
一番簡単な Trait を使ったフックを紹介します。
/app/WebApiTrait.php
<?php
namespace App;
trait WebApiTrait
{
public static function bootWebApiTrait()
{
static::created(function ($model) {
dd( 'created hook', $model );
});
static::updated(function ($model) {
dd( 'updated hook', $model );
});
static::deleted(function ($model) {
dd( 'deleted hook', $model );
});
}
}
Userモデルに追加してみましょう
<?php
namespace App;
use App\Notifications\UserResetPassword;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ===== Trait フック(この行を追加) =====
use WebApiTrait;
// ===== / Trait フック(この行を追加) =====
これだけです。
これだけでDBデータに「新規登録」「更新(実際にデータ更新があった時)」「削除」
の時に dd(); が動いて、ダンプして停止します。
実際のロジックはdd() を書き換えればいいでしょう。
モデルの boot()メソッド内 に次のように追加します。
// Hook
static::created(function ($model) {
dd( 'created hook', $model );
});
static::updated(function ($model) {
dd( 'updated hook', $model );
});
既に \App\Events などにイベントが作成してある場合は
// events
protected $dispatchesEvents = [
'updated' => \App\Events\MyEvent::class,
];
でディスパッチするだけです。簡単ですね。
// 変更があったかどうかの検知
$model->isDirty(); // true , false
// 変更内容
$d = $model->getDirty(); // 配列
// 変更があったかどうかの検知
$model->wasChanged(); // true , false
// 変更内容
$d = $model->getChanges(); // 配列
resources/views/admin/inc/disk_info.blade.php
@php
$d = `df -hT`;
$d_array = preg_split("/\n/",$d);
$disk_info = $d_array[0];
$disk_info = preg_replace("/Mounted on/","MountedOn",$disk_info);
$disk_info = preg_replace("/\s+/"," ",$disk_info);
$disk_headers = preg_split("/\s/",$disk_info);
$disk_data = [];
foreach ($d_array as $k => $v) {
if ( preg_match("{/$}",$v) ) {
$v = preg_replace("/\s+/"," ",$v);
$v = preg_replace("{ /$}","",$v);
$disk_data_array = preg_split("/\s/",$v);
array_push($disk_data,$disk_data_array);
}
}
// dd( $disk_data );
echo "<table class='table table-striped table-bordered table-condensed' style='max-width:400px;'>\n";
// 1. ヘッダ
echo "<tr>\n";
foreach ($disk_headers as $v) {
if ( strcmp($v, 'MountedOn') == 0 ){ continue; }
if ( strcmp($v, 'Avail') == 0 ){ $v = "残り容量"; }
echo "<th>{$v}</th>";
}
echo "</tr>\n";
// 2. データ
echo "<tr>\n";
foreach ($disk_data as $row) {
foreach ($row as $v) {
echo "<td>{$v}</td>";
}
}
echo "</tr>\n";
echo "</table>\n";
@endphp
他のBladeから呼び出します
@include('admin.inc.disk_info')
同じ user_id を持つデータを1つ(後ろにある方を有効)にする
$reports_diet = $reports->keyBy('user_id');
$unique = $collection->unique();
https://readouble.com/laravel/7.x/ja/eloquent-collections.html#method-contains
exceptメソッドは、指定した主キーを持たないモデルをすべて返します。
$users = $users->except([1, 2, 3]);
わざわざ別のクラスを作成したりトレイトに外出ししなくてもLaravel では簡単に別のコントローラーのパブリックメソッド実行することができます
App\Http\Controllers\User\MypageController の createParam メソッドを実行してみます
なお呼び出されるメソッドは public である必要があります
$mypage_controller = app()->make('App\Http\Controllers\User\MypageController');
$user_param = $mypage_controller->createParam( $hogehoge );
composer require doctrine/dbal
Laravelのマイグレーションでカラムの名前と型を変更するには、
今既にあるマイグレーションファイルは 変更せずに置いておいて、変更を記述したマイグレーションファイルを新規に作成します。
テーブル名 | カラム名 | 型 |
---|---|---|
artists | year_birth_no | smallint |
↓ (例)こちらに変更するとします。
テーブル名 | カラム名 | 型 |
---|---|---|
artists | year_birth_no_name | string |
マイグレーションファイル名はなんでもいいです。
php artisan make:migration change_artists_table_column_year_birth_no --table=artists
成功すると 次のようなファイルが生成されます
2019_07_08_180737_change_artists_table_column_year_birth_no
以下のように変更用の命令と戻し用の命令を記述しておきます。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeArtistsTableColumnYearBirthNo extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// カラム名を変更
Schema::table('artists', function (Blueprint $table) {
$table->renameColumn('year_birth_no', 'year_birth_no_name');
});
// 型を変更
Schema::table('artists', function (Blueprint $table) {
$table->string('year_birth_no_name')->default(NULL)->change();
});
// カラム「fax_name」を「string型」「nullを許可」に変更
$table->string('fax_name')->nullable()->change();
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// 型を戻す
Schema::table('artists', function (Blueprint $table) {
$table->smallInteger('year_birth_no_name')->change();
});
// カラム名を戻す
Schema::table('artists', function (Blueprint $table) {
$table->renameColumn('year_birth_no_name','year_birth_no');
});
// カラム「fax_name」を「string型」「nullを許可しない」に変更
$table->string('fax_name')->nullable(false)->change();
}
}
(注意)tinyIntegerでは実行できません。 ↓ を参照
composer require doctrine/dbal
php artisan migrate
php artisan migrate:rollback
composer require "doctrine/dbal:2.*"
Schema::table('applications', function (Blueprint $table) {
// nullを許可に変更
DB::statement('ALTER TABLE users MODIFY COLUMN is_active tinyint COMMENT \'フラグ\'');
});
if (strpos(php_sapi_name(), 'cli') !== false) {
// Run from command
}
if (strpos(php_sapi_name(), 'cli') === false) {
// Run from web
}
laravel-responsecacheは ララベルのミドルウェアとして動作するキャッシュクラスです。 Laravelのルーティングを通る時に動作する( テンプレートファイルの更新すら確認しない)ので とても高速に動作します
composer require spatie/laravel-responsecache
php artisan vendor:publish --provider="Spatie\ResponseCache\ResponseCacheServiceProvider"
↑ このコマンドを実行すると
/config/responsecache.php ファイルが生成されます。
キャッシュをミドルウェア(アクセスのたびに自動的に呼び出されるクラス)にセットします。
既存の $routeMiddleware の一番最後に追加します。
app/Http/Kernel.php
protected $routeMiddleware = [
...
'cacheResponse' => \Spatie\ResponseCache\Middlewares\CacheResponse::class, // 追加
'doNotCacheResponse' => \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class, // 追加
];
次に routes/web.php のキャッシュを適応したいルートをこちらのミドルウェアで囲みます。
Route::group(['middleware' => 'cacheResponse'], function () {
Route::get("/", function (Request $request) {
// 何かしらの処理
});
});
以上でキャッシュは実行されています。
確認してみましょう。
.env の以下の記述があるか確認しましょう
APP_DEBUG=true
app/ResponsecacheTrait.php を以下の内容で新規作成します
<?php
namespace App;
use Spatie\ResponseCache\Facades\ResponseCache;
trait ResponsecacheTrait
{
public static function bootClearsResponseCache()
{
self::created(function () {
ResponseCache::clear();
});
self::updated(function () {
ResponseCache::clear();
});
self::deleted(function () {
ResponseCache::clear();
});
}
}
Laravelアプリで使用する全てのモデルファイルに以下のトレイトを追加します
例 : app/news.php に追加します
class News extends Model
{
// ===== Trait =====
use \App\ResponsecacheTrait; // ● 追加
// ===== Trait =====
これで DBにデータを「新規登録」「変更」「削除」したときにキャッシュがクリアされます。
こちらのコマンドで実際にWEBアプリのベンチマークを3回ずつとってみたところ
ab -n 100 -c 100 https://TEST-SITE.TLD/
Requests per second: 9.19 [#/sec] (mean)
Requests per second: 7.54 [#/sec] (mean)
Requests per second: 6.50 [#/sec] (mean)
↓
Requests per second: 80.19 [#/sec] (mean)
Requests per second: 73.79 [#/sec] (mean)
Requests per second: 76.89 [#/sec] (mean)
約9.9倍 速くなりました !!
開発時にこのミドルウェアが入っていると 一時的にキャッシュをオンにしたりオフにしたり する必要が出てきますそこで設定を .env ファイルに設定しておき そこで簡単に切り替えられるようにします
.env
RESPONSE_CACHE_FLAG=true
routes/web.php
// キャッシュミドルウェア( .env の RESPONSE_CACHE_FLAG が true の場合 キャッシュ on)
$cache_middleware = [];
if ( env('RESPONSE_CACHE_FLAG',false) == true ){
$cache_middleware = ['middleware' => 'cacheResponse'];
}
Route::group($cache_middleware, function () {
Route::get("/", function (Request $request) {
........... 何かしらの処理
});
});
https://laravel.com/docs/5.8/helpers
if ( $user->touroku_date ){
echo $user->touroku_date->format("Y年n月j日");
}
↓ このように記述できます。
echo $accountId = optional($user->touroku_date)->format("Y年n月j日");
オブジェクトが存在しない場合は null が返ります。
便利ですね
例
受診データ (hasOne) -> 患者 (hasMany)-> 担当医(複数)
こちらの例のように、リレーション先の患者にさらにリレーションで担当医が複数いる場合の担当医の数を取得します。
$data_loop = $model->with('patient')->get();
↓ このように追加します
$data_loop = $model->with(['patient' => function($query){
$query->with('doctors');
}])->get();
$data_loop = $model->with(['patient' => function($query){
$query->withCount('doctors');
}])->get();
● Laravel で リレーション先のさらに先のリレーションの件数を検索条件にする。
「doctors を2人以上持つ」「patient」のデータを取得する
$data_loop = $model->with(['patient' => function($query){
$query->with('doctors');
}])->has('patient.doctors','>',1)get();
本番環境にデプロイするときに複数のリポジトリからデータを取ってくる必要があったりだとか
何かシェルコマンドを実行する必要があったりだとか処理が多い場合はタスクランナーを是非使いましょう。
Envoyは シンプルでとても使いやすいタスクランナーです。
(Laravel がインストールされていなくても使用することができます)
https://readouble.com/laravel/5.8/ja/envoy.html
https://laravel.com/docs/5.8/envoy
composer global require laravel/envoy
パスを通します。
cd
vi .bash_profile
次の内容を追加
# laravel / envoy
export PATH="~/.composer/vendor/bin:$PATH"
envoy -v
次のようにバージョンが帰って来ればインストール成功です
Laravel Envoy 1.5.0
Usage:
command [options] [arguments]
(ディレクトリはどこでもokです。自分で好きなディレクトリを作成し、そのディレクトリ内で Envoy コマンドを作成して実行できます。)
ディレクトリの作成
mkdir test
cd test
envoyコマンドファイルの作成 (ローカルで作業する場合)
envoy init 127.0.0.1
envoyコマンドファイルの作成 ( 他のサーバに対して作業する場合)
envoy init [user]@[host]
実行するとカレントディレクトリに Envoy.blade.php が生成されます。
少し編集して以下のようにします。
@servers(['web' => '127.0.0.1'])
@task('deploy')
cd /path/to/site
git pull origin master
@endtask
@task('hoge')
ls -la
@endtask
上の例ではタスク deploy と hoge が登録されています。
envoy run hoge
実行結果
[127.0.0.1]: total 8
[127.0.0.1]: drwxr-xr-x 3 akimitsu staff 96 6 26 22:44 .
[127.0.0.1]: drwxr-xr-x+ 45 akimitsu staff 1440 6 26 22:43 ..
[127.0.0.1]: -rw-r--r--@ 1 akimitsu staff 144 6 26 22:44 Envoy.blade.php
このようにタスクを登録して使用します。
envoy tasks
複数のタスクをまとめてストーリーとして登録しておいてそのストーリーを 実行することができます。
Envoy.blade.php ファイル内に以下を記述
@story('story_deploy_all__dev')
task_deploydev_cms
task_deploydev_html
@endstory
envoy run story_deploy_all__dev
composer global update laravel/envoy --with-all-dependencies
// validation(自動リダイレクト)
$request->validate([
$this->username() => 'required|string',
'password' => 'required|string',
]);
↓ このように書き換えます。
// validation(チェックのみ)
$validator = Validator::make($request->all(), [
$this->username() => 'required|string',
'password' => 'required|string',
]);
// validation エラーがある場合、エラーメッセージをダンプする
if ($validator->fails()) {
dd( $validator->messages()->toArray() );
}
これで、とりあえず バリデーションエラーがあるときはエラーメッセージが表示されます。 好きな動作を ダンプメソッド dd( $validator->messages()->toArray() ); のところに書き込めば自由にカスタマイズできます。
DBマイグレーションする前にLaravelのモデルの複数形のチェックは行いましょう。
php artisan tinker
として tinker を立ち上げて次のコマンドで調べます。
echo str_plural('複数形を調べたい単語');
次のように記述をしてもオッケーです
echo echo Str::plural('複数形を調べたい単語');
( Laravel 6 以降 )
echo Str::plural('book');
( Laravel 5 )
echo str_plural('book');
→ 結果 : books
echo str_plural('blog');
→ 結果 : blogs
echo str_plural('information');
→ 結果 : information
echo str_plural('news');
→ 結果 : news
return \App::abort(404);
$data['title'] = '404';
$data['name'] = 'Page not found';
return response()->view('errors.404',$data,404);
次のうちどちらかを行いコンポーザのオートローダーを高速化しておきましょう。
● composer を updateする時に パスを絶対パスに書き換えて高速化する
composer --optimize-autoloader update
● composer の autoloader の パスを絶対パスに書き換えて高速化する
composer dumpautoload -o
次のコマンドを実行してLaravelのクラスマップを最適化し高速化しておきましょう
php artisan optimize
↑ 上記コマンドで php artisan config:cache , php artisan route:cache が実行されます。
private function renderString($string, $data)
{
$php = \Illuminate\Support\Facades\Blade::compileString($string);
$obLevel = ob_get_level();
ob_start();
extract($data, EXTR_SKIP);
try {
eval('?' . '>' . $php);
} catch (Exception $e) {
while (ob_get_level() > $obLevel) ob_end_clean();
throw $e;
} catch (Throwable $e) {
while (ob_get_level() > $obLevel) ob_end_clean();
throw new FatalThrowableError($e);
}
return ob_get_clean();
}
$user = \App\User::first();
$text = "こんにちは {{$user->name}} さん";
$html = $this->renderString($text, compact('user'));
dump( $html );
{{$user->name}}
としてください。
{{ $user->name }}
ではエラーになる場合があります。
コントローラーのコンストラクタで変更しても、フォームのエラーメッセージには適用されないので、ミドルウェアで locale を設定します
php artisan make:middleware Language
app/Http/Middleware/Language.php が自動で 作成されるので以下のように書き換えます。
<?php
namespace App\Http\Middleware;
use Closure;
class Language
{
public function handle($request, Closure $next)
{
$now_url = request()->fullUrl();
$pattern_zh = '/' . preg_quote( env('BASE_URL_ZH') , '/') . '/';
$pattern_ja = '/' . preg_quote( env('BASE_URL_JA') , '/') . '/';
$pattern_en = '/' . preg_quote( env('BASE_URL_EN') , '/') . '/';
if ( preg_match($pattern_zh, $now_url) ){
\Illuminate\Support\Facades\App::setLocale( 'zh-TW' );
}
elseif ( preg_match($pattern_ja, $now_url) ){
\Illuminate\Support\Facades\App::setLocale( 'ja' );
}
elseif ( preg_match($pattern_en, $now_url) ){
\Illuminate\Support\Facades\App::setLocale( 'en' );
}
return $next($request);
}
}
app/Http/Kernel.php
$middlewareGroups のリストに追加します。
これでWEBアクセスの時には必ず読み込まれます。
protected $middlewareGroups = [
'web' => [
......
......
\App\Http\Middleware\Language::class, // ● この行を追加
],
いろいろやり方はあるかと思いますが今回は .env ファイルに 他言語のサイトのベース URL を設定して、 その「ベースURLにマッチする言語を現在の言語」と判別させてみます
.env
BASE_URL_JA=https://ja.YOUR-SITE.TLD
BASE_URL_ZH=https://zh.YOUR-SITE.TLD
BASE_URL_EN=https://en.YOUR-SITE.TLD
これでURLが「https://zh.YOUR-SITE.TLD」の場合は自動的にロケール「zh-TW」がセットされるようになりました。
https://github.com/caouecs/Laravel-lang
ここから太陽市大言語のリソースファイルを入手して resources/lang に コピーすれば多言語対応は完了です。
app/Providers/AppServiceProvider.php に以下を追加します
public function boot()
{
// 767bytes問題の対応
Schema::defaultStringLength(191);
// notice を解除 ● ↓ これを追加 ●
error_reporting(E_ALL ^ E_NOTICE);
}
https://readouble.com/laravel/9.x/ja/collections.html
laravel のモデルの検索結果 collection をさらに検索する時に便利なメソッドを紹介します。
sortByメソッドは指定したキーでコレクションをソートします。
$sorted = $collection->sortBy('price');
このメソッドの使い方はsortByと同じで、コレクションを逆順にソートします。
firstメソッドは指定された真偽テストをパスしたコレクションの最初の要素を返します。
composer show | grep carbon
nesbot/carbon 2.17.1 A simple API extension for DateTime.
(バージョン2以上である事を確認します。)
\Carbon\Carbon::setLocale('ja_JP');
$dt = new \Carbon\Carbon();
return $dt->isoFormat('YYYY.M.D (dddd)');
結果例
2019.5.30 (木) 木曜日
\Carbon\Carbon::setLocale('en-US');
$dt = new \Carbon\Carbon();
return $dt->isoFormat('YYYY.M.D (dddd)');
結果例
2019.5.30 (Thursday)
\Carbon\Carbon::setLocale('zh-TW');
$dt = new \Carbon\Carbon();
return $dt->isoFormat('YYYY.M.D (dddd)');
結果例
2019.5.30 (星期四)
$dt->isoFormat("YYYY年MM月DD日 HH:mm:ss dddd"); // 2019年05月10日 12:34:56 水曜日
moment.js と互換があるそうなので、こちらを参考にするといいです。
Laravel シーダー実行時に、sql文(mysqlumpなどのダンプファイル)を実行したい。
という要件は結構あったりします。
Laravelならとても簡単にできます。
通常のデータベースシーダー ( database/seeds/MyTableSeeder.php )
<?php
use Illuminate\Database\Seeder;
class LangdicTableSeeder extends Seeder {
public function run()
{
\DB::table("mytable")->insert([
'id' => 1 ,
'name' => "ichitaro suzuki" ,
]);
}
}
↓ このように書き換えます
SQL文を実行するデータベースシーダー ( database/seeds/MyTableSeeder.php )
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class LangdicTableSeeder extends Seeder
{
public function run()
{
$path = 'database/sql/mytable_2019-05-20.sql';
\DB::unprepared(file_get_contents($path));
}
}
sqlファイルをここにおきます。 database/sql/mytable_2019-05-20.sql'
シーダーを実行します
php artisan migrate:fresh --seed
以上です。簡単ですね。
yum install -y graphviz
composer require beyondcode/laravel-er-diagram-generator --dev
php artisan generate:erd
graph.png が生成されるので、これを見てみましょう。
https://qiita.com/mya-zaki@github/items/e4f0514b320460ae64c5
$zip = new \ZipArchive;
if ($zip->open($disk->path("zip/{$save_file_basename}.zip")) === TRUE) {
$zip->extractTo($disk->path("zip/{$save_file_basename}"));
$zip->close();
} else {
die('次のzipファイルが開けません:' . $disk->path("zip/{$save_file_basename}.zip"));
}
php artisan make:provider HelperServiceProvider
app/Providers/HelperServiceProvider.php が自動生成されます
public function register()
{
// ここから追加
foreach (glob(app_path().'/Helpers/*.php') as $filename){
require_once($filename);
}
// ここまで追加
}
'providers' => [
...
...
// ここを追加
App\Providers\HelperServiceProvider::class ,
],
(ディレクトリ権限エラーが出る場合は実行してください。)
chmod 0777 bootstrap/cache
作成場所は app/Helpers/myhelper.php です。 myhelper は 好きな名前に変更してください。
if (!function_exists('myhelper')) {
/**
* 自作ヘルパー関数
*/
function myhelper( $arg1=null, $arg2=null, $arg3=null, $arg4=null, $arg5=null )
{
return 'test';
}
}
Laravelデフォルトでは、 パスワード再設定時にメールアドレスが同じユーザーの最初に検索されたユーザーに対して
パスワード変更が行われます。
これのユーザー検索するためのDB検索カラムを増やして見ましょう。
例として shop_id と email が同じユーザーで検索するように変更して見ましょう。 ( shop_id を追加します)
app/Http/Controllers/UserAuth/ResetPasswordController.php を変更します。
( Laravel デフォルトの authの場合は app/Http/Controllers/Auth/ )
/**
* オーバーライド用に (vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php) からコピー
*/
protected function credentials(Request $request)
{
// ● shop_id をプラスする
return $request->only(
'shop_id', 'email', 'password', 'password_confirmation', 'token'
);
}
/**
* オーバーライド用に (vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php) からコピー
* $shop_id は ルーターから受け取る
*/
public function reset(Request $request, int $shop_id )
{
// ● request に shop_id をプラスする
$request->request->add(['shop_id' => $shop_id]);
$request->validate($this->rules(), $this->validationErrorMessages());
$user = \App\User::where("shop_id","=",$shop_id)->where("email","=",$request->email)->first();
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($request, $response)
: $this->sendResetFailedResponse($request, $response);
}
以上です。
簡単ですね。
public function up()
{
Schema::create('dics', function(Blueprint $table) {
$table->string('id')->primary(); // ● id を string にする
$table->text('name')->nullable()->comment('テキスト');
$table->integer('sort_no')->unsigned()->comment('ソート順');
$table->timestamps();
$table->primary('id');
});
}
class dics extends Model
{
public $incrementing = false;
}
フォームをかなりの時間スクリーン上に置いておいた後に送信するときに、フォームがタイムアウトしないようにします。 (Laravelのデフォルトは120分ですが、これは設定可能であり、サイトごとに異なる可能性があります。)
Laravelはもちろん(Laravel に限らず PHP で PDO を使っている場合)DBの数値型カラムが文字列型で返ってくる事があります。
なぜこのような自動型変換が起こるかというと、PHPのMySQL PDOドライバの仕様だそうです。
ただし、次の条件の時に
1. mysqlのドライバーが 「mysqlnd(MySQLNaitiveDriver)」である。
2. PDO::ATTR_EMULATE_PREPARES属性が false である。
自動型変換を回避することが可能です。
phpinfo() の出力から確認しましょう。
または次のコマンドで mysqlnd が表示されれば使用されています
php -m | grep mysqlnd
$dbh = \DB::connection()->getPdo();
$attributes = [
"ATTR_CLIENT_VERSION",
"ATTR_EMULATE_PREPARES",
"ATTR_STRINGIFY_FETCHES",
"ATTR_AUTOCOMMIT",
"ATTR_ERRMODE",
"ATTR_CASE",
"ATTR_ORACLE_NULLS",
"ATTR_PERSISTENT",
"ATTR_PREFETCH",
"ATTR_SERVER_INFO",
"ATTR_SERVER_VERSION",
"ATTR_TIMEOUT",
];
foreach ($attributes as $val) {
echo "PDO::{$val}: ";
try {
echo $dbh->getAttribute(constant("PDO::{$val}")) . "<br>\n";
} catch (\Exception $e) {
echo "error not supported !!! <br>\n";
}
}
結果例
PDO::ATTR_CLIENT_VERSION: mysqlnd 5.0.12-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $
PDO::ATTR_EMULATE_PREPARES: 0
↑ この例の場合は(mysqlnd有り)(PDO::ATTR_EMULATE_PREPARES: 0)なので、数値型カラムの値は数値型で返ってきます。
レンタルサーバで(おそらく昔の)XSERVERとかは mysqlnd が入ってないので、自動型変換は必ず起きると思われます。
Laravel の モデルには $casts プロパティがあり、これに型をセットすると、Eloquentが結果セットを返すときに、明示的にその方にキャストしてくれます。
/**
* 明示的なdb型変換
*
*/
protected $casts = [
'id' => 'int' ,
'shop_id' => 'int' ,
'is_active' => 'int' ,
'price_no' => 'int' ,
];
次のコードを実行してください。自動で生成します。( int のみ。) コントローラーから次のようなメソッドを実行します
/**
* Laravelのモデルの Casts を生成する
*
* [int, integer, real, float, double, string, bool, boolean, object, array, json, collection, date, datetime]
*
*
*/
public function getModelCasts(string $table_name = '')
{
print "<hr>\n";
echo "<strong>{$table_name}</strong>";
print "<hr>\n";
echo "<pre style='margin: 20px; border: solid #eee 1px;'>";
print <<< 'DOC_END'
/**
* カラムの明示的なdb型変換
*
*/
protected $casts = [
DOC_END;
$query = "SHOW COLUMNS FROM {$table_name}";
foreach (\DB::select($query) as $column) {
if (preg_match("{^int}", $column->Type)) {echo "'{$column->Field}' => 'int' ,\n";}
elseif (preg_match("{^tinyint}", $column->Type)) {echo "'{$column->Field}' => 'int' ,\n";}
elseif (preg_match("{^smallint}", $column->Type)) {echo "'{$column->Field}' => 'int' ,\n";}
elseif (preg_match("{^mediumint}", $column->Type)) {echo "'{$column->Field}' => 'int' ,\n";}
elseif (preg_match("{^bigint}", $column->Type)) {echo "'{$column->Field}' => 'int' ,\n";}
elseif (preg_match("{^(text|char|varchar)}", $column->Type)) {}
elseif (preg_match("{^}", $column->Type)) {}
else {
dump($column);
}
}
print <<< 'DOC_END'
];
DOC_END;
echo "</pre>";
print "<hr>\n";
}
.env
(YOUR-NAME)(YOUR-PASSWORD)を適宜書き換えてください。
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=YOUR-NAME@gmail.com
MAIL_PASSWORD=【gmail2段階認証のアプリパスワード】
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=YOUR-NAME@gmail.com
MAIL_FROM_NAME=デフォルトサイト名
gmail2段階認証のアプリパスワード は通常のログインパスワードとは別に作成する必要があります。
https://myaccount.google.com/security
コントローラー
// テキストメール送信
$mail_subject = "メールのタイトルテスト";
$mail_content = "メールの本文です\nテスト";
$to_email = "customer@user.com";
\Mail::send([], [], function($message) use ($from_email, $from_name, $mail_subject, $mail_content, $to_email ) {
$message->to( $to_email );
$message->subject( $mail_subject );
$message->setBody($mail_content);
});
users テーブルのIDを5000番から始める場合は次のように記述します。
// SET auto-increment start value
DB::statement("ALTER TABLE users AUTO_INCREMENT = 5000;");
$inserted_flag = $model->insert($insert_data);
↓ メソッド insertGetId() を使用します
$inserted_id = $model->insertGetId($insert_data);
insertGetId()
テーブルが自動増分IDを持っている場合、insertGetIdメソッドを使うとレコードを挿入し、そのレコードのIDを返してくれます。
その他 : INSERT時に使用するメソッド一覧 http://bit.ly/2PGUl1h
// テキストメール送信
$from_email = "shop@test.server.com";
$from_name = "ショップ名";
$mail_subject = "ホームページからお問い合わせがありました。";
$mail_content = "メールの本文です\nテスト";
$to_email = "customer@user.com";
\Mail::send([], [], function($message) use ($from_email, $from_name, $mail_subject, $mail_content, $to_email ) {
$message->from( $from_email, $from_name );
$message->to( $to_email );
$message->subject( $mail_subject );
$message->setBody($mail_content);
});
// htmlメール送信
$from_email = "shop@test.server.com";
$from_name = "ショップ名";
$mail_subject = "ホームページからお問い合わせがありました。";
$mail_content = "<h1>メールの本文です</h1>";
$to_email = "customer@user.com";
\Mail::send([], [], function($message) use ($from_email, $from_name, $mail_subject, $mail_content, $to_email ) {
$message->from( $from_email, $from_name );
$message->to( $to_email );
$message->subject( $mail_subject );
$message->setBody($mail_content, 'text/html');
});
メール送信方式は .env の値を参照しに行きます sendmail を使って送信する場合の設定
MAIL_FROM_ADDRESS=test@user.com
MAIL_FROM_NAME=サイト名
MAIL_DRIVER=sendmail
MAIL_HOST=localhost
.env の メール設定を取得するには config() を使用します
$mailconfig = config('mail');
dump( $mailconfig );
メール設定を動的に変更する
$mailconfig = [];
$mailconfig['driver'] = 1;
$mailconfig['host'] = 2;
$mailconfig['port'] = 3;
$mailconfig['username'] = 4;
$mailconfig['password'] = 5;
$mailconfig['encryption'] = null;
// メール設定の変更
$mailconfig = config(['mail' => $mailconfig ]);
https://putsmail.com/tests/new
こちらからhtmlメール送信が行えます。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="robots" content="noindex">
<title>htmlメールのタイトル</title>
</head>
<body>
<h1>見出し1</h1>
<h2>見出し2</h2>
<h3>見出し3</h3>
<h4>見出し4</h4>
<h5>見出し5</h5>
<a href="https://www.google.co.jp/" target="_blank">GoogleのHP</a>
</body>
</html>
127.0.0.1
123.123.123.0/24
からのアクセスのみ許す場合
php artisan down --allow=127.0.0.1 --allow=123.123.123.0/24 --message="現在メンテナンス中です"
php artisan up
Laravel アプリ内に複数の index.php が存在する場合は
{{ env('APP_URL') }}
をお勧めします。
.env にURLを記述します。
明示的に public ディレクトリを指定するときには asset('/') をおすすめします。
asset('/') はスラッシュ終わりの場合に最後にスラッシュがつきます。
url('/') はスラッシュ終わりの場合に最後にスラッシュがつきません。
{{ asset('/') }}
{{ url('/') }}
↓
https://YOUR-SERVER.TLD/
https://YOUR-SERVER.TLD
また YOUR-SERVER.TLD のところは、実際に駆動させているサーバ名 が入ります。( .env の APP_URL ではありません )
ファイル名まで指定する場合はどちらも同じです。
{{ asset('/assets/js/jquery-1.7.2.min.js') }}
{{ url('/assets/js/jquery-1.7.2.min.js') }}
↓
https://YOUR-SERVER.TLD/assets/js/jquery-1.7.2.min.js
https://YOUR-SERVER.TLD/assets/js/jquery-1.7.2.min.js
{!! app_path() !!}
{!! base_path() !!}
{!! config_path() !!}
{!! database_path() !!}
{!! public_path() !!}
{!! resource_path() !!}
{!! storage_path() !!}
/PATH/TO/LARAVEL_APP/app
/PATH/TO/LARAVEL_APP
/PATH/TO/LARAVEL_APP/config
/PATH/TO/LARAVEL_APP/database
/PATH/TO/LARAVEL_APP/public
/PATH/TO/LARAVEL_APP/resources
/PATH/TO/LARAVEL_APP/storage
LoginController.php
public $redirectTo = '/myhome/'; // ここを変更する
LoginController.php
public function redirectPath()
{
return '任意のurl';
}
元にいたページは次のようにして取得できます。
$path = \Session::pull('url.intended');
LoginController.php
public function redirectPath()
{
$path = \Session::pull('url.intended');
return $path;
}
LoginController.php
次の行を追加する。( /mylogin にリダイレクトする )
/**
* ログアウトしたときの画面遷移先
*/
protected function loggedOut(\Illuminate\Http\Request $request)
{
return redirect('/mylogin');
}
app/Http/Middleware/RedirectIfAuthenticated.php
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/myhome'); // ここを変更する
}
// バリデーションエラーがない場合の処理
if ( \Session::get('errors') == null ){
// ...
// ...
// ...
}
https://stackoverflow.com/questions/30817249/create-plan-on-stripe-through-laravel
https://stripe.com/docs/api/plans/create
use \Stripe\Plan;
Plan::create(array(
"amount" => 2000,
"interval" => "month",
"name" => "Amazing Gold Plan",
"currency" => "usd",
"id" => "gold")
);
POSTメソッドのみのルーティング
Route::post("order/input", "OrderController@input")->name('order.input');
↓
GET , POSTメソッドを受け付けるルーティング
Route::match(['get', 'post'],"order/input", "OrderController@input")->name('order.input');
例)POSTメソッドの時だけバリデーションするようにします
if ( $request->isMethod('post') ){
// バリデーション
$this->validate( $request, $this->customer_info );
}
htmlタグをエスケープする場合
(通常こちらを使用します)
{!! nl2br(e($shop->my_text)) !!}
エスケープしない場合
{!! nl2br($shop->my_text) !!}
Laravelアプリケーションではmysql の sql_mode はどうなっているのでしょうか?
$sql_mode = DB::select( 'SHOW VARIABLES LIKE "%sql_mode%"' );
dump($sql_mode);
戻り値()
ONLY_FULL_GROUP_BY,
STRICT_TRANS_TABLES,
NO_ZERO_IN_DATE,
NO_ZERO_DATE,
ERROR_FOR_DIVISION_BY_ZERO,
NO_AUTO_CREATE_USER,
NO_ENGINE_SUBSTITUTION
app/config/database.php
'mysql' => [
'driver' => 'mysql',
.........
// これをコメントアウトOFF 'strict' => true,
],
この状態で sql_mode を調べると
$sql_mode = DB::select( 'SHOW VARIABLES LIKE "%sql_mode%"' );
dump($sql_mode);
戻り値()
NO_AUTO_CREATE_USER,
NO_ENGINE_SUBSTITUTION
となります。
sql_mode を追加するには modes に記述してあげます。
app/config/database.php を以下のように変更します
// 'strict' => true, // OFF
'modes' => [
//'ONLY_FULL_GROUP_BY', // OFF
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'NO_ZERO_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
'NO_AUTO_CREATE_USER',
'NO_ENGINE_SUBSTITUTION'
],
app/Http/Controllers/Auth/LoginController.php に以下のメソッドを追加します
/**
* ログイン認証カラムを増やす
*
*/
protected function credentials(Request $request)
{
// ログインに必要なすべてのパラメーターが渡っているかチェックする
if (! $request->has(['email', 'shop_id', 'password']) ) {
throw new \Exception("ログインに必要なパラメーターが渡されていません");
}
return $request->only('email', 'shop_id', 'password');
}
ログインに必要なすべてのパラメーターが渡っているかチェックしないと非常に危険です。
必ずチェックしてください。
Smartyで言う所のmb_truncateは
echo str_limit($report->report_honnin_name, $limit = 80, $end = '...');
という風に記述します。
htmlタグをエスケープする場合は
echo htmlspecialchars( str_limit($report->report_honnin_name, $limit = 80, $end = '...') );
$request を使う場合
$request->session()->put('contact', $request->contact);
Session を使う場合
use Illuminate\Support\Facades\Session;
Session::put('contact', 'hogehoge');
ヘルパーを使う場合
session(['contact' => 'hogehoge' ]);
$request を使う場合
$request->session()->get('contact');
Session を使う場合
\Session::get('contact', 'xxx'); // 取得できない場合 xxx を返す
ヘルパーを使う場合
session('contact');
// $request を使う場合
if ( ! $request->session()->has('_old_input') ){ ..... }
// \Session を使う場合
if ( ! \Session::has('_old_input') ){ ..... }
// session() を使う場合
if ( ! session()->has('_old_input'); ){ ..... }
// $request を使う場合
$request->session()->forget('contact');
// \Session を使う場合
\Session::forget('contact');
// session() を使う場合
session()->forget('contact');
$request->session()->flush();
\Session::flush();
$session__all = \Session::all();
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{$page_title or ''}}</title>
</head>
<body>
@yield('content')
</body>
</html>
{{-- 継承元 --}}
@extends('user.layout_user')
{{-- ページタイトル --}}
@php
$page_title = '記事詳細'
@endphp
{{-- コンテンツ --}}
@section('content')
<div>ページ内容</div>
@endsection
(先頭は大文字です)
php artisan make:controller InfoController
app/Http/Controllers/InfoController.php が自動で作成されます。
例: 外字ファイルが eudc.tte の場合
「eudc.tte」→「eudc.ttf」
git clone https://github.com/tecnickcom/tcpdf
cd tcpdf/tools
php ./tcpdf_addfont.php -b -t eudc -f 32 -i eudc.ttf
これでフォルダ tcpdf/fonts にファイル
eudc.ctg.z
eudc.php
eudc.z
が生成されます。
これらの3つのファイルをtcpdfを動作させるサーバの好きなディレクトリにコピーします。
$pdf = new Fpdi\TcpdfFpdi();
$font_2 = $pdf->AddFont( "eudc", "", "/YOUR/SERVER/PATH/fonts/eudc.php", true );
これで
<span style="font-family:eudc">◆◆◆</span>
(◆ が外字 )
で表示されます。 外字文字のところだけ、このCSSをあててあげます。
Laravel の モデルにアクセサを登録します。
/**
* アクセサー : PDF生成時に外字文字のみCSSを追加します。
*/
public function getPdfGaijiNameAttribute()
{
if ( $this->attributes['name'] != null ){
$text = $this->attributes['name'];
$encoding = "UTF-8";
$textLength = mb_strlen($text, $encoding);
for($i = 0; $i < $textLength; $i++) {
$m = mb_substr($text, $i, 1, $encoding);
if (preg_match('/^(\xEE[\x80-\xBF])|(\xEF[\x80-\xA3])|(\xF3[\xB0-\xBF])|(\xF4[\x80-\x8F])/', $m)){
echo '<span style="font-family:eudc">' . $m ."</span>";
}
else {
echo $m;
}
}
} else {
return 'ERROR: getPdfGaijiNameAttribute()';
}
}
使い方
{{$user->name}}
↓ の代わりに次のように記述します。
{{$user->pdf_gaiji_name}}
$table->bigIncrements('id');
これだけで unsigned, auto increment , primary key 設定になります。
app/Http/Kernel.php
「TrimStrings」( フォーム入力値の前後の空白を自動的にトリムする)
「ConvertEmptyStringsToNull」( フォーム入力値の空文字をnullに自動的に変換する)
を削除します。
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
// コメントアウト \App\Http\Middleware\TrimStrings::class,
// コメントアウト \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
Laravel Mix とは 何のことはない、webpack をラッパーしたものです。
webpackは何かというと、javascript や css を圧縮したり1つのファイルにまとめたり、古いブラウザでも使えるように変換するものです。
どういうときに使うかというと、複数のcssやjsを1つにまとめる , cssやjsファイルの圧縮 などを行いたい時です。
composer create-project "laravel/laravel" my_app
cd my_app
npm install
これで準備はOKです。
ファイル webpack.mix.js をエディタで開き編集します。
mix.js( 'public/assets/js/mylib.js', 'public/assets/js_dist');
mix.styles([
'public/css/aa.css',
'public/css/bb.css'
], 'public/css/all.css');
mix.sass('resources/assets/sass/style.scss', 'public/css');
テスト環境(ソースを圧縮せず)で実行する
npm run dev
本番環境環境(ソースを圧縮して)で実行する
npm run prod
npm run watch
解凍ファイルが更新されるたび自動でLaravel mixが実行されます
laravel-db-snapshotsを使用すると、Laravel で DBの構造とデータをまとめてバックアップ / リストア することができます。 (データのみの取得はできないようです。)
composer コマンドでインストールします
composer require spatie/laravel-db-snapshots
インストールを確認します。
php artisan
snapshot
snapshot:create Create a new snapshot.
snapshot:delete Delete a snapshot.
snapshot:list List all the snapshots.
snapshot:load Load up a snapshot.
config/filesystems.php:56
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
// ここから追加
'snapshots' => [
'driver' => 'local',
'root' => database_path('snapshots'),
],
// ここまで追加
(実際のディレクトリはバックアップを取るときに自動作成されます)
テストで database/snapshots/my-first-dump.sql を作成します
php artisan snapshot:create my-first-dump
ファイル名を日付にしてもいいと思います。
php artisan snapshot:create 2019_03_12
何度もとるなら時刻も入れておくといいと思います。
php artisan snapshot:create 2019_04_16__03_19_00
なお 同名のファイルの場合は上書き されますので注意してください。
php artisan snapshot:create
Creating new snapshot...
Snapshot `2019-06-04_08-06-49` created (size: 1.37 MB)
2019-06-04_08-06-49 というフォーマットで自動的にファイル名を命名してバックアップを取ってくれます。
バックアップファイル一覧の確認
php artisan snapshot:list
一覧の Name を指定してリストアを実行します。
リストアの実行
php artisan snapshot:load <バックアップName>
本番環境 (.env に APP_ENV=production が設定されている場合)ではリストアはエラーとなります。
一時的に戻します
.env
APP_ENV=production
↓
APP_ENV=local
これで実行できます。 .env ファイルの中身は戻しておきましょう
参考: FTPにバックアップを取る場合は
http://bit.ly/35EXQwg
http://bit.ly/34BWLUT
mysqldumpコマンドを使ってバックアップを作成するのでこちらのコマンドを実行できるようにすればdocker環境でも使用することができます
管理画面の編集完了後にリダイレクトするページを複数にしたい時、 戻る URL を次のようにして 持ちまわると便利です。
use Illuminate\Support\Facades\Session;
\Session::flash('_back_url', $request->fullUrl());
use Illuminate\Support\Facades\Session;
if (\Session::has('_back_url')) { \Session::keep('_back_url'); }
セッションがある時とない時で処理を分けます。
use Illuminate\Support\Facades\Session;
if (\Session::has('_back_url')) { \Session::keep('_back_url'); }
if ( \Session::get('_back_url') ){
return redirect( \Session::get('_back_url') )->with([ 'message' => 'success !' ]);
}
else {
return redirect()->route("admin.data.index")->with([ 'message' => 'success !' ]);
}
httpミドルウェアを使用しましょう。
https://readouble.com/laravel/5.8/ja/middleware.html
vendor/laravel/framework/src/Illuminate/Foundation/Application.php を調べます
/**
* The Laravel framework version.
*
* @var string
*/
const VERSION = '5.0.31';
php artisan -V
{{ App::VERSION() }}
$laravel_ver = app()->version();
文字列で返ります
5.4.36
$laravel_ver = preg_replace("{([0-9]+)\.([0-9]+)\.([0-9]+)}","$1", app()->version() );
文字列で返ります
5.4.36
例) として aes256(キー固定)に変更してみます。
app/Libs/CustomHash/CustomHasher.php を以下の内容で作成する
<?php
namespace App\Libs\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
public function info($hashedValue)
{
return $this->driver()->info($hashedValue);
}
public function make($value, array $options = array()) {
$key = env('CUSTOM_HASHER_AES256_KEY', false);
return openssl_encrypt($value,'aes-256-ecb',$key);
}
public function unmake($value, array $options = array()) {
$key = env('CUSTOM_HASHER_AES256_KEY', false);
return openssl_decrypt($value,'aes-256-ecb',$key);
}
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
}
app/Providers/CustomHashServiceProvider.php を以下の内容で作成する
<?php
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
config/app.php の変更
'providers' => [
・・・・・
App\Providers\CustomHashServiceProvider::class , // 追加
// Illuminate\Hashing\HashServiceProvider::class, // コメントアウト
・・・・・
CUSTOM_HASHER_AES256_KEY=my_aes256key
'password' => bcrypt('my-pass-word') ,
↓
'password' => \Illuminate\Support\Facades\Hash::make('my-pass-word') ,
(管理画面で初期マスタデータをクライアントに登録してもらう時などに使用します)
$insert_data = [
'name' => '代入 太郎' ,
];
$practitioner = \App\Practicioner::create($insert_data);
$insert_data = [
'name' => '代入 太郎' ,
];
$practitioner = \App\Practicioner::fill($insert_data)->save();
$insert_data = [
'id' => 123456 ,
'name' => '代入 太郎' ,
];
$practitioner = \App\Practitioner::insert($insert_data);
(パラメータ名「email_or_id」を受けて、メールアドレスの場合はメールアドレスとして認証。それ以外の場合はidとして認証に行きます。)
app/Http/Controllers/Auth/LoginController.php に以下を追加します
function username()
{
return 'email_or_id';
}
function attemptLogin(\Illuminate\Http\Request $request)
{
$email_or_id = $request->input($this->username());
$password = $request->input('password');
if (filter_var($email_or_id, \FILTER_VALIDATE_EMAIL)) {
$credentials = ['email' => $email_or_id, 'password' => $password];
} else {
$credentials = ['id' => $email_or_id, 'password' => $password];
}
return $this->guard()->attempt($credentials, $request->filled('remember'));
}
パラメータ名を「email」 →「email_or_id」に変更します resources/views/auth/login.blade.php を以下のように変更します
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" autofocus>
↓
<input id="email" type="text" class="form-control" name="email_or_id" value="{{ old('email_or_id') }}" autofocus>
以上で id またはメールアドレスでログインする事ができます
引用元 : https://goo.gl/Wodfh1
https://github.com/akat03/wptrait
class Mymodel extends Model
{
use WpTrait;
}
{!! $mymodel->wpautop($mymodel->contents) !!}
カラム「text_name」にのみ適用します
@if ( $row->field == 'text_name' )
@php
echo $dataTypeContent->wpautop($dataTypeContent->text_name);
@endphp
@else
<p>{!! $dataTypeContent->{$row->field} !!}</p>
@endif
.env.example からリネームして .env を作成した時に主に出現します。
「application encryption key」がないですよ、と言う事ですので作成しましょう。
php artisan key:generate
英語の場合は「resources/lang/en」ディレクトリ内に言語ファイルを格納します(元からあるはずです) 日本語の場合は「resources/lang/en」ディレクトリを作成します。
ファイル名の例(myapp)
resources/lang/ja/myapp.php
return [
'list' => '一覧',
'edit' => '編集',
'delete' => '削除',
'print' => '印刷',
];
こちらの記法がオススメです
@lang('myapp.list')
(一覧 と表示されます。)
こういう書き方もできます。
{{ __('messages.welcome') }}
'delete_confirm' => ':nameを削除します。よろしいですか?',
と定義している時、次のようにしてパラメーターを渡します。
@lang('excrud.delete_confirm',['name' => 'データ100番'])
「データ100番を削除します。よろしいですか?」になる
@php
$page_title = \Lang::get('myapp.edit');
@endphp
オリジナルのCMSや管理画面を作りたい時に、面倒な色々の準備をやってくれるのが Voyager です。
必要最低限の機能がかなりのクォリティで用意されています。
https://github.com/the-control-group/voyager
( 必ず見るようにしましょう。 簡潔に説明してくれています。)
https://laravelvoyager.com/academy/configurations/
Laravel コマンドでアプリを作成した後に次のコマンドを入力します
composer create-project "laravel/laravel=5.7.*" my_app
cd my_app
composer require tcg/voyager
composer show
laravel と voyager だけ表示させる
composer show | grep -e laravel/framework -e tcg/voyager
リストの中の次のバージョンを確認する(何か問題が発生した時はバージョンを考慮して検索しましょう)
laravel/framework v5.7.28
tcg/voyager v1.1.12
適宜変更します
https://YOUR-SERVER-NET
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
app\Providers\AppServiceProvider.php
// ↓ この行を追加
use Illuminate\Support\Facades\Schema;
public function boot()
{
// ↓ この行を追加
Schema::defaultStringLength(191);
}
php artisan voyager:install --with-dummy
(↑ このコマンドでDBデータも自動作成されます)
php artisan voyager:install
php artisan voyager:admin your@email.com --create
admin@admin.com
password
Voyager 1.2 以降から
{{ voyager_asset('lib/css/responsive.dataTables.min.css') }}
の様な形で voyager_asset が追加されました。 「今まで public ディレクトリ以下にファイルをコピーしていた代わりに内部ファイルを返す」 といったものです。これが標準の nginx では動作しない可能性があります。
location ~* .(js|css|jpg|png|gif|svg|woff|ttf)$ {
try_files $uri $uri/ /index.php?$args;
}
nginx -s reload
これで読み込まれます。
右上のメニューから「Profile」 →「Edit My Profile」→「Locale を ja にセットする」
「Settings」→「Admin」→「Admin Loader」に画像をアップロードする
こちらの画像ファイルを変更します。
/public/vendor/tcg/voyager/assets/images/bg.jpg
デフォルトでは画像サイズ「1800ox * 1200px 」となっています。
管理画面のBREDのテンプレートビューを変更するには次の位置に Bladeテンプレートファイルを作成します。
(詳細表示 : browse)を変更する場合
/resources/views/vendor/voyager/<テーブル名>/browse.blade.php
元の管理画面を参考に変更したい場合は次のファイルの内容をコピーします。
vendor/tcg/voyager/resources/views/bread/browse.blade.php
解説: https://laravelvoyager.com/academy/views/
vendor/tcg/voyager/resources/master.blade.php
↓ コピーする
resources/views/vendor/voyager/master.blade.php
例: BREADのリスト表示で
「admin の role を持つ場合は表示させる」
「user の role の場合は Policy(別途作成が必要)により TCG\Voyager\Actions\EditAction が可能な場合は表示させる」
を Bladeテンプレートに記述します。
@if (Auth::user()->hasRole('admin'))
<!-- admin -->
@else
@php
$edit_action = new TCG\Voyager\Actions\EditAction($dataType, $data);
@endphp
@can($edit_action->getPolicy(), $data)
@php echo '<!-- user (edit可能です)-->'; @endphp
@else
@php echo '<!-- user (edit ×不可能です)-->'; @endphp
@continue
@endcan
@endif
管理画面のBREDのコントローラーを変更するには次の位置に コントローラーファイルを作成します。
例: (EstimateController)を変更する場合
app/Http/Controllers/EstimateController.php
app/Http/Controllers/EstimateController.php を次のようにします。( VoyagerBaseController を継承して中身が空のコントローラを作成 )
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use TCG\Voyager\Database\Schema\SchemaManager;
use TCG\Voyager\Events\BreadDataAdded;
use TCG\Voyager\Events\BreadDataDeleted;
use TCG\Voyager\Events\BreadDataUpdated;
use TCG\Voyager\Events\BreadImagesDeleted;
use TCG\Voyager\Facades\Voyager;
use TCG\Voyager\Http\Controllers\Traits\BreadRelationshipParser;
class EstimateController extends \TCG\Voyager\Http\Controllers\VoyagerBaseController
{
}
元の管理画面を参考に変更したい場合は次のコントローラーファイルの内容をコピーします。
vendor/tcg/voyager/src/Http/Controllers/VoyagerBaseController.php
最後にVoyager の BREAD から 対象となるDBテーブルの「コントローラー名」に「\App\Http\Controllers\EstimateController」を指定します。 (必須です) これで作成したコントローラが使用されます。
'additional_css' => [
'storage/css/voyager_custom.css',
],
storage/app/public/css/voyager_custom.css にcssファイルを置きます
<My-APP>/public/css/voyager_custom.css
https://voyager-cheatsheet.ulties.com/
/config/voyager-hooks.php
/config/voyager.php
https://github.com/thedevdojo/wordpress-import
辞書ファイル(日本語)はこちらにあります。
/vendor/tcg/voyager/publishable/lang/ja
app/User.php
<?php
namespace App\Models;
use TCG\Voyager\Models\User as VoyagerUser;
class User extends VoyagerUser {
// add custom mutators and other code in here
}
app/config/voyager.php
'user' => [
'add_default_role_on_register' => true,
'default_role' => 'user',
'admin_permission' => 'browse_admin',
'namespace' => App\User::class,
],
BREADのオプションに任意のパラメータを設定した場合は次のように $row->details から参照できます。
例)編集画面にコメントを表示させる
resources/views/vendor/voyager/<BREAD名>/edit-add.blade.php
{{-- コメント表示 --}}
@php
if ( @$row->details->comment ){
echo '<small>';
echo $row->details->comment;
echo '</small>';
}
@endphp
{{-- / コメント表示 --}}
なお、編集時に項目の見出しをつけるには
{
"legend": {
"text": "○○編集エリア"
}
}
のように指定すると見出しが表示されます。
https://github.com/the-control-group/voyager/issues/2008
{
"relationship": {
"key": "id",
"label": "name"
}
}
少しややこしい表現になりますが、
リレーション先の複数のカラムをソートする のではなく、
リレーション先のカラムを使って親データをソート します
\App\Shirt::with('size')
->select('shirts.*', \DB::raw('(SELECT sort FROM sizes WHERE shirts.size_id = sizes.id limit 1 ) as sort'))
->orderBy('sort', 'ASC')
->get(); // または paginate( $limit );
limit 1 をつけないと、複数行ある時に 「Subquery returns more than 1 row」エラーが返ります
Laravel 5.6.19 から は DB::raw を使用する代わりに joinSub()、leftJoinSub()、rightJoinSub()が追加されたようです。
DB::table('table')->joinSub('select * from "subtable"', 'sub', ...);
DB::table('table')->leftJoinSub(function ($q) { $q->from('subtable'); }, 'sub', ...);
DB::table('table')->rightJoinSub(DB::table('subtable')->where('foo', 'bar'), 'sub', ...);
引用元 : https://goo.gl/2UPi1E
{{ Form::file('myfile', ['class' => $class_name]) }}
( Form::text の場合は 2番目の引数に値をセットしますが、Form::file はセットしません。 )
コントローラーに以下を記述
$file_store_dir = 'img/';
$file_store_disk = 'local'; // local または public または s3
$file_name = $request->myfile->store($file_store_dir, $file_store_disk );
コントローラーに以下を記述
$ext = pathinfo($request->myfile->hashName(), PATHINFO_EXTENSION); // ファイル名から拡張子を取得
$file_store_dir = 'img/';
$file_store_file_name = $id.".{$ext}"; // IDに拡張子をつけたものを保存ファイル名とする
$file_store_disk = 'local'; // local または public または s3
$file_name = $request->myfile->storeAs($file_store_dir, $file_store_file_name, $file_store_disk ); // ディレクトリ, ファイル名, ディスク
/**
* アクセサー : 「->patient_birth_date_wareki」 で 値「昭和45年6月27日」のフォーマットで誕生日を返す
*/
public function getPatientBirthDateWarekiAttribute()
{
if ( $this->attributes['patient_birth_date'] != null ){
$c = new \Carbon\Carbon($this->attributes['patient_birth_date']);
setlocale(LC_TIME, 'ja_JP.utf8');
$format = '%EC%Ey年%-m月%-d日';
return $c->formatLocalized($format);
} else {
return 'ERROR: getPatientBirthDateWarekiNengoAttribute()';
}
}
/**
* アクセサー : 「->patient_birth_date_wareki_nengo」 で 値「昭和」を返す
*/
public function getPatientBirthDateWarekiNengoAttribute()
{
if ( $this->attributes['patient_birth_date'] != null ){
$c = new \Carbon\Carbon($this->attributes['patient_birth_date']);
setlocale(LC_TIME, 'ja_JP.utf8');
$format = '%EC';
return $c->formatLocalized($format);
} else {
return 'ERROR: getPatientBirthDateWarekiNengoAttribute()';
}
}
/**
* アクセサー : 「->patient_birth_date_wareki_nengo」 で 年号以降の和暦の誕生日を返す
*/
public function getPatientBirthDateWarekiNengappiAttribute()
{
if ( $this->attributes['patient_birth_date'] != null ){
$c = new \Carbon\Carbon($this->attributes['patient_birth_date']);
setlocale(LC_TIME, 'ja_JP.utf8');
$format = '%Ey年%-m月%-d日';
return $c->formatLocalized($format);
} else {
return 'ERROR: getPatientBirthDateWarekiNengoAttribute()';
}
}
dump($model->patient_birth_date); // DB に入っている誕生日
dump($model->patient_birth_date_wareki);
dump($model->patient_birth_date_wareki_nengo);
dd($model->patient_birth_date_wareki_nengappi);
Carbon @1548926428 {#558 ▼
date: 2019-01-31 18:20:28.0 Asia/Tokyo (+09:00)
}
"平成31年1月31日"
"平成"
"31年1月31日"
https://readouble.com/laravel/5.7/ja/eloquent-relationships.html
// 最低1つのコメントを持つ全ポストの取得…
$posts = App\Post::has('comments')->get();
// 3つ以上のコメントを持つ全ポストの取得
$posts = App\Post::has('comments', '>=', 3)->get();
// 最低1つのコメントと、それに対する評価を持つポストの取得
$posts = App\Post::has('comments.votes')->get();
// ========== トランザクション ==========
DB::transaction(function () use ($params) {
// ...
// ...
// 何かしらの処理を記述
});
// ========== / トランザクション ==========
クロージャ(無名関数)が正常に実行されると、トランザクションは自動的にコミットされます
// ===== トランザクション =====
try {
DB::beginTransaction();
// ...
// ...
// 何かしらの処理を記述
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
}
// ===== /トランザクション =====
// ========== トランザクション ==========
$data = DB::transaction(function () use ($params) {
// DB 処理
.......
return $data;
});
// ========== / トランザクション ==========
$insert_data = [
'id' => 1001 ,
'user_name' => 'テスト太郎'
];
IDがガードされている場合、次のようにすると user_name のみ INSERTするSQL文が実行されます。
$model = new MyModel();
$model->fill($insert_data)->save();
↓ 明示的に ID にも値を入れて INSERT する場合はこのようにします。
$model = new MyModel();
$model->insert($insert_data);
ただし この方法では自動的に created_at にインサート時刻が入りません。 そこで明示的に現在時刻を渡してあげます。
$insert_data['created_at'] = new \Carbon\Carbon();
local : ローカルディスク(一般公開しないファイル)
public : ローカルディスク(一般公開するファイル。コマンド aritizan storage:link を実行すると公開することができる。 )
s3 : Amazon S3
ftp :FTP (別途パッケージのインストールが必要)
など。
publicディスクを使用するときは次のコマンドを事前に実行しておきましょう。
php artisan storage:link
config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
],
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
// ===== permissions =====
'permissions' => [
'dir' => [
'public' => 0775 ,
],
'file' => [
'public' => 0664 ,
],
],
// ===== / permissions =====
],
次のようなファイル移動を行うとします。
<アプリのトップディレクトリ>/storage/app/tmp/test.csv
↓
<アプリのトップディレクトリ>/storage/app/csv/123456789.csv
注意: Diskをまたいだコピーや移動は move()メソッドだけではできません
参考 : http://bit.ly/2Lk5Qw7
Laravel Storage を使って次の様に記述します。
$disk = \Storage::disk('local');
$disk->move("tmp/test.csv", "csv/12345679.csv" );
簡単ですね。 おすすめです。
dump( $disk->path(false) ); // パスのみの表示
dump( $disk->path("csv/test.csv") ); // ファイル名をフルパスで表示
↓ 表示例
/var/www/laravel/my_app/storage/app/
/var/www/laravel/my_app/storage/app/test.csv
url(\Storage::disk('public')->url($image_name))
blade
<img src="{{ url(\Storage::disk('public')->url($image_name)) }}">
.env を次のように修正しておきましょう
APP_URL=http://localhost
↓
APP_URL=https://YOUR-SITE.TLD/
例えばコントローラに次のように記述するとローカルファイルのデータを表示できます。
$pdf_file_path = 'pdf/125_01.pdf';
$disk = \Storage::disk('local');
$mimetype = $disk->mimeType($pdf_file_path);
return response($disk->get($pdf_file_path))->header('Content-Type', $mimetype);
// ファイルの絶対パスを取得
$file_path = \Storage::disk('local')->path($path);
// ファイル内容の取得
$contents = Storage::get('file.jpg');
// ファイルの存在
$exists = Storage::disk('s3')->exists('file.jpg');
// ファイルをダウンロードさせる
return Storage::download('file.jpg');
return Storage::download('file.jpg', $name, $headers);
// ファイル情報
$url = Storage::url('file.jpg');
$size = Storage::size('file.jpg');
$time = Storage::lastModified('file.jpg');
// ファイル操作
Storage::put('file.jpg', $contents);
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
// ディスク外のディレクトリにあるファイルをコピー
$tmp_file_fullpath_name = "/tmp/test.jpg";
$d = file_get_contents( $tmp_file_fullpath_name );
\Storage::disk($file_store_disk)->put("{$file_store_dir}/{$image_name}", $d);
// 公開状態かどうか
Storage::setVisibility('file.jpg', 'public')
// ファイルの削除
Storage::delete('file.jpg');
Storage::delete(['file.jpg', 'file2.jpg']);
Storage::disk('s3')->delete('folder_path/file_name.jpg');
// ファイル/ディレクトリ一覧の取得
$files = Storage::files($directory);
$files = Storage::allFiles($directory);
$directories = Storage::directories($directory);
$directories = Storage::allDirectories($directory);
// ディレクトリ操作
Storage::makeDirectory($directory);
Storage::deleteDirectory($directory);
// ディレクトリ操作(ディスク指定)
Storage::disk('local')->makeDirectory($directory);
// ディレクトリ内のファイルやディレクトリを削除して 「ディレクトリの中身を空」にする
\File::cleanDirectory( storage_path().'/app/zip' );
// ディレクトリを パーミッション 0777 で作成する
umask( 0 );
\File::makeDirectory($path, '0777');
// ディレクトリ内のファイル一覧を取得(SplFileInfo クラスが返ります)
$files_list = \File::files( $folder );
https://www.php.net/manual/de/class.splfileinfo.php
https://github.com/illuminate/filesystem/blob/master/Filesystem.php
Route::group(['prefix' => 'admin', 'middleware' => 'admin', 'namespace' => 'Admin'], function () {
Route::resource("consentforms","ConsentformController");
});
とするとルート名は
| GET|HEAD | admin/consentforms/create | consentforms.create |
| GET|HEAD | admin/consentforms/{consentform} | consentforms.show |
| PUT|PATCH | admin/consentforms/{consentform} | consentforms.update |
... (以下続く)
と、なりますが
次の様に 'as' => 'admin.' を使うと 名前の先頭に admin. が追加されます
Route::group(['prefix' => 'admin', 'middleware' => 'admin', 'namespace' => 'Admin', 'as' => 'admin.'], function () {
Route::resource("consentforms","ConsentformController");
});
↓ この様なルート名になります
| GET|HEAD | admin/consentforms/create | admin.consentforms.create |
| GET|HEAD | admin/consentforms/{consentform} | admin.consentforms.show |
| PUT|PATCH | admin/consentforms/{consentform} | admin.consentforms.update |
... (以下続く)
use View;
View::addNamespace('Pdf', base_path().'/resources/pdf' );
return View::make('Pdf::my_template');
レンダリングするファイルが Views 以下にある場合はこちらでOKです。
$html = view('welcome', compact('user'))->render();
dd( $html );
Views 以外のディレクトリにあるテンプレートファイルをレンダリングして変数に代入するには
/**
* views 以外にあるbladeテンプレート( *.blade.php )を描画して変数とし受け取る
*
* @param string $template_full_path テンプレートhtmlファイル名(フルパス)
* @param string $compact compact() で受け取るテンプレートパラメーター
*
* @return string htmlテンプレート描画内容
*/
private function getViewRender( string $template_full_path , array $compact )
{
$template_path = pathinfo($template_full_path, PATHINFO_DIRNAME);
$templatefile_name = pathinfo($template_full_path, PATHINFO_FILENAME);
$app = app();
$paths = [$template_path];
// もとの設定を取得
$originalFinder = \Illuminate\Support\Facades\View::getFinder();
$finder = new \Illuminate\View\FileViewFinder($app['files'], $paths);
\Illuminate\Support\Facades\View::setFinder($finder);
$html = view($templatefile_name, $compact )->render();
dd($html);
// もとの設定に戻す
\Illuminate\Support\Facades\View::setFinder($originalFinder);
return $html;
}
使い方
$html = $this->getViewRender( $template_html, compact('param') );
.env を 以下のように設定します。
APP_ENV=production
APP_ENV=production の設定がある時に、migrate コマンドを実行すると
$ artisan migrate:fresh --seed
次のような確認入力が表示されます。
これで本番環境で安易なマイグレーションの実行を抑止することができます。
**************************************
* Application In Production! *
**************************************
Do you really wish to run this command? (yes/no) [no]:
1対1のリレーションに有効です。
1対多のリレーションの場合は Slow Query になる可能性があるようです。必ず生成されるSQL文を確認しましょう。
/**
* 1対1リレーション (従属の関係)
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user() // 単数形
{
return $this->belongsTo('App\User');
}
(モデルの中に記述してもいいです。お好みで。)
whereHasメソッド または orWhereHasメソッド を使用します。
// ● リレーション先のユーザー名「太郎」でも検索
$q = '太郎';
$model->orWhereHas('user', function ($query) use ($q) { // リレーション名 user を渡す
$query->where('name', 'LIKE', "%{$q}%");
});
WHERE
exists(select * from users
where articles.user_id = users.id
and name LIKE '%太郎%')
exists を使ってサブクエリを呼び出します。
パッケージのインストール
composer require mpyw/eloquent-has-by-join
composer require mpyw/eloquent-has-by-non-dependent-subquery
全ての View で 何かの件数を表示したい時、ミドルウェアを作ったり、コントローラーを継承した親コントローラーに 共通処理を書いてもいいのですが、直接モデルオブジェクトを呼ぶという方法もあります。
Blade の ビューファイル から直接呼び出します。
例
自分の記事数 : {{ \App\MyArticle::count() }}
自分のユーザー数 : {{ \App\MyUser::count() }}
同様に static なメソッドを作成すれば、どこからでも呼ぶことができます。
app/Http/Controllers/MyController.php
public static function getMyCount() {
// ここに何かの処理
$result = 99999;
return $result;
}
ビューからはこのように呼び出せます。
{{ \App\Http\Controllers\MyController::getMyCount() }}
pdfを出力する時に、全てをhtmlで書いてもいいのですが、ロゴなどはpdfテンプレートで使い回すという方法も便利なので紹介します。
composer require setasign/fpdi
composer require tecnickcom/tcpdf
composer require wyrihaximus/html-compress
格納場所 resources/pdf を作成します。
mkdir resources/pdf
ここに pdf と html をコピーします。以下のファイル名とします。
template_01.pdf
template_01.pdf.html
http://jikasei.me/font/genshin/
明朝フォントはこちらの 源様明朝 がお勧めです。
https://github.com/ButTaiwan/genyo-font
格納場所 resources/fonts を作成します。
mkdir resources/fonts
ここに 拡張子が .ttf のフォントファイルをコピーします。
Laravelのコントローラーからpdf 出力
use setasign\Fpdi;
use TCPDF_FONTS;
public function tcpdf()
{
$pdf = new Fpdi\TcpdfFpdi();
// $pdf = new \TCPDF("L", "mm", "A4", true, "UTF-8" ); // pdf テンプレートを使わない場合はこちら
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->AddPage();
// テンプレートPDFファイル読み込み
$pdf->setSourceFile(resource_path('pdf/template_01.pdf'));
$page = $pdf->importPage(1);
$pdf->useTemplate($page);
// フォント
$font = new TCPDF_FONTS();
// フォント:源真ゴシック
// $font_1 = $font->addTTFfont( resource_path('fonts/ipag.ttf') );
$font_1 = $font->addTTFfont( resource_path('fonts/GenShinGothic-Medium.ttf') );
$pdf->SetFont($font_1 , '', 10,'',true);
// テンプレートhtmlファイル読み込み
$html = \File::get( resource_path('pdf/template_01.pdf.html') );
$pdf->writeHTML( $html, $ln=false, $fill=0, $reseth=false, $cell=false, $align="L" );
$pdf->Output("output.pdf", "I");
}
このサイトがとても便利です
htmlをminifyしましょう
composer require wyrihaximus/html-compress
$parser = \WyriHaximus\HtmlCompress\Factory::construct();
$html = $parser->compress($html);
以下のcssプロパティが使用できます。スタイルを思った通りにするのにはかなり足りないと思います。
font-family
font-size
font-weight
font-style
color
background-color
text-decoration
width
height
text-align
ではレイアウトはどうすれば良いのか?
「line-height を使う」か「いにしえのテーブルレイアウトを使う」のがいいでしょう。
例: line-height を使う
<table border="0">
<tr>
<td style="font-size:18px;line-height:57px;">お名前 太郎</td>
</tr>
</table>
例: いにしえのテーブルレイアウトを使う
<html><body>
<table border="0">
<tr>
<td style="width:30px;"></td>
<td style="width:350px; height:75px;"></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>平成31年1月25日</td>
</tr>
</table>
</body></html>
LaravelCollective の Form::select の 4番目の引数に配列を渡す事で実現できます。
{{ Form::select($name, $select_loop, $selected_value, ['class' => 'my_css_class', 'disabled' => 'disabled']) }}
{{ Form::select('size', ['L' => 'Large', 'S' => 'Small'], 'S', ['disabled' => 'disabled']) }}
引数4番目の配列に 'disabled' => 'disabled' を指定するだけでOKです。
これだけで、ドロップダウンリスト全体が disabled になります。
全体じゃなくて特定の <option>項目だけ選択できないようにしたいという時がたまにあります。 その場合は以下のようにフォームマクロを作成すると簡単にできます。
次のコマンドを実行します
php artisan make:provider FormMacroServiceProvider
コマンドが実行されると app/Providers/FormMacroServiceProvider.php が自動生成されます。
boot() メソッド内に以下を追加します
public function boot()
{
// この行を追記する ↓
require base_path() . '/resources/macros/selectbox.php';
}
'providers' => [
// この行を追記する ↓
App\Providers\FormMacroServiceProvider::class,
]
mkdir ./resources/macros
<?php
Form::macro('mySelectBox', function ($name, $list = [], $selected = null, array $selectAttributes = [], array $optionsAttributes = []) {
$html = '<select name="' . $name . '"';
foreach ($selectAttributes as $k => $v) {
$html .= ' ' . $k . '="' . $v . '"';
}
$html .= ">\n";
foreach ($list as $value => $text) {
$html .= '<option value="' . $value . '"';
if (strcmp($value, $selected) == 0) {
$html .= ' selected="selected"';
}
if (isset($optionsAttributes[$value])) {
$html .= ' ' . $optionsAttributes[$value];
}
$html .= '>' . $text . "</option>\n";
}
$html .= '</select>';
return $html;
});
今まではこのように記述してドロップダウンリストを生成していたと思いますが次のように書き換えます
{{Form::select('size', ['L' => 'Large', 'M' => 'Medium', 'S' => 'Small'], 'S')}}
↓
{!! Form::mySelectBox('size', ['L' => 'Large', 'M' => 'Medium', 'S' => 'Small'], 'S', [], ['L' => 'disabled']) !!}
このようなドロップダウンリストが生成されます。 (Largeだけ選択出来ないようになりました)
コントローラーなどの各モジュールを作成するときにもちろん既存のファイルのコピペでもいいのですが、 artisan の make コマンドを使うと、エラーのない中身が空のひな形ファイルが作成されます。
php artisan make:controller -h
php artisan make:controller HomeController
成功すると app/Http/Controllers/HomeController.php が作成されます。
以下、同様に次の make コマンドがあります。
make:auth Scaffold basic login and registration views and routes
make:channel Create a new channel class
make:command Create a new Artisan command
make:controller Create a new controller class
make:event Create a new event class
make:exception Create a new custom exception class
make:factory Create a new model factory
make:job Create a new job class
make:listener Create a new event listener class
make:mail Create a new email class
make:middleware Create a new middleware class
make:migration Create a new migration file
make:model Create a new Eloquent model class
make:notification Create a new notification class
make:observer Create a new observer class
make:policy Create a new policy class
make:provider Create a new service provider class
make:request Create a new form request class
make:resource Create a new resource
make:rule Create a new validation rule
make:scaffold Create a scaffold with bootstrap 3
make:seeder Create a new seeder class
make:test Create a new test class
種類 | ヘルプ表示コマンド |
---|---|
コントローラー | php artisan make:controller -h |
モデル(DBデータ操作) | php artisan make:model -h |
マイグレーション(DBデータ構造) | php artisan make:migration -h |
シーダー(DBデータ作成) | php artisan make:seeder -h |
モデルファイルに $appends プロパティをセットします
// SELECTされるデータセットに次の独自カラムを追加する
protected $appends = ['_editable_flag' , '_email_aisatsu' ];
任意のタイミングで appends に追加したいときにはメソッドを使用します。
return $user->append('is_admin')->toArray();
Laravelのモデル(Eloquent)の結果セット(Collection)に手動で任意のカラムを追加するには map() を使用します。
// コレクションのすべてのデータにurl = http://your.url/here を追加
$collection->map(function ($v) {
$v['url'] = 'http://your.url/here';
return $v;
});
// コレクションそれぞれに count=xxx (任意の値) を追加
$collection->map(function ($v) {
$v['count'] = <計算ロジック>;
return $v;
});
user_code カラムを隠して結果セットをjsonで返します
$collection->setHidden(['user_code'])->toJson(JSON_UNESCAPED_UNICODE)
getCollection() で コレクションを取り出してから行います
$paginator->getCollection()->map(function ($v) {
$v['count'] = <計算ロジック>;
return $v;
});
少し書式を変えたいときは Accessor /Mutator を使いましょう
モデルファイルに以下を記述
/**
* ● アクセサー : ->_star_mark で is_starred=1 の時に ☆ を表示する
*
*/
public function getStarMarkAttribute()
{
if ($this->attributes['is_starred'] == 1){
return '<div class="text-warning">★</div>';
}
}
呼び出し方
$model->star_mark
/**
* アクセサー : 「->dispatch_date_ja」 で 値「dispatch_date」が存在する時にフォーマットして表示する。存在しない場合は未発送を返す。
*/
public function getDispatchDateJaAttribute()
{
if ( $this->attributes['dispatch_date'] != null ){
$c = new \Carbon\Carbon($this->attributes['dispatch_date']);
return $c->format('m/d');
} else {
return '<span class="text-danger">未発送</span>';
}
}
呼び出し方
$model->dispatch_date_ja
Laravel-Excel ver 3 では まだ現時点では import が使えないようなので ver 2 を入れます。( 参考: https://goo.gl/wxHLE7 )
composer で インストールします
composer require "maatwebsite/excel:~2.1.0"
インストールされたモジュールのうち excel 関連のバージョンを調べて置きます
composer show | grep excel
maatwebsite/excel 2.1.30 Supercharged Excel exports in Laravel
phpoffice/phpexcel 1.8.2 PHPExcel - OpenXML - Read, Create and Write Spreadsheet ...
Laravel Excel の 2.1.30 と phpexcel の 1.8.2 がインストールされています。
ググる時にはこれらのバージョンを参考にしましょう。
( storage/excel/ )は存在しないので作成します。
Route::get('/test/excel', 'TestController@excel');
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class TestController extends Controller
{
public function excel()
{
$excel_file = storage_path('excel/test.xls');
Excel::load($excel_file, function($reader) {
// 1番目のシートを選択
$reader->sheet(0, function($sheet) {
// セルA3に現在の日付を書き込み
$sheet->cell('A3', function($cell) {
$cell->setValue( now()->format('Y/m/d') );
});
});
})->export('xlsx');
}
}
https://YOUR-APP.COM/test/excel
にアクセスすると、エクセルファイルがダウンロードされます。
最初のシートの「A3」に今日の日付が書き込まれていることを確認します。
まずこれで Laravel Excel の 動作確認の完了です。
PHPでするよりLibreOfficeを使用しましょう。再現性が上がります。(それでも80点ぐらいの再現度ですが。)
yum -y install libreoffice libreoffice-langpack-ja
インストール後 バージョンを確認します。
libreoffice --version
もし libreoffice が見つからない場合は こちらから検索します。
find / -name soffice.bin
wget https://ipafont.ipa.go.jp/old/ipafont/IPAfont00303.php -O IPAfont00303.zip
unzip IPAfont00303.zip
cd IPAfont00303
mv *.ttf /usr/share/fonts
cd ..
rm -rf IPAfont00303
rm -f IPAfont00303.zip
mkdir genshingothic
cd genshingothic
wget https://osdn.jp/downloads/users/8/8637/genshingothic-20150607.zip genshingothic-20150607.zip
unzip genshingothic-20150607.zip
mv *.ttf /usr/share/fonts
cd ..
rm -rf genshingothic
rm genshingothic-20150607.zip
fc-list | grep IPA
コマンドラインから以下のコマンドを実行します。
libreoffice --headless --convert-to pdf --outdir /home/kusanagi/pdf test.xls
パスは絶対パスで指定しておくと確実です。
composer require doctrine/dbal
add_columns_articles_table は任意の命名でOKですが「作業内容_テーブル名_table」としておくとテーブル作成時のファイルと命名が揃います
php artisan make:migration add_columns_articles_table --table=articles
例:「articles」テーブルに以下のカラムを追加します
・「status_id」カラムの後ろにint型の「recruit_flg」を追加します
・「recruit_flg」カラムの後ろにint型の「recruit_date」を追加します
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->integer('recruit_flg')->default(0)->after('status_id'); // この行を追加
$table->dateTime('recruit_date')->nullable()->after('recruit_flg'); // この行を追加
});
}
注意:SQLiteでは任意の位置にカラム追加が出来ないようです
回避策 : https://goo.gl/a2atCx
public function down()
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn(['recruit_date','recruit_flg']);
});
}
php artisan migrate
php artisan migrate:rollback
以上で、既存のデータベースのデータを削除することなく、カラムを追加できます。
ガリガリ書いてもいいですが、 fast-excelを使ってやるとさっとできます
https://github.com/rap2hpoutre/fast-excel
fast-excelのインストール
composer require rap2hpoutre/fast-excel
use Rap2hpoutre\FastExcel\FastExcel;
$csv = (new FastExcel)->import('data.csv');
dd($csv);
これだけで「data.csv」を読み込んで $csv にハッシュの配列形式で格納します。
CSVの1行目にカラム名を記述しておくとその名前のハッシュに変換して取り込まれます。
文字コードは utf-8 以外の場合文字化けします。 csvファイルは sjis なことが多いので、事前に変換しておきましょう。
//========================================== convertFileEncode
protected function convertFileEncode($infname="", $incode='sjis-win', $outfname="", $outcode='UTF-8', $nl="\r\n") {
if ( ! is_file($infname) ) {
die("変換失敗:{$infname} が見つかりません.");
}
$tmp_filename = getmypid().'.tmp';
$outfp = fopen($tmp_filename, 'wb');
if ($outfp === FALSE) {
die("変換失敗:{$tmp_filename} に書き込むことができません.");
}
$fp = fopen($infname,'r') or die("ファイル({$infname})のオープンに失敗しました");
while ( ($line = fgets($fp,999999)) !== false ) {
$outstr = mb_convert_encoding($line, $outcode, $incode);
$outstr = preg_replace("/\r\n|\r|\n/", $nl, $outstr);
fwrite($outfp, $outstr);
}
fclose($fp);
fclose($outfp);
rename($tmp_filename, $outfname);
chmod($outfname, 0666);
return true;
}
エラー処理は適宜書き換えてください。(とりあえず die() していますが、例外を投げた方がいいでしょう。)
Laravel では以下のコードでは catch できません。
try {
// 何かしらの処理
} catch (Exception $e) {
dd($e);
}
↓ このように変更します
try {
// 何かしらの処理
} catch (\Exception $e) {
dd($e);
}
(\Exception と表記します)
例えば「日付が 2019/01/01」かつ「 report_name_1に何か入っている または report_name_2に何か入っている または report_name_3に何か入っている」
という条件はクロージャを使って以下のように記述できます。
$data_loop = \App\Report::where('kigen_date','=','2019-01-01')
->where(function($query) {
$query->orWhereNotNull('report_name_1')
->orWhereNotNull('report_name_2')
->orWhereNotNull('report_name_3');
})
->get();
ローカルスコープを使うのもおすすめです
スコープを使うと SQL文で言う所の 「AND ( 何かしらのSQL文 )」 を発行することができます。
例えば次のような単純なSQL文もスコープにしてしまうと簡単です。
AND ( feed_id = 100 )
モデルファイル( 例:User モデルの場合 ) app/User.php
/**
* ローカルスコープ:inFeed:フィードIDに限定するスコープ
*/
public function scopeInFeed($query, $feed_id)
{
return $query->where('feed_id','=', $feed_id);
}
コントローラファイルに記述
$model->inFeed( 100 ); // ローカルスコープ適用
次のようにリレーションがあった場合
/**
* 1対1リレーション
*/
public function mt_status()
{
return $this->belongsTo('App\MtStatus');
}
↓ 次のようにして条件をつけることができます ( is_only_admin = 0 )
/**
* 1対1リレーション
*/
public function mt_status()
{
return $this->belongsTo('App\MtStatus')->where('is_only_admin', '=', 0);
}
Laravelで自分で作成した
Laravelのモデルで自分で設定した日付型カラムで
$touroku_date->format('Y/m/d')
のようなformat メソッドを使用すると
以下のようなエラーとなります
Call to a member function format() on string
このエラーを出さないようにするにはモデルファイルに登録します
app/モデル.php
// dates(formatメソッドを使用できるようにする)
protected $dates = [
'created_at',
'updated_at',
'touroku_date' // 追加する
];
こちらの方法がとてもよかったので引用させていただきます。
変更点としては アクセスを許可するIP を .env ファイルに記述しています(後から見返したときに見通しをよくするため)
app/Http/Middleware/IpLimit.php
<?php
/**
* IpLimit.php
*
* クライアントのリアルIPがホワイトリストに存在するかチェックするためのミドルウェア
*
* @version 0.2 [fix] (int)に正しく型キャストするよう修正
*
*/
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
class IpLimit {
/**
* Handle an incoming request.
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next) {
$config = \Config::get('ip_limit');
if ($config['enable'] !== true) {
return $next($request);
}
if ($config['isProxy'] === true) {
$request->setTrustedProxies([$request->ip()]);
}
if ($this->isAllow($request->ip(), $config['allowIps']) === false) {
$error_message = 'cannot access from : ' . $request->ip();
Log::debug($error_message);
abort(404, $error_message);
}
return $next($request);
}
private function isAllow(string $remoteIp, array $accepts) {
foreach ($accepts as $accept) {
if ($this->isIn($remoteIp, $accept)) {
return true;
}
}
return false;
}
private function isIn(string $remoteIp, string $accept) {
if (strpos($accept, '/') === false) {
return ($remoteIp === $accept);
}
list($acceptIp, $mask) = explode('/', $accept);
$acceptLong = ip2long($acceptIp) >> (32 - (int) $mask);
$remoteLong = ip2long($remoteIp) >> (32 - (int) $mask);
return ($acceptLong == $remoteLong);
}
}
app/Http/Kernel.php に以下を追加
protected $routeMiddleware = [
'iplimit' => \App\Http\Middleware\IpLimit::class, // この行を追加する
config/ip_limit.php
<?php
return [
'enable' => env('IP_LIMIT_ENABLE', false),
'isProxy' => env('IP_LIMIT_PROXY', false),
'allowIps' => [
env('LOCALHOST_IP', '127.0.0.1'),
env('IP_LIMIT_ENABLE_ADDRESS_01', false) ,
env('IP_LIMIT_ENABLE_ADDRESS_02', false) ,
env('IP_LIMIT_ENABLE_ADDRESS_03', false) ,
env('IP_LIMIT_ENABLE_ADDRESS_04', false) ,
env('IP_LIMIT_ENABLE_ADDRESS_05', false) ,
],
];
.env
IP_LIMIT_ENABLE=true
# IP_LIMIT_PROXY: Laravelがバランサー配下に設置されている場合にtrueにすると、Real IPを取得する
IP_LIMIT_PROXY=false
IP_LIMIT_ENABLE_ADDRESS_01=xxx.xxx.xxx.xxx/24
IP_LIMIT_ENABLE_ADDRESS_02=yyy.yyy.yyy.yyy
IP_LIMIT_ENABLE_ADDRESS_03=
IP_LIMIT_ENABLE_ADDRESS_04=
IP_LIMIT_ENABLE_ADDRESS_05=
routes/web.php
ミドルウェアをかませます
Route::get('/register', 'RegisterController@showRegistrationForm')->name('register');
↓
Route::group(['middleware' => 'iplimit'], function () {
Route::get('/register', 'RegisterController@showRegistrationForm')->name('register');
});
以上です。
Laravelの標準のエラーページをカスタマイズするには
/resources/views/errors/404.blade.php にファイルを置くだけです。
ファイル名は
403.blade.php
404.blade.php
500.blade.php
とすると、404エラーの時は 「404.blade.php」を見に行きます。
abort( 404, '独自のエラーメッセージ' );
{{ $exception->getMessage() }}
これだけで、エラーページにメッセージが表示されます。
デフォルトのデザイン ↑ にしたい場合は次のようなコードにします
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Not Found</title>
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
<style>
html,
body {
background-color: #fff;
color: #636b6f;
font-family: 'Nunito', sans-serif;
font-weight: 100;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.content {
text-align: center;
}
.title {
font-size: 36px;
padding: 20px;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title">
Sorry, the page you are looking for could not be found.
</div>
</div>
</div>
</body>
</html>
以下のようにすると .env で APP_DEBUG=1 の時だけエラーメッセージを表示させることができます。
@if(env('APP_DEBUG') == 1)
{{ $exception->getMessage() }}
@endif
例:
Laravel のデバッグパッケージといえば laravel/telescope とlaravel-debugbar の2つが有名ですが、ここでは laravel-debugbar の使い方を紹介いたします。
composer require barryvdh/laravel-debugbar
composer require barryvdh/laravel-debugbar:~2.4
.env の設定が以下になっていることを確認します。
APP_DEBUG=true
Google Chrome で Laravelアプリを起動すると、
laravel-debugbar を インストールすると debug() メソッドが使用できるようになります。
これは dump() メソッドの出力を laravel-debugbarのウィンドウ内に表示するものです。
dump('test); // 通常のダンプ
↓
debug('test); // debug-bar に表示させるダンプ
次のように設定すると laravel-debugbarのみ無効 にできます
APP_DEBUG=true
DEBUGBAR_ENABLED=false # laravel-debugbarを無効
laravel-debugbar は 実行するSQL文が多いと重くなるので、ページごとに オンオフを切り替えたい時があります。
次のように オン/オフ します。
\Debugbar::enable();
\Debugbar::disable();
PHPのFakerです。Faker Senpai ではないです。
Laravelだと標準でFakerがインストールされ、とても簡単に扱えるのでデータを作成する際には是非利用しましょう。
(Laravelアプリのインストール、モデルファイルはすでに用意してある前提で進めます。 )
config/app.php の一番下に以下を追加
'faker_locale' => 'ja_JP',
routes/web.php に以下を追加
Route::get('/faker', function () {
$faker = Faker\Factory::create('ja_JP');
$dummyData = [
'random_no' => $faker->randomNumber(4) , // 最大4桁の数字
'name' => $faker->name,
'password' => $faker->password,
'country' => $faker->country,
'prefecture' => $faker->prefecture,
'city' => $faker->city,
'postcode' => $faker->postcode,
'address' => $faker->address,
'streetAddress' => $faker->streetAddress,
'phoneNumber' => $faker->phoneNumber,
'email' => $faker->email,
'safeEmail' => $faker->safeEmail, // (実在しないアドレスのため処理とかで使っても安心)
'company' => $faker->company,
'iso8601' => $faker->iso8601($max = 'now'),
'dateTimeBetween' => $faker->dateTimeBetween($startDate = '-110 years', $endDate = 'now')->format('Y年m月d日'),
'numberBetween' => $faker->numberBetween($min = 100, $max = 200),
'title' => $faker->title,
'realText' => $faker->realText($maxNbChars = 50, $indexSize = 2),
'randomNumber' => $faker->randomNumber($nbDigits = NULL),
'randomFloat' => $faker->randomFloat($nbMaxDecimals = NULL, $min = 0, $max = NULL),
'randomElement' => $faker->randomElement($array = ['男性', '女性']),
'lexify' => $faker->lexify($string = '??????'),
'hexcolor' => $faker->hexcolor,
'ipv4' => $faker->ipv4,
'url' => $faker->url,
'imageUrl' => $faker->imageUrl($width = 640, $height = 480, $category = 'cats', $randomize = true, $word = null),
'userAgent' => $faker->userAgent,
'creditCardType' => $faker->creditCardType,
'creditCardNumber' => $faker->creditCardNumber,
'creditCardExpirationDate' => $faker->creditCardExpirationDate,
'isbn13' => $faker->isbn13,
'isbn10' => $faker->isbn10,
];
dump($dummyData);
exit();
});
http://YOUR-APP.TLD/faker にアクセスして表示させます。 次のようなダミーデータが表示されます。
array:31 [▼
"random_no" => 724
"name" => "浜田 舞"
"password" => "sPz?uT[rr"
"country" => "ドミニカ国"
"prefecture" => "埼玉県"
"city" => "廣川市"
"postcode" => "9926575"
"address" => "1202087 大阪府加藤市西区原田町廣川1-8-9"
"streetAddress" => "若松町山岸5-1-2"
"phoneNumber" => "05-4265-9978"
"email" => "mitsuru67@gmail.com"
"safeEmail" => "chiyo.kanou@example.net"
"company" => "株式会社 喜嶋"
"iso8601" => "1984-04-25T21:15:23+0000"
"dateTimeBetween" => "1974年08月07日"
"numberBetween" => 125
"title" => "Dr."
"realText" => "るしていただおじぎを捕とりと歴史れきしになって、と思ってすうりんどんどん流ながら上着うわぎのりを川。"
"randomNumber" => 55
"randomFloat" => 6539745.6
"randomElement" => "男性"
"lexify" => "znkbep"
"hexcolor" => "#fb573b"
"ipv4" => "136.204.203.254"
"url" => "http://wakamatsu.jp/delectus-aspernatur-unde-quae-explicabo-aut-nisi"
"imageUrl" => "https://lorempixel.com/640/480/cats/?97142"
"userAgent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_2) AppleWebKit/5330 (KHTML, like Gecko) Chrome/37.0.841.0 Mobile Safari/5330"
"creditCardType" => "Visa"
"creditCardNumber" => "4485419150999"
"creditCardExpirationDate" => DateTime @1634115758 {#207 ▼
date: 2021-10-13 09:02:38.0 UTC (+00:00)
}
"isbn13" => "9793648060130"
"isbn10" => "7424451421"
]
引用: https://goo.gl/VmSXqV
参考: https://fwhy.github.io/faker-docs/
参考: https://bit.ly/3roaudM
モデル「Client.php (DBテーブル名:client)」に対する「ClientFactory」を作成します。
php artisan make:factory ClientFactory
完了すると database/factories/ClientFactory.php が作成されています
デフォルトでは
$factory->define(Model::class, function (Faker $faker) {
return [
//
];
});
となっているのでここに設定を記述して行きます。
return [
'client_name' => $faker->name ,
'addr_name' => $faker->address,
];
またクラス名も変更しておきます。
use App\Model;
use Faker\Generator as Faker;
$factory->define(Model::class, function (Faker $faker) {
↓
use App\Client;
use Faker\Generator as Faker;
$factory->define(App\Client::class, function (Faker $faker) {
該当クラス( Client )に書き換えた例。↑
これでOKです。
以下の内容で database/seeds/ClientsSeeder.php を作成する
<?php
use Illuminate\Database\Seeder;
class ClientsSeeder extends Seeder {
public function run()
{
// 10件作成
factory(App\Client::class, 10)->create();
}
}
シーダー実行ファイルに記述してあげます
database/seeds/DatabaseSeeder.php
public function run()
{
$this->call(ClientsSeeder::class);
}
まずキャッシュを削除する
php artisan cache:clear; php artisan config:clear; php artisan route:clear; php artisan view:clear; composer dump-autoload
データを全て作り直し、シーダーを実行します
php artisan migrate:fresh --seed
ルーターで
Route::group(['prefix' => 'admin'], function () {
のように、プレフィクスをつけている場合に Bladeでその値を取得する方法。
@php
$prefix = Request::route()->getPrefix()
@endphp
としておいてから
{{URL::to($prefix.'/articles/search')}}
このようにも書けます
{{ url("$prefix/articles/search") }}
例
http://YOUR-SITE.TLD/admin/articles/search
もちろん、わざわざこんなことをせずに素直に
{{ route('articles.search') }}
と書くのがいいです。
resources/lang/ja.json を以下の内容で作成するだけでメッセージが日本語になります。
{
"Login":"ログイン",
"E-Mail Address":"メールアドレス",
"Password":"パスワード",
"Remember Me":"ログイン状態を保存する",
"Forgot Your Password?":"パスワードをお忘れですか ?",
"Register":"登録",
"Name":"お名前",
"Confirm Password":"パスワード(確認用)",
"Reset Password":"パスワードリセット",
"Send Password Reset Link":"パスワードリセットリンク送信",
"Logout":"ログアウト",
"Verify Your Email Address":"ユーザ登録を完了してください",
"A fresh verification link has been sent to your email address.":"新しいリンクをあなたのメールアドレスに送信しました。",
"Before proceeding, please check your email for a verification link.":"メールに記載されているリンクをクリックして登録手続きを完了してください。",
"If you did not receive the email":"もしメールが届いていない場合は",
"click here to request another":"こちらをクリックして確認メールを再送信してください",
"Please click the link below to verify your email address.":"メールアドレスを確認するために下のリンクをクリックしてください。",
"Verify Email Address":"メールアドレス確認",
"If you did not create an account, no further action is required.":"心当たりがない場合は、本メッセージは破棄してください。",
"Click button below and reset password.":"下のボタンをクリックしてパスワードを再設定してください。",
"Reset password":"パスワードリセット",
"If you did not request a password reset, no further action is required.":"心当たりがない場合は、本メッセージは破棄してください。"
}
https://qiita.com/nekyo/items/eba7f145a71a5d04b57d
https://github.com/caouecs/Laravel-lang/tree/master/src/ja
https://github.com/caouecs/Laravel-lang
'locale' => 'en',
↓
'locale' => 'ja',
'timezone' => 'UTC',
↓
'timezone' => 'Asia/Tokyo',
以上です。
php artisan vendor:publish --tag=laravel-mail
php artisan vendor:publish --tag=laravel-notifications
コマンドを実行すると
resources/views/vendor/mail/(複数のテンプレートファイル)
resources/views/vendor/notifications/email.blade.php
ファイルが自動生成されます。 変更する箇所は次の画像の通りです。
↓ すべて日本語化すると次のようになります
app/Http/Middleware/RedirectIfNotUser.php
public function handle($request, Closure $next, $guard = 'user')
{
if (!Auth::guard($guard)->check()) {
return redirect('user/login'); // ここを書き換えます
}
ログにはレベルがあります。( PSR-3 )
PSR-3: Logger Interface: https://www.php-fig.org/psr/psr-3/
(危険な順)
8. EMERGENCY
7. ALERT
6. CRITICAL
5. ERROR
4. WARNING
3. NOTICE
2. INFO
1. DEBUG
このログレベルを利用して
・ログレベルが ERROR 以上の時はログに保存しつつメールで送信
・ログレベルが WARNING 以下の時はログに保存
という運用ができるように設定してみます。
コントローラーのコンストラクタに以下を記述
protected $monolog;
public function __construct()
{
// umask。適宜書き換えてください。
umask(0000);
$this->monolog = Log::getLogger(); // laravel 5.6以降
// 1. monolog - mail
$transport = new \Swift_SendmailTransport('/usr/sbin/sendmail -t');
$mailer = new \Swift_Mailer($transport);
$message = new \Swift_Message('monolog ERROR');
$message->setFrom('メールアドレス');
$message->setTo('メールアドレス');
$this->monolog->pushHandler(new \Monolog\Handler\SwiftMailerHandler($mailer, $message, \Monolog\Logger::ERROR, false));
// 2. monolog - file - rotation
$formatter = new \Monolog\Formatter\LineFormatter(null, null, true);
$handler = new \Monolog\Handler\RotatingFileHandler(storage_path('logs').DIRECTORY_SEPARATOR.'error.log', 10, \Monolog\Logger::ERROR );
$handler->setFormatter($formatter);
$this->monolog->pushHandler($handler);
}
次のメソッドでログに記録します。
ログファイルに記録
$this->monolog->debug('1. debug');
$this->monolog->info('2. info');
$this->monolog->notice('3. notice');
$this->monolog->warning('4. warning');
ログファイルに記録(ファイル名と行番号)
$this->monolog->info('ログメッセージ', ['file' => basename(__FILE__), 'line' => __LINE__]); // ファイル名(フルパス)
$this->monolog->info('ログメッセージ', ['file' => basename(__FILE__), 'line' => __LINE__]); // ファイル名のみ
ログファイルに記録 + メール送信
$this->monolog->error('5. error');
$this->monolog->critical('6. critical');
$this->monolog->alert('7. alert');
$this->monolog->emergency('8. emergency');
cron実行時などは手動でログを出力して保存しておくと後からのデバッグが楽になります
config/logging.php の 'channels に追加します。
'channels' => [
// 追加したチャンネル [apidebug]
'apidebug' => [
'driver' => 'daily',
'path' => storage_path('logs/apidebug.log'),
'level' => 'info',
'days' => 30,
'permission' => 0666,
],
// ロガー
$logger = \Log::channel('apidebug')->getLogger();
$logger->info( "TEST LOG", ['file' => basename(__FILE__), 'line' => __LINE__] );
storage/logs/apidebug-2019-xx-xx.log 次のようなログが 出力されます
[2019-08-26 11:34:06] local.INFO: TEST LOG {"file":"ApiDebugController.php","line":87}
// DEBUG
$monolog->debug($message, $context);
// INFO
$monolog->info($message, $context);
// NOTICE
$monolog->notice($message, $context);
// WARNING
$monolog->warning($message, $context);
// ERROR
$monolog->error($message, $context);
// CRITICAL
$monolog->critical($message, $context);
// ALERT
$monolog->alert($message, $context);
// EMERGENCY
$monolog->emergency($message, $context);
use Log;
$monolog = Log::getLogger();
$ip = new \Monolog\Processor\IntrospectionProcessor(
\Monolog\Logger::DEBUG,
[
'Monolog\\',
'Illuminate\\',
]
);
$monolog->pushProcessor($ip);
このようなログになります
[2018-11-09 01:52:35] local.WARNING: テーブルからデータを取得できません {"file":"/home/app/Http/Controllers/TestController.php","line":231,"class":"App\\Http\\Controllers\\TestController","function":"postarticle"}
とても便利なので是非利用しましょう。
config/app.php を以下のように変更します
'timezone' => 'Asia/Tokyo',
LaravelのコーディングスタイルはPSR-2に準拠。
PSR-2 スタイルの特徴
PSR-1のコーディング規約には従わなければなりません。
インデントには4つのスペースを利用しなければなりません。タブは使用してはいけません。
1行の長さに制限はありませんが、120文字以下が望ましいです。できれば80文字以下にして下さい。
クラス・メソッドの波括弧の前には改行を入れなければなりません。
メソッドやプロパティの定義は最初にabstract/final、次にpublic/protected/private、最後にstaticを書かなければなりません。
制御構文開始の波括弧の前には改行を入れません。
制御構文の(の後、)の前にはスペースを入れません。
単数形複数形には注意する事。
http://www.infiniteloop.co.jp/docs/psr/psr-2-coding-style-guide.html
種類 | 単数 or 複数 | ケース | 例 |
---|---|---|---|
モデル (ファイル名) | 単数形 | パスカルケース(先頭大文字) | Post.php MsCountry.php |
テーブル名 | 複数形 | スネークケース(小文字) | posts ms_countries |
コントローラ (ファイル名) | どちらもOK | パスカルケース(先頭大文字) | PostController.php MsCountryController.php |
クラス | どちらもOK | パスカルケース(先頭大文字) | PostController MsCountryController |
メソッド | --- | キャメルケース(先頭小文字) | public function searchUser() |
変数 | --- | スネークケース(小文字) | $user_name |
名前が決まっているカラム
カラム名 | 何を表すか? |
---|---|
id | 主キー |
created_at | 登録日時 |
updated_at | 更新日時 |
Laravelではフォーム送信時に先頭と最後のスペース(改行も。)は自動削除されます。
これを停止するには
Http/Kernel.php の 次の行をコメントアウトします。
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
// \App\Http\Middleware\TrimStrings::class, // この行をコメントアウトする
/etc/nginx/conf.d/YOUR-SITE.conf
KUSANAGI9 の場合は /etc/opt/kusanagi/nginx/conf.d/
location /my_app/ {
try_files $1 /my_app/index.php?$query_string;
}
nginx -s reload で nginx をリスタートすればOKです。
my_app に シンボリックリンクを貼ってください。
KUSANAGIを使っている場合は設定ファイルが2つに分かれている事があります YOUR-SITE_http.conf , YOUR-SITE_ssl.conf の2ファイルを編集しましょう
こちらに公式の設定ファイルがあるので、手元の設定ファイルと比較して揃えておきましょう https://laravel.com/docs/5.8/deployment#nginx
管理者側テーブル「admin」を利用した認証とユーザー側テーブル「users」を利用した認証を作成する
「素早く」がテーマなので composer パッケージを利用します。
「Laravelアプリの作成」「DB接続設定」は設定完了しているものとします。
https://github.com/Hesto/multi-auth
composer require hesto/multi-auth
Laravel 6 , 7の場合は次のパッケージもインストールします
composer require laravel/helpers
Laravel 8 , 9 の場合は次のパッケージもインストールします
composer require laravel/ui
php artisan multi-auth:install admin -f
php artisan multi-auth:install user -f
database/seeders/DatabaseSeeder.php
public function run()
{
// この行を追加 ↓
$this->call(AdminsSeeder::class);
}
database/seeders/AdminsSeeder.php を新規作成
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class AdminsSeeder extends Seeder
{
public function run()
{
DB::table("admins")->insert([
'name' => 'YOUR-NAME',
'email' => 'YOUR-EMAIL',
'password' => Hash::make('YOUR-PASSWORD'),
]);
}
}
composer dump-autoload
php artisan migrate
php artisan db:seed
例
Route::get('/login', 'UserAuth\LoginController@showLoginForm')->name('login');
↓
use Illuminate\Support\Facades\Route;
Route::get('/login', [App\Http\Controllers\UserAuth\LoginController::class, 'showLoginForm'])->name('login');
以上です。 (composer dump-autoload は必ず実行しましょう。クラスが not found になってしまう可能性があります。)
http://YOUR-SITE.TLD/admin/login
http://YOUR-SITE.TLD/user/login
Bladeテンプレートで adminまたはuserでログインしているかどうかを判別する
@if ( Auth::guard('admin')->check() )
<h1>adminとしてログイン済みです</h1>
@endif
@if ( Auth::guest() )
<h1>管理者ログイン前のguestです</h1>
@endif
user の場合
{{ Auth::guard('user')->user()->name }}
admin の場合
{{ Auth::guard('admin')->user()->name }}
use \Illuminate\Support\Facades\Auth;
$loginUser = Auth::user();
dump( \Illuminate\Support\Facades\Auth::guard('user')->user()->name );
// 強制ログアウト
Auth::guard('user')->logout();
if ( Auth::guard('admin')->check() ){
// ..................
}
// 特定のユーザーでログインさせる(後ろに true をつけると自動ログイン(ログインを記憶)となります。)
\Auth::login($user, true);
app/Http/Kernel.php には以下のような記述が増えています
protected $routeMiddleware = [
'user' => \App\Http\Middleware\RedirectIfNotUser::class,
'user.guest' => \App\Http\Middleware\RedirectIfUser::class,
'admin' => \App\Http\Middleware\RedirectIfNotAdmin::class,
'admin.guest' => \App\Http\Middleware\RedirectIfAdmin::class,
........
なのでこれを使用しましょう。
コントローラのコンストラクタに以下のように記述します
function __construct(){
// admin ログイン認証
$this->middleware('admin');
}
routes/web.php に次のように 'middleware' => 'admin' ,'as' => 'admin.' を追加します
// ● admin ログインが必要なページ
Route::group(['prefix' => 'admin', 'middleware' => 'admin' ,'as' => 'admin.' ], function () {
Route::get("articles/index", "ArticleController@index")->name("articles.index");
});
routes/admin.php に記述する場合は次のようにします。
合わせて app/Providers/RouteServiceProvider.php もチェックしておきます。
// ● admin ログインが必要なページ
// check app/Providers/RouteServiceProvider.php
//
Route::group(['namespace' => 'Admin'], function () {
Route::resource("posts","PostController");
});
ログアウトした時のリダイレクト先はコントローラーで設定します
管理者用、ユーザー用のAuthコントローラーは
/app/Http/Controllers/AdminAuth/LoginController.php
/app/Http/Controllers/UserAuth/LoginController.php
にあるので、ここに記述します。
( vendor/hesto/multi-auth/src/Traits/LogsoutGuard.php のトレイトをオーバーライドします )
/**
*
* ログアウト後のリダイレクト先を「/admin/login」に設定する
*
*/
public function logoutToPath() {
return '/admin/login';
}
デフォルトでは routes/user.php に
Route::get('/home', function () {
$users[] = Auth::user();
$users[] = Auth::guard()->user();
$users[] = Auth::guard('admin')->user();
return view('admin.home');
})->name('home');
という風にクロージャで /user/home へのルーティングが記述されています。 これらをコメントアウトで消してしまってroutes/web.php に記述していくと見通しがいいと思います。
デフォルトではログイン成功時には /admin/home に全てリダイレクトされますが、次のようなケース
(ログインしていない状態で)
/admin/special(ログインが必要なページ)へアクセス
↓
/admin/login(ログイン画面へリダイレクト)
↓
ID, PASS 入力(ログイン成功)
↓
もともとアクセスしたページ(/admin/special)へリダイレクトしたい!
に対応するには次の箇所を書き換えます。
app/Http/Middleware/RedirectIfNotAdmin.php
public function handle($request, Closure $next, $guard = 'admin')
{
if (!Auth::guard($guard)->check()) {
\Session::put('RedirectIfNotAdmin_next_url', $request->fullUrl()); // ●追加● ログイン成功後の遷移先を保存
return redirect('admin/login');
}
return $next($request);
}
app/Http/Controllers/AdminAuth/LoginController.php 以下の login()メソッドを丸々追加します。
/**
*
* ログイン時のメソッド
*
*/
public function login(\Illuminate\Http\Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
// ● 追加 ↓
$RedirectIfNotAdmin_next_url = \Session::get('RedirectIfNotAdmin_next_url', null );
\Session::forget('RedirectIfNotAdmin_next_url');
if ( $RedirectIfNotAdmin_next_url ){
return redirect( $RedirectIfNotAdmin_next_url );
}
// ● 追加 ↑
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
app/Notifications/UserResetPassword.php を以下のように変更
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url('user/password/reset', $this->token))
->line('If you did not request a password reset, no further action is required.');
↓ テンプレート (resources/views/email/password_reset.blade.php) を使ってメール送信するように変更する
return (new MailMessage)
->from($email_address, $email_name)
->view( 'email.password_reset' , [
'shop' => $shop ,
'reset_url' => url('user/password/reset', [$shop->id, $this->token]) ,
]);
メール送信者はデフォルトで .env でしたいしたアドレスになるので、明示的に変更したい場合は ->from() メソッドで書き換えます
独自のパタメーターは配列で渡します。( 上の例では $shop を渡している)
'reset_url は適宜書き換えてください。
テンプレート例 (resources/views/email/password_reset.blade.php に保存)
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<style>
body{
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';
box-sizing:border-box;
}
</style>
<div style="background-color:#f8fafc;color:#74787e;height:100%;line-height:1.4;margin:0;width:100%!important;word-break:break-word">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f8fafc;margin:0;padding:0;width:100%">
<tbody>
<tr>
<td align="center">
<table width="100%" cellpadding="0" cellspacing="0" style="margin:0;padding:0;width:100%">
<tbody>
<tr>
<td style="padding:25px 0;text-align:center">
<a href="{{ url('/user/login', $shop->id) }}" style="color:#bbbfc3;font-size:19px;font-weight:bold;text-decoration:none" target="_blank" >
{{ $shop->name }}
</a>
</td>
</tr>
<tr>
<td width="100%" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-bottom:1px solid #edeff2;border-top:1px solid #edeff2;margin:0;padding:0;width:100%">
<table align="center" width="570" cellpadding="0" cellspacing="0" style="background-color:#ffffff;margin:0 auto;padding:0;width:570px">
<tbody>
<tr>
<td style="padding:35px">
<p style="color:#3d4852;font-size:16px;line-height:1.5em;margin-top:0;text-align:left">「パスワード再設定」<wbr>ボタンを押してパスワードを再設定してください。</p>
<table align="center" width="100%" cellpadding="0" cellspacing="0" style="margin:30px auto;padding:0;text-align:center;width:100%">
<tbody>
<tr>
<td align="center">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td>
<a href="{{ $reset_url }}" style="border-radius:3px;color:#fff;display:inline-block;text-decoration:none;background-color:#3490dc;border-top:10px solid #3490dc;border-right:18px solid #3490dc;border-bottom:10px solid #3490dc;border-left:18px solid #3490dc" target="_blank" >パスワード再設定</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p style="color:#3d4852;font-size:16px;line-height:1.5em;margin-top:0;text-align:left">もしこのメッセージに心当たりがない場合は破棄してください。</p>
<table width="100%" cellpadding="0" cellspacing="0" style="border-top:1px solid #edeff2;margin-top:25px;padding-top:25px">
<tbody>
<tr>
<td>
<p style="color:#3d4852;line-height:1.5em;margin-top:0;text-align:left;font-size:12px">もし上の "パスワード再設定" ボタンを押してもうまく動作しない時はこちらのURLをブラウザ<wbr>に貼り付けてアクセスしてください。
<a href="{{ $reset_url }}" style="color:#3869d4" target="_blank">
{{ $reset_url }}
</a>
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<table align="center" width="570" cellpadding="0" cellspacing="0" style="margin:0 auto;padding:0;text-align:center;width:570px">
<tbody>
<tr>
<td align="center" style="padding:35px">
copyright (c)2019 YOUR-SITE.COM
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
モデルが User の場合は /app/User.php に以下を追加
/**
* パスワード再設定メールの送信
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify( new \App\Notifications\UserResetPassword($token) );
}
app/Notifications/UserResetPassword.php を以下のように変更
// OFF ->line('You are receiving this email because we received a password reset request for your account.')
// OFF ->action('Reset Password', url('user/password/reset', $this->token))
// OFF ->line('If you did not request a password reset, no further action is required.');
->line('「パスワード再設定」ボタンを押してパスワードを再設定してください。')
->action('パスワード再設定', url('user/password/reset', $this->token))
->line('もしこのメッセージに心当たりがない場合は破棄してください。');
php artisan vendor:publish --tag=laravel-mail
php artisan vendor:publish --tag=laravel-notifications
コマンドを実行すると
resources/views/vendor/mail/(複数のテンプレートファイル)
resources/views/vendor/notifications/email.blade.php
ファイルが自動生成されます。 変更する箇所は次の画像の通りです。
↓ すべて日本語化すると次のようになります
app/Http/Middleware/RedirectIfNotUser.php
public function handle($request, Closure $next, $guard = 'user')
{
if (!Auth::guard($guard)->check()) {
return redirect('user/login'); // ここを書き換えます
}
Laravelでフォーム入力値のバリデーションを行った後、独自のチェックを行って、エラーが発生した場合 エラーメッセージと共に入力画面へ戻したい。といったことがあるかと思います。
次のようにして好きなメッセージでエラーメッセージを設定できます。
use Validator;
// 通常のバリデーション
$validator = Validator::make($request->all(), [
'feed_url' => 'required',
]);
$validator->validate();
// 追加で入力チェックを行う
if ( エラーがある場合 ) {
$validator->errors()->add('feed_url', 'このURLにはRSSフィードが含まれていません。');
return back()->withInput()->withErrors($validator);
}
エラーメッセージのセット方法は次のように記述してもOKです。
return back()->withInput()->withErrors(array('feed_url' => 'このURLにはRSSフィードが含まれていません。'));
ざっと見たところ、「simplepie/simplepie」のLaravel5用ラッパーです。
composer require awjudd/feed-reader
config/app.php の編集
'providers' => [
.....
Awjudd\FeedReader\FeedReaderServiceProvider::class, // この行を追加
'aliases' => [
.....
'FeedReader' => Awjudd\FeedReader\Facade::class,, // この行を追加
php artisan vendor:publish
( [1 ] Provider: Awjudd\FeedReader\FeedReaderServiceProvider )を選択します。
use Awjudd\FeedReader\Facade as FeedReader;
$feed = FeedReader::read('https://RSS-FEED-SITE-URL');
if ( $feed->error() ) {
echo $feed->error();
}
foreach ($feed->get_items() as $item) {
$hash = [];
$hash['site_title'] = $item->get_feed()->get_title();
$hash['title'] = trim($item->get_title());
$hash['permalink'] = trim($item->get_permalink());
$hash['link'] = trim($item->get_link());
$hash['date'] = $item->get_date('Y-m-d H:i:s');
$hash['content'] = $item->get_content();
dump($hash);
}
中身は「simplepie/simplepie」なので下記を参照してください。
https://github.com/simplepie/simplepie
https://goo.gl/ma6j9T
Laravelでデータベース sqlite を使用します。データベースファイルは database/database.sqlite になります。
mysqlの設定を # でコメントアウトして、sqlite設定を記述します。
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=homestead
# DB_USERNAME=homestead
# DB_PASSWORD=secret
DB_CONNECTION=sqlite
touch database/database.sqlite
php artisan migrate
以上です。簡単ですね。
.env にフルパスでDBファイルを指定します。
例: /PATH/TO/YOUR/APP/database/production.sqliteを指定する
DB_DATABASE=/PATH/TO/YOUR/APP/database/production.sqlite
https://github.com/calebporzio/gitdown
composer require calebporzio/gitdown
{{ $memo->text_name }}
↓
{!! GitDown::parseAndCache($memo->text_name) !!}
これだけでマークダウンでパースされます。
@gitdown
以上です。最速。
composer require andreasindal/laravel-markdown
{{ $memo->text_name }}
↓
@markdown($memo->text_name)
これだけでマークダウンでパースされます。
Laravelで httpの全てのGETデータやPOSTデータは
$request->all();
で取得しますが、この時、取得したい項目を指定することができます。
「user_name」「email_name」を取得
$request->all(['user_name','email_name']);
なので、EloquentモデルでデータのUPDATEを行う時
$user->fill($request->all())->save();
↓
$user->fill( $request->all(['user_name','email_name']) )->save();
と書くことができます。 便利ですね。
例としてあるモデル(User)に独自カラム「_editable_flag」を値「normal」で追加する。
app/User.php に以下の記述を追加する
// 独自カラム
// protected $appends = array('editable_flag');
protected $appends = array('_editable_flag'); // このようにアンダースコア始まりのカラムも作成できます。(メソッド名は変更せずでOK)
// 独自カラムのアクセサ
public function getEditableFlagAttribute()
{
return 'normal';
}
デバッグ時にどのBladeビューファイルが使用されているかを確認したい時があります。 そんな時は
それぞれのテンプレートファイル名に配置
@if(env('APP_DEBUG') == 1)
@php($template_filename = 'show.blade.php')
@php($compiled_template_filename = __FILE__)
@endif
( show.blade.php はファイルごとに適宜書き換えてください。 )
@if(env('APP_DEBUG') == 1)
<!-- {{$template_filename or '' }} -->
<!-- {{$compiled_template_filename or '' }} -->
@endif
レイアウトファイルに記述しておきます。
Laravelのログはデフォルトでは1つのファイルに追記を繰り返していきます。
ローテーションさせましょう。
.envファイルのデフォルト値「stack」を「daily」に変更します。
.env
LOG_CHANNEL=stack
↓
LOG_CHANNEL=daily
これだけでローテーションできるようになります。
/config/logging.php の days 項目に設定できます。
'daily' => [
'days' => 7,
],
その他以下のような設定が可能です。
名前 | 説明 |
---|---|
stack |
「マルチチャンネル」チャンネルを作成するためのラッパー機能 |
single |
シングルファイル/パスベースのロガーチャンネル(StreamHandler ) |
daily |
RotatingFileHandler ベースの毎日ファイルを切り替えるMonologドライバ |
slack |
SlackWebhookHandler ベースのMonologドライバ |
syslog |
SyslogHandler ベースのMonologドライバ |
errorlog |
ErrorLogHandler ベースのMonologドライバ |
monolog |
サポートしているMonologハンドラをどれでも使用できる、Monologファクトリドライバ |
custom |
チャンネルを生成するため、指定したファクトリを呼び出すドライバ |
monolog を選択して、深刻なエラーの場合はメールなり通知を送信するという運用が実用的かと思われます。
/config/logging.php に permission 項目を記述できます。 (Laravel version 5.6.10 以降)
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 7,
'permission' => 0666,
],
https://packagist.org/packages/zizaco/entrust
https://itsolutionstuff.com/post/laravel-52-user-acl-roles-and-permissions-with-middleware-using-entrust-from-scratch-tutorialexample.html
https://packagist.org/packages/spatie/laravel-permission
https://packagist.org/packages/laravel/socialite
https://packagist.org/packages/rtconner/laravel-tagging
https://packagist.org/packages/cviebrock/eloquent-taggable
{{ \Request::fullUrl() }}
または
{{ request()->fullUrl() }}
例: http://test.server.tld/users/search?q=test
{{ url()->current() }}
例: http://test.server.tld/users/search
{{ str_replace(url('/'),"",request()->fullUrl()) }}
例: /search?q=test
{{ request()->path() }}
例: users/search
@if ( request()->is('*users*') )
マッチします<br>
@endif
{{ request()->url() }}
例: http://test.server.tld/users/search
{{ request()->method() }}
↓
GET
@if( request()->isMethod('get') )
getメソッドです<br>
@endif
例: PUT メソッドを埋め込みます
@method('PUT')
<input type="hidden" name="_method" value="PUT">
@csrf
<input type="hidden" name="_token" value="{{ csrf_token() }}"
LaravelのBladeテンプレートで未定義の変数を使用するとエラーとなります。 事前に isset で 確認しましょう。
@if (isset( $text ))
<p>$test</p>
@endif
変数が未定義の場合に null をセットする
@if ( ! isset($v->flag) )
<?php $v->flag = null; ?>
@endif
または @ をつけて未定義エラーを回避します
@if ( $q['data_id'] )
↓
@if ( @$q['data_id'] )
変数が存在しない場合に null (または空文字)表示でいいのなら
{{ @$hoge }}
LaravelのコントローラーでHTTPのGETパラメーターを受け取る
use Illuminate\Http\Request;
public function show($id)
{
↓ Request型の $request をコントローラーのメソッドで受け取るようにします。
public function show(Request $request, $id)
{
URL https://YOUR-SERVER.TLD/test/action?file=001 でアクセスする時
echo $request->input('file') ;
次のように出力されます
001
Laravelのbladeテンプレート、vue.jsのテンプレート、どちらも {{ }} で変数を囲う記法なので、混在するとLaravelでエラーとなります。 そこで対応方法が2つあるようです。
<div>
{{ message }}
</div>
↓
<div>
@{{ message }}
</div>
こちらの方法がオススメです。
Vue.config.delimiters = ['(%', '%)']; でデリミタを変更して使用します。
var vm = new Vue({
el: '#myApp',
data: {
}
});
↓
var vm = new Vue({
delimiters: ["(%","%)"] ,
el: '#myApp',
data: {
}
});
<?php $current_controller_name = explode('.', Route::currentRouteName())[0]; ?>
現在のコントローラー名は {{ $current_controller_name }}
記法 | 戻り値(例) |
---|---|
{{ Route::currentRouteName() }} | projects.edit |
記法 | 戻り値(例) |
---|---|
{{ Request::url() }} | https://MY-SERVER.TLD/projects/3/edit |
{{ Request::url("/") }} | https://MY-SERVER.TLD/projects/3/edit |
{{ Request::root() }} | https://MY-SERVER.TLD |
{{ Request::fullUrl() }} | https://MY-SERVER.TLD/projects/3/edit |
{{ Request::path() }} | projects/3/edit |
{{ Request::decodedPath() }} | projects/3/edit |
例
{{ urlencode(request()->fullUrl()) }}
記法 | 戻り値(例) |
---|---|
{{ url("") }} | https://MY-SERVER.TLD |
{{ url('/') }} | https://MY-SERVER.TLD |
{{ app_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app/app |
{{ base_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app |
{{ config_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app/config |
{{ database_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app/database |
{{ storage_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app/storage |
{{ resource_path() }} | /var/www/vhosts/MY-SERVER.TLD/laravel/my_app/resources |
laravelcollective/html を使用してドロップダウンリスト( select, option リスト) を簡単に作成します。
https://laravelcollective.com/docs/master/html
composer require "laravelcollective/html":"^5.4.0"
@php
$job_name_loop = [
'' => '選択してください' ,
'公務員' => '公務員' ,
'医師' => '医師' ,
'弁護士' => '弁護士' ,
];
@endphp
{{ Form::select('job_name', $job_name_loop, old('job_name'), ['class' => 'my_class']) }}
モデルから一覧を取得してきて、それを (key/value)方式に変換します。
コントローラーの好きなところに以下を記述します。
$clients = Client::select('id', 'client_name')->get();
// key,value ペアに直す
$client_id_loop = $clients->pluck('client_name','id');
return view('projects.create', compact('client_id_loop') );
フォーム名「FORM_NAME」 CSSクラス名「my_class」 なドロップダウンリストを生成します。
{{ Form::select('FORM_NAME', $client_id_loop, null, ['class' => 'my_class']) }}
次のようなドロップダウンリストが生成されます
<select class="my_class" name="FORM_NAME">
<option value="1">テスト1</option>
<option value="2">テスト2</option>
<option value="3">テスト3</option>
</select>
一撃で記述したい場合はこのように記述します。
bladeテンプレート内に記述
3つ目の引数に値を渡すと選択済みになります。null を渡すと最初の項目が選択済みになります
{{ Form::select('FORM_NAME', \App\Client::select('id', 'client_name')->get()->pluck('client_name','id')->prepend( "選択してください", ""), null, ['class' => 'form-control']) }}
3つめの引数に値をセットすると selected になります
{{ Form::select('FORM_NAME', $client_id_loop, 2, ['class' => 'my_class']) }}
↓ 次のようなドロップダウンリストが生成されます
<select class="my_class" name="FORM_NAME">
<option value="1">テスト1</option>
<option value="2" selected="selected">テスト2</option>
<option value="3">テスト3</option>
</select>
key が null の値を登録します。
{{ Form::select('FORM_NAME', [null=>'選択してください']+$client_id_loop, 2, ['class' => 'my_class']) }}
または prepend() メソッドを使って先頭に追加します。
prepend() メソッドを使用するときは 空文字 "" を使用します。( null を渡すと 連想配列から普通の配列になります。)
また 空文字 "" のところに 0 を渡すと、配列のキーが数字の場合 キーが 0,1,2,3,4,5.... という風にリナンバーされてしまうので注意してください。
(複数人数で開発する時は prepend() メソッドは使わずに、 foreach でベタに書くのもいいと思います。)
{{ Form::select('FORM_NAME', \App\Client::select('id', 'client_name')->get()->pluck('client_name','id')->prepend( "選択してください", ""), null, ['class' => 'form-control']) }}
↓ 次のようなドロップダウンリストが生成されます
<select class="my_class" name="FORM_NAME">
<option value="">選択してください</option>
<option value="1">テスト1</option>
<option value="2" selected="selected">テスト2</option>
<option value="3">テスト3</option>
</select>
disabled な値を追加するにはこちら
https://goo.gl/cBJVaM
Laravelでテーブルが2つ「clients」「users」あって、「clients」テーブル内に登録者IDと更新者IDが格納されている場合に 更新者の名前を取得する方法。
app/Client.php
DBカラムの
「clients.created_user_id」と「users.id」と同じデータがあれば取得します。
「clients.updated_user_id」と「users.id」と同じデータがあれば取得します。
/**
* 1対1リレーション
*
* @return \Illuminate\Database\Eloquent\Relations\hasOne
*/
public function created_user()
{
return $this->hasOne('App\User', 'id', 'created_user_id')->withDefault();
}
/**
* 1対1リレーション
*
* @return \Illuminate\Database\Eloquent\Relations\hasOne
*/
public function updated_user()
{
return $this->hasOne('App\User', 'id', 'updated_user_id')->withDefault();
}
->withDefault() がミソですね。存在しないときに中身が空のオブジェクトを返します。
$clients = Client::with('created_user','updated_user')->get();
app/Http/Controllers/ClientController.php
public function index( Request $request )
{
$clients = Client::with('created_user')
->with('updated_user')
->orderBy('id', 'desc')->paginate( 10 );
}
LaravelのCRUD操作のメソッドとルーティング一覧
例 : teams モデルの場合
HTTPメソッド | URL例 | ルーティング名 | メソッド名 | |
---|---|---|---|---|
データの一覧表示 | ||||
GET,HEAD | teams | teams.index | App\Http\Controllers\TeamController@index | |
データの詳細表示 | ||||
GET,HEAD | teams/123456 | teams.show | App\Http\Controllers\TeamController@show | |
データの新規作成 | ||||
GET,HEAD | teams/create | teams.create | App\Http\Controllers\TeamController@create | |
POST | teams | teams.store | App\Http\Controllers\TeamController@store | |
データの更新 | ||||
GET,HEAD | teams/123456/edit | teams.edit | App\Http\Controllers\TeamController@edit | |
PUT,PATCH | teams/123456 | teams.update | App\Http\Controllers\TeamController@update | |
データの削除 | ||||
DELETE | teams/123456 | teams.destroy | App\Http\Controllers\TeamController@destroy |
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
use \Illuminate\Http\Request;
Route::get("/{id}", function (Request $request, $id) {
return App\Http\Controllers\HomeJaController::index( $request, $id ); }
})->where([ 'id' => '[0-9]+' ]);
( id は 正規表現で数字のみに限定しておきます。 )
/routes/web.php
メソッド: 「PUT」
URL: 「teams/{id}/myteamupdate」
を ↓
コントローラー「TeamController」の メソッド「myteamupdate」
にルーティングする
を
Route::put("teams/{id}/myteamupdate", "TeamController@myteamupdate")->name('teams.myteamupdate'); // Add this line in routes.php
/routes/web.php
clients のCRUD関連メソッドを一括でルーティングする
Route::resource("clients","ClientController");
CRUDから一部を除外する方法です
/routes/web.php
Route::resource("clients","ClientController");
↓ 「SHOWメソッド」「DELETE メソッド」を除外します
Route::resource('clients', 'ClientController', ['except' => ['show', 'delete'] ]);
コマンドラインから以下のコマンドでルーティングを確認します。
peco コマンドがある場合は peco を通しましょう。
php artisan route:list | peco
Laravelにはメールを送信するクラス(mailable)が最初から用意されています。
これを使って管理者宛にメールを送信してみます。
今回管理者宛にメール送信するプログラムなので名前を「Admin」とします。(命名は自由です好きに書き換えてください)
app/Mail/Admin.php というファイルを次のコマンドで生成します。
php artisan make:mail Admin
app/Mail/Admin.php が生成されました。 以下のように修正します。
public function build() の中を書き換えます
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class Admin extends Mailable
{
use Queueable, SerializesModels;
private $message;
/**
* Create a new message instance.
* @return void
*/
public function __construct( $m )
{
$this->message = $m;
}
/**
* Build the message.
* @return $this
*/
public function build()
{
// ここを書き換えます ↓
return $this->from('FROM-ADDRESS@YOUR-DOMAIN.COM')
->subject( "エラーのお知らせ" )
->view('mails.admin')
->with([
'user_name' => 'テスト 太郎' ,
'my_text' => $this->message ,
]);
// ここを書き換えます ↑
}
}
まず、 resources/views へ移動し、ディレクトリ mails/ を作成します。 そしてその中にファイル admin.blade.php を以下の内容で作成します
こんにちは{{ $user_name }} !!!<br>
テストメールです<br>
{!! $my_text !!}
コントローラーの好きな所に記述します。
use Illuminate\Support\Facades\Mail;
use App\Mail\Admin;
// メール送信
$t = '任意のテキストをここに記述します';
Mail::to('TO-ADDRESS@YOUR-DOMAIN.COM')->send( new Admin($t) );
正しくメール送信が行える設定になっているか .env ファイルを確認します。
ローカルサーバではなくインターネット上にあるサーバの場合はとりあえずは以下の設定でメール送信が可能です。
MAIL_FROM_ADDRESS=<メールアドレス>
MAIL_FROM_NAME=<メールアドレスの横に表示させる名前>
MAIL_DRIVER=sendmail
MAIL_HOST=localhost
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
メール送信を記述したコントローラにアクセスしてメールを送信してみます。
このようなメールが到着します
.env の MAIL_DRIVER には次の指定ができるようです。
MAIL_DRIVERの値 | 説明 | 補足 |
---|---|---|
sendmail | PHPのmail関数で送信する(一番簡単) | |
smtp | SMTPサーバーへログインして送信する(id,passが必要) | |
mailgun | MailgunというAPIベースのメール配信サービスから送信する | |
mandrill | MailgunというAPIベースのメール配信サービスから送信する | |
ses | Amazon AWSが提供するAmazon SESから送信する | |
sparkpost | SparkPostというAPIベースのメール配信サービスから送信する | |
log | 送信は行わず、ログファイルに内容を書き込む | storage/logs/laravel.log に内容が記述されます |
array | ロジックは通りますが、送信はされません |
$model->touch();
これだけです、これだけで下記のようなSQL文が実行されます
[query] => update `MY-TABLE` set `updated_at` = ? where `id` = ?
[bindings] => Array
(
[0] => 2018-10-17 22:44:43
[1] => 16090
)
MYAPP_URL=http://localhost
$aaa = env(MYAPP_URL);
設定がない場合にデフォルト値をセットすることもできます
$aaa = env(MYAPP_URL, 'default_value');
例: config/myfile.php に値を保存します
<?php
return [
'data1' => "設定情報です" ,
];
KEY, VALUE 形式だけでなく 配列形式でも保存できます。
次の2つのうちどちらでも取り出すことが出来ます。
config('myfile.data1');
\Config::get('myfile.data1');
configファイルから値がとりだせない / 取り出した値が NULL になる時は次のコマンドを実行します
php artisan config:clear;
フレームワークによってはコントローラーを直接CLIから叩けるものもありますが、 Laravelの場合はコマンドを用意する必要があるようです。 (とはいえ artisan コマンドが用意されているので簡単です。)
コマンド名は
make:auth
make:migration
のように(makeグループの中のauth)みたいな感じで、グルーピングされていますので、
自作コマンドもグルーピングしておくと、コマンドが増えた時に一覧表示で見やすくなります。
今回作成するコマンドは
myapp:print
とします。
myapp:print コマンドを作成する時には次のように入力します
php artisan make:command MyappPrint
するとファイル app/Console/Commands/MyappPrint.php が自動生成されます。
app/Console/Commands/MyappPrint.php を以下のようにします
protected $signature = 'myapp:print'; にコマンド名をセットします。
protected $description = 'MYAPP:にコマンドの説明をセットします。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class MyappPrint extends Command
{
/**
* The name and signature of the console command.
* @var string
*/
protected $signature = 'myapp:print';
/**
* The console command description.
* @var string
*/
protected $description = 'MYAPP: ハローと表示します';
/**
* Create a new command instance.
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
* @return mixed
*/
public function handle()
{
// ここにロジックを記述
echo 'Hello Myapp Command !!!' . "\n";
}
}
php artisan myapp:print
結果
Hello Myapp Command !!!
protected $signature = 'myapp:print';
↓
protected $signature = 'myapp:print {id}';
と変更します。
「引数なし」を許可するには
protected $signature = 'myapp:print {id?}';
とします。
public function handle()
{
// ここにロジックを記述
$id = $this->argument('id');
echo 'Hello Myapp Command !!!' . "\n";
echo $id;
}
ターミナルから実行時に引数を渡します
php artisan myapp:print
Laravel で定期実行させるにはまず cron で毎分ごとにLaravelのスケジューラを実行させ、その中でLaravelで設定した時間ごとにコマンドを自動実行します。
crontab -e
crontabの書式に沿って以下のように記述します
* * * * * php7 /YOUR/APP/PATH/artisan schedule:run >> /dev/null 2>&1
コマンド「myapp:getdata」を1分ごとに起動するように設定します。
protected function schedule(Schedule $schedule)
{
// RSSからデータを取得 (1分ごと)
$schedule->command('myapp:getdata')->everyMinute();
}
以上です。 ログを見て定期実行が行われているか確認します。
スケジュールは以下のように設定できます。
メソッド | 説明 |
---|---|
->cron('* * * * * *'); |
CRON記法によるスケジュール |
->everyMinute(); |
毎分タスク実行 |
->everyFiveMinutes(); |
5分毎にタスク実行 |
->everyTenMinutes(); |
10分毎にタスク実行 |
->everyFifteenMinutes(); |
15分毎にタスク実行 |
->everyThirtyMinutes(); |
30分毎にタスク実行 |
->hourly(); |
毎時タスク実行 |
->hourlyAt(17); |
一時間ごと、毎時17分にタスク実行 |
->daily(); |
毎日深夜12時に実行 |
->dailyAt('13:00'); |
毎日13:00に実行 |
->twiceDaily(1, 13); |
毎日1:00と13:00時に実行 |
->weekly(); |
毎週実行 |
->monthly(); |
毎月実行 |
->monthlyOn(4, '15:00'); |
毎月4日の15:00に実行 |
->quarterly(); |
四半期ごとに実行 |
->yearly(); |
毎年実行 |
->timezone('America/New_York'); |
タイムゾーン設定 |
cronでコマンドが呼ばれているかを確認するには
php artisan schedule:run
としますが、あらかじめ メソッドの修正が必要です!(重要)
1分に変更しないと、No scheduled commands are ready to run. が出て終了してしまいます。
また、重複起動を回避するメソッド withoutOverlapping() も外しておきましょう。
protected function schedule(Schedule $schedule)
{
$schedule->command('myapp:getdata')->everyMinute();
php artisan schedule:run
とします。
これで必ず設定されたタスクが走るので、ログを見て動作を確認します。
どうやら withoutOverlapping() はデッドロックを自動で解除できないようです。
app\Providers\AppServiceProvider.php
public function boot()
{
// ↓ この行を追加
\Illuminate\Support\Facades\Schema::defaultStringLength(191);
}
function __construct(){
// コンストラクタ内で Auth を使う
$this->middleware(function ($request, $next) {
$user = Auth::user();
// .....
return $next($request);
});
}
use Illuminate\Support\Facades\Redirect;
Redirect::route('myerror')->withErrors(['redirect'=>'エラー発生'])->throwResponse();
Laravelのモデルのスコープとは簡単にいうと SQL文の「 WHERE aaa = 'bbb'」みたいなWHERE句 なのですが、
・「User」モデルの中の管理者フラグを持つものだけを取り扱う
・「User」モデルが既にあるときに、現在削除されてないユーザー( WHERE is_deleted = 0 )を「Activeuser」モデルとして作成する。
みたいなところで使います。
その時に使うのが モデルの「スコープ」です。
好きな時につけたり外したりできる「ローカルスコープ」と
基本的につけっ放しの「グローバルスコープ」があります。
例:自分(ログインしているユーザー)と同じチームIDを持つという条件を
( Inmyteam )というグローバルスコープとして作成して、モデルに適用します。
/app/Scopes/Inmyteam.php
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class Inmyteam implements Scope
{
/**
* 自分のチーム内のユーザーに限定するスコープ
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->where('team_id', '=', Auth::user()->team_id);
}
}
app/User.php のメソッドに下記の boot() メソッドを追加する
use App\Scopes\Inmyteam;
protected static function boot()
{
parent::boot();
// スコープ「Inmyteam」を適用
static::addGlobalScope(new Inmyteam);
}
以上です。簡単ですね。
なおグローバルスコープに変数は渡せないようなので、ダイナミックに変わる条件(動的なパラメーター)を指定したい場合はセッションなど別のルートから取って来ましょう。
取れない場合はローカルスコープで設定します。
モデルファイル内に記述します。 利点は初めてLaravelを触る方にも見落としがない、という点と記述が楽な点です。
app/User.php のメソッドに下記の boot() メソッドを追加し、その中に直接記述する
// /**
// * boot method
// * @return void
// */
protected static function boot()
{
parent::boot();
// GlobalScope
static::addGlobalScope('report_start', function(\Illuminate\Database\Eloquent\Builder $builder){
$builder->where('team_id', '=', Auth::user()->team_id);
});
}
モデルファイルの中に直接書けばOK。 ローカルスコープはメソッドに引数を渡せます。
例) あるショップ内の商品に限定するスコープを作成する
app/Items.php
/**
* ローカルスコープ : ->inShop(<id>) で あるショップ内の商品に限定する
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeInShop($query, $shop_id)
{
return $query->where('shop_id', '=', $shop_id);
}
shop_id が 99 な商品を全件取得
Item::inShop(99)->all()
実は
Item::inShop(99)
Item::inshop(99)
どちらも使うことができますが、同じようにキャメルケースで使用することをお勧めします。
LaravelでEloquentを使ってリレーションを設定し、リレーション先のテーブルを取得する方法です。
次のような2つのテーブルがあるとします
・「チーム」(teams)
・「ユーザー」(users)
このようにチームの中に複数のユーザーが所属するとします
─── マイチーム
├── 鈴木 一郎
├── 山田 太郎
├── 中村 二郎
モデル /app/Team.php
/**
* 1対多リレーション
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function users() // 複数形(users)にする
{
// 「is_deleted = 0」のデータを「idの大きい順」で取得する
return $this->hasMany('App\User')->where('is_deleted', 0)->orderBy('id', 'DESC');
}
(取得条件やソート順を指定することができます。)
モデル /app/User.php
public function team() // 単数形(team)にする
{
return $this->belongsTo('App\Team');
}
use App\Team;
$all_teams = Team::with('users')->get();
dd( $all_teams->toArray() );
(実は with() メソッドを指定しなくても、Bladeテンプレートの中で リレーションオブジェクトを呼び出そうとすると自動取得されます。 ただ、自動取得が100回あると100回SQLクエリが投げられるので非効率です。with()メソッドだと in句 で一撃で取得してきます。)
方法1: with() を使ったやり方
$user = User::with('team')->find( Auth::user()->id );
dd( $user->toArray() );
実行されるSQL文
select * from `users`";
select * from `teams` where `teams`.`id` in (?); // ? は プレースホルダ
方法2: LEFT JOIN を使ったやり方
$user = User::leftJoin('teams','teams.id','=','users.team_id')->find( Auth::user()->id );
Mydump::dump( $user->toArray() );
実行されるSQL文
select * from `users` left join `teams` on `teams`.`id` = `users`.`team_id` where `users`.`id` = ? limit 1
$all_users = DB::table('users')
->leftJoin('teams','teams.id','=','users.team_id')
->get();
Mydump::dump( $all_users );
その他リレーションの参考: https://laravel-news.com/eloquent-tips-tricks
マイグレーションをやり直した時に、DBデータも自動で登録できるようにシーダーを使ってデータを作成しておくと マイグレーションのやり直しがとても楽にできます。
テーブル名「clients」の場合「ClientsSeeder」や「ClientsTableSeeder」といった名前をつけて作成します。
(例: clients テーブル用のシーダーファイルを作成する )
php artisan make:seeder ClientsSeeder
( database/seeds/ClientsSeeder.php が作成されます )
database/seeds/ClientsSeeder.php
<?php
use Illuminate\Database\Seeder;
class ClientsSeeder extends Seeder
{
/**
* Run the database seeds.
* @return void
*/
public function run()
{
DB::table("clients")->insert([
'id' => 1 ,
'client_name' => 'テスト商事' ,
'tel_name' => '012-345-6789' ,
'fax_name' => '012-345-6780',
]);
DB::table("clients")->insert([
'id' => 2 ,
'client_name' => 'てすとの商事' ,
'tel_name' => '112-345-6789' ,
'fax_name' => '112-345-6780',
]);
}
}
database/seeds/DatabaseSeeder.php に作成したシーダーファイルを記述して呼び出します
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
* @return void
*/
public function run()
{
$this->call([
ClientsSeeder::class , // 追加
]);
}
}
php artisan db:seed
composer の autoload を再読み込みしてから実行するとうまく実行できます。
composer dump-autoload
php artisan db:seed
composer require diglactic/laravel-breadcrumbs
古いLaravelにインストールする場合はこちら
composer require davejamesmiller/laravel-breadcrumbs:5.x
routes/breadcrumbs.php を以下の内容で作成します
<?php
// Home
Breadcrumbs::for('home', function ($trail) {
$trail->push('Home', route('home'));
});
// Home > About
Breadcrumbs::for('about', function ($trail) {
$trail->parent('home');
$trail->push('About', route('about'));
});
次の設定ファイルを生成コマンドを実行します
php artisan vendor:publish --provider="DaveJamesMiller\Breadcrumbs\BreadcrumbsServiceProvider"
ビューファイル(XXXXX.blade.php)に以下のように記述します
{{ Breadcrumbs::render('home') }}
もしくは、Route::currentRouteName() で現在のルーティングが取得できるので
{{ Breadcrumbs::render(Route::currentRouteName()) }}
としておくとビューファイルは触らなくてもいいので楽です。(レイアウトファイルにパンくずを設置してある場合はこちらがおすすめです。)
自作のパンくずリストテンプレートを使用するには config/breadcrumbs.php を編集します
25行目を次のように書き換えます
### 'view' => 'breadcrumbs::bootstrap4',
'view' => 'my_breadcrumbs',
すると、テンプレートファイルviews/breadcrumbs.blade.phpを見に行くようになります。
テンプレートファイルを用意する
views/breadcrumbs.blade.php
(以下は例です。適宜書き換えてください。)
@if (count($breadcrumbs))
<ol class="navbar__breadcrumb breadcrumb d-none d-sm-flex">
@foreach ($breadcrumbs as $breadcrumb)
@if ($breadcrumb->url && !$loop->last)
<li class="breadcrumb-item"><a href="{{ $breadcrumb->url }}">{{ $breadcrumb->title }}</a></li>
@else
<li class="breadcrumb-item active">{{ $breadcrumb->title }}</li>
@endif
@endforeach
</ol>
@endif
以上です。 とても簡単にパンくずリストが出来るのでLaravelを使っている場合は必須ともいえるでしょう。
これも便利です。blade.php ファイルにページタイトルを記述しなくても自動的にセットされます。
<title>{{ ($breadcrumb = Breadcrumbs::current()) ? $breadcrumb->title : 'No Name' }}</title>
Laravelで「ファイルを更新したのに更新されてない?」
という時はキャッシュが残っている可能性が大です。
こちらのコマンドでキャッシュを削除しましょう。
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
composer dump-autoload
php artisan cache:clear; php artisan config:clear; php artisan route:clear; php artisan view:clear; composer dump-autoload
PackageCreator がとても便利です
https://github.com/SUKOHI/PackageCreator
composer require sukohi/package-creator
インストールすると「make:package コマンド」が使用できるようになります
php artisan make:package (ベンダー名) (パッケージ名) (保存するフォルダ名(アプリケーションからの相対パス) )
php artisan make:package myname my-package test_packages
./test_packages/Myname/MyPackage/src/Facades/MyPackage.php
./test_packages/Myname/MyPackage/src/MyPackage.php
./test_packages/Myname/MyPackage/src/MyPackageServiceProvider.php
./test_packages/Myname/MyPackage/composer.json
例として mydump クラスを作成してみます。
php artisan make:package myname mydump mypackages
APP/mypackages/MyName/Mydump/src/Mydump.php を編集して以下の内容にします
<?php
namespace MyName\Mydump;
class Mydump {
public static function dump( $mix )
{
print "\n".'<pre style="text-align:left;display:block;padding:9.5px;margin:10px;font-size:13px;line-height:1.4;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px;">'."\n";
print '<span style="color:#999;">TYPE: ' . gettype($mix) . "</span>\n";
print_r($mix);
print "\n</pre>\n\n";
}
public static function dump2( $mix )
{
print "\n"."<!--"."\n";
print "\n".'<pre style="text-align:left;display:block;padding:9.5px;margin:10px;font-size:13px;line-height:1.4;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px;">'."\n";
print '<span style="color:#999;">TYPE: ' . gettype($mix) . "</span>\n";
print_r($mix);
print "\n</pre>\n\n";
print "\n"."-->"."\n";
}
}
APP/config/app.php の「providers」と「aliases」に以下を追加します
'providers' => [
.....................
MyName\Mydump\MydumpServiceProvider::class,
'aliases' => [
.....................
'Mydump' => MyName\Mydump\Facades\Mydump::class,
composer.json を以下のように修正します
"psr-4": {
"App\\": "app/"
}
↓
"psr-4": {
"App\\": "app/" ,
"MyName\\Mydump\\": "mypackages/MyName/Mydump/src"
}
composer dumpautoload -o
php artisan config:cache
use Mydump;
$mix = [
'aaa' => 'bbb' ,
'ccc' => 'ddd' ,
];
Mydump::dump( $mix );
複数データを渡すときは連想配列で渡します
$data = [
"data1" => $data1 ,
"data2" => $data2 ,
];
return view('mytemplate')->with($data);
またはwithメソッドを2回呼んでもOKです。
return view('mytemplate')->with('data1',$data1)->with('data2',$data2);
すっきりと書きたい場合は compact を使って以下のように書くことができます
return view('mytemplate',compact('data1', 'data2'));
コントローラーから次のように渡します。
$pagination_params = [
"aaa" => 1 ,
"bbb" => 2 ,
];
return view('mytemplate', compact('pagination_params'));
ビューでは次のようにして呼び出します
mytemplate.blade.php
{{ $model->appends($pagination_params)->links() }}
次のようなリンクがページネーションにつきます
( 3ページ目へのリンクにつく文字列 )
?&aaa=1&bbb=2&page=3
foreach でオブジェクトを回す
<ul>
@foreach($data_obj as $v)
<li><a href="#">{{$v->data_name}}</a></li>
@endforeach
</ul>
foreach で配列を回す
<ul>
@foreach($data_loop as $v)
<li><a href="#">{{$v['data_name']}}</a></li>
@endforeach
</ul>
<a href="{{ route('tweets.index') }}">Tweets</a>
またはこのようにも書けます
<a href="{{ url("/tweets") }}">Tweets</a>
Eloquent モデルとクエリビルダに対して paginate() メソッドを 実行することができます。
Eloquent モデルに対してpaginate()を実行する
$users = User::where('votes', '>', 100)->paginate(15);
return view('user.index', ['users' => $users]);
クエリビルダーに対してpaginate()を実行する
$users = DB::table('users')->paginate(15);
return view('user.index', ['users' => $users]);
paginate() メソッドのパラメーターは次のようになっています。
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);
例:
$articles = Articles::paginate(5, ['*'], 'page', $pageNumber);
Laravelの 結果セットに対してforPage() メソッドを使用してpaginationを作成することもできますが、それよりも次の方法をお勧めします。
複数のテーブルから検索して結合した後にページネーションを付けたいという場合にとても有効です。
メソッド名が変わるので注意してください。
app/Providers/AppServiceProvider.php の boot()メソッド内に以下のコードを追加します
public function boot()
{
/**
* Collectionに対して paginate できるようにするマクロ
*
* @param int $perPage
* @param int $total
* @param int $page
* @param string $pageName
* @return array
*/
Collection::macro('paginate', function($perPage, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
return new LengthAwarePaginator(
$this->forPage($page, $perPage),
$total ?: $this->count(),
$perPage,
$page,
[
'path' => LengthAwarePaginator::resolveCurrentPath(),
'pageName' => $pageName,
]
);
});
}
これで普通に paginate() メソッドを使用することができます。
// forPageを使用する方法(あまりお勧めしません。 total() などのメソッドはこの方法では使用できません)
// 1つ目の引数 : ページ番号
// 2つ目の引数 : 1ページあたりのアイテム数
$collection = collect([1,2,3,4,5,6,7,8,9,0]);
$items = $collection->forPage($_GET['page'], 5);
$articles_collection = $articles_paginator->getCollection();
Bladeテンプレート内で次のように記述します
{!! $tweets->render() !!}
links() というエイリアスもあります。(機能は同じ)
{{ $model->links() }}
検索結果ページでは「検索文字列」「1ページあたりの表示数」などのパラメーターを引き継ぐ必要があります。
その時は次のように appends() メソッドに連想配列をセットしてを呼びます。
bladeファイル
{!! $datas->render() !!}
↓
{!! $datas->appends(['q' => $q['q']])->render() !!}
現在のページに表示されている件数: {{ $data->count() }}
現在のページ数: {{ $data->currentPage() }}
現在のページの最初の要素: {{ $data->firstItem() }}
次のページがあるかどうか: {{ $data->hasMorePages() }}
現在のページの最後の要素: {{ $data->lastItem() }}
最後のページ数: {{ $data->lastPage() }}
次のページのURL: {{ $data->nextPageUrl() }}
1ページに表示する件数: {{ $data->perPage() }}
前のページのURL: {{ $data->previousPageUrl() }}
合計件数: {{ $data->total() }}
指定ページのURL: {{ $data->url(4) }}
ページネーションをカスタマイズして「First Page」「Last Page」のリンクを追加してみましょう。
ページネーションの生成方法を次のように links() メソッドに書き換えます
{!! $tweets->render() !!}
↓
{!! $tweets->links('pagination.default') !!}
resources/views/pagination/default.blade.php を以下の内容で新規作成します
@if ($paginator->lastPage() > 1)
<ul class="pagination">
<li class="page-item {{ ($paginator->currentPage() == 1) ? ' disabled' : '' }}">
<a class="page-link" href="{{ $paginator->url(1) }}">First Page</a>
</li>
<li class="page-item {{ ($paginator->currentPage() == 1) ? ' disabled' : '' }}">
<a class="page-link" href="{{ $paginator->url(1) }}">
<span aria-hidden="true">«</span>
{{-- Previous --}}
</a>
</li>
@for ($i = 1; $i <= $paginator->lastPage(); $i++)
<li class="page-item {{ ($paginator->currentPage() == $i) ? ' active' : '' }}">
<a class="page-link" href="{{ $paginator->url($i) }}">{{ $i }}</a>
</li>
@endfor
<li class="page-item {{ ($paginator->currentPage() == $paginator->lastPage()) ? ' disabled' : '' }}">
<a class="page-link" href="{{ $paginator->url($paginator->currentPage()+1) }}" >
<span aria-hidden="true">»</span>
{{-- Next --}}
</a>
</li>
<li class="page-item {{ ($paginator->currentPage() == $paginator->lastPage()) ? ' disabled' : '' }}">
<a class="page-link" href="{{ $paginator->url($paginator->lastPage()) }}">Last Page</a>
</li>
</ul>
@endif
↓
Laravel の ページャーはデフォルトでは 1ページしかない時は表示されません
それではデザイン的にさみしい時は次のようにして表示させます。
{{-- paginate --}}
{!! $data_loop->links('pagination.front') !!}
{{-- / paginate --}}
↓ このように変更します
{{-- paginate --}}
@if ( $data_loop->hasPages() )
{!! $data_loop->links('pagination.front') !!}
@else
<div class="g_pager">
<a class="prev"></a>
<a class="current" href="">1</a>
<a class="next"></a>
</div>
@endif
{{-- / paginate --}}
use Illuminate\Support\Facades\Schema;
category テーブルのカラムを配列で取得する
$columns = Schema::getColumnListing('category');
dump($columns);
$data = $model->orderBy('id', 'desc')->paginate( 10 );
dump( $model->toSql() );
\DB::enableQueryLog();
$data = $model->orderBy('id', 'desc')->paginate( 10 );
dump(\DB::getQueryLog());
$query = \DB::table('users')
->where('status', '<>', 1);
var_dump($query->toSql(), $query->getBindings());