- 投稿日:2020-11-24T22:16:11+09:00
LaravelにVurtifyを追加したらChromeでDevTools failed to load SourceMap
- 投稿日:2020-11-24T22:08:32+09:00
GitHub Actionsにphp-security-auditを組み込んで、プルリク時にレビューコメントとして出力してみた
こんにちは!
GitHubのプルリク時に該当のphpコードに含まれる脆弱性を検知し、コメントとして出力してほしいという要望がありました。
なので、GitHub Actionsにphpcs-security-auditとreviewdogを組み込むことにしました。
phpcs-security-auditを導入している事例や導入方法が日本語のドキュメントで少なかったので、もし、導入してみたい方とかは参考にしてみてください。
phpcs-security-auditとは?
↓公式ドキュメントになります。
https://github.com/FloeDesignTechnologies/phpcs-security-auditphpcs-security-audit is a set of PHP_CodeSniffer rules that finds vulnerabilities and weaknesses related to security in PHP code.
phpのコードに含まれている脆弱性を発見するためのphpcsのルールになります。
前提条件
Requires PHP CodeSniffer version 3.1.0 or higher with PHP 5.4 or higher.
- PHP CodeSnifferのバージョンが3.1.0以上がインストールされていること
- PHP のバージョンが5.3以上がインストールされていること
導入方法
ローカルで実行する方法
composer require --dev pheromone/phpcs-security-auditまず、開発環境にphpcs-security-auditのルールをパッケージを追加します。
cd vendor/pheromone/phpcs-security-audit/次は
vendor/pheromone/phpcs-security-audit/
配下に移動します。../../squizlabs/php_codesniffer/bin/phpcs --standard=example_base_ruleset.xml tests.php移動したら、
../../squizlabs/php_codesniffer/bin/phpcs
でphpcsコマンドを実行し、--standard=example_base_ruleset.xml
でxmlルールセットを設定します。test.phpというphpの例ファイルがあるので、それを実行することで、どんな表示が出力されるかを確認することができます。example_base_ruleset.xml<?xml version="1.0"?> <ruleset name="Drupal7"> <description>Rules for standard PHP projects</description> <!-- Code Reviews Rules --> <!-- <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/> <rule ref="PEAR"/> --> <!-- Security Code Reviews Rules --> <!-- Global properties --> <!-- Please note that not every sniff uses them and they can be overwritten by rule --> <!-- Paranoya mode: Will generate more alerts but will miss less vulnerabilites. Good for assisting manual code review. --> <config name="ParanoiaMode" value="1"/> <!-- BadFunctions --> <!-- PHP functions that can lead to security issues --> <rule ref="Security.BadFunctions.Asserts"/> <rule ref="Security.BadFunctions.Backticks"/> <rule ref="Security.BadFunctions.CallbackFunctions"/> <rule ref="Security.BadFunctions.CryptoFunctions"/> <rule ref="Security.BadFunctions.EasyRFI"/> <rule ref="Security.BadFunctions.EasyXSS"> <properties> <!-- Comment out to follow global ParanoiaMode --> <property name="forceParanoia" value="1"/> </properties> </rule> <rule ref="Security.BadFunctions.ErrorHandling"/> <rule ref="Security.BadFunctions.FilesystemFunctions"/> <rule ref="Security.BadFunctions.FringeFunctions"/> <rule ref="Security.BadFunctions.FunctionHandlingFunctions"/> <rule ref="Security.BadFunctions.Mysqli"/> <rule ref="Security.BadFunctions.NoEvals"/> <rule ref="Security.BadFunctions.Phpinfos"/> <rule ref="Security.BadFunctions.PregReplace"/> <rule ref="Security.BadFunctions.SQLFunctions"/> <rule ref="Security.BadFunctions.SystemExecFunctions"/> <!-- CVE --> <!-- Entries from CVE database from vendor PHP and bugs.php.net --> <rule ref="Security.CVE.20132110"/> <rule ref="Security.CVE.20134113"/> <!-- Misc --> <rule ref="Security.Misc.BadCorsHeader"/> <rule ref="Security.Misc.IncludeMismatch"/> </ruleset>しかし、このままでは上手く実行することができません。
実行したら、下記エラーが出てしまいます。ERROR: Referenced sniff "Security.BadFunctions.Asserts" does not exist Run "phpcs --help" for usage information
Security.BadFunctions.Asserts
が存在しませんといわれてしまします。そもそも、xmlルールセットに記述されているファイルでは、いきなりSecurity.BadFunctions.Asserts
を見に行っているので、上手くいきません。なので、上から21行目に
<rule ref="Security">
、下から2行目に</rule>
を追記し、Securityルールで囲ってあげれば、ルールセットがうまく実行できるようになります。example_base_ruleset.xml<?xml version="1.0"?> <ruleset name="Drupal7"> <description>Rules for standard PHP projects</description> <!-- Code Reviews Rules --> <!-- <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/> <rule ref="PEAR"/> --> <!-- Security Code Reviews Rules --> <!-- Global properties --> <!-- Please note that not every sniff uses them and they can be overwritten by rule --> <!-- Paranoya mode: Will generate more alerts but will miss less vulnerabilites. Good for assisting manual code review. --> <config name="ParanoiaMode" value="1"/> <!-- BadFunctions --> <!-- PHP functions that can lead to security issues --> <!-- ↓のrule refを追記 --> <rule ref="Security"> <rule ref="Security.BadFunctions.Asserts"/> <rule ref="Security.BadFunctions.Backticks"/> <rule ref="Security.BadFunctions.CallbackFunctions"/> <rule ref="Security.BadFunctions.CryptoFunctions"/> <rule ref="Security.BadFunctions.EasyRFI"/> <rule ref="Security.BadFunctions.EasyXSS"> <properties> <!-- Comment out to follow global ParanoiaMode --> <property name="forceParanoia" value="1"/> </properties> </rule> <rule ref="Security.BadFunctions.ErrorHandling"/> <rule ref="Security.BadFunctions.FilesystemFunctions"/> <rule ref="Security.BadFunctions.FringeFunctions"/> <rule ref="Security.BadFunctions.FunctionHandlingFunctions"/> <rule ref="Security.BadFunctions.Mysqli"/> <rule ref="Security.BadFunctions.NoEvals"/> <rule ref="Security.BadFunctions.Phpinfos"/> <rule ref="Security.BadFunctions.PregReplace"/> <rule ref="Security.BadFunctions.SQLFunctions"/> <rule ref="Security.BadFunctions.SystemExecFunctions"/> <!-- CVE --> <!-- Entries from CVE database from vendor PHP and bugs.php.net --> <rule ref="Security.CVE.20132110"/> <rule ref="Security.CVE.20134113"/> <!-- Misc --> <rule ref="Security.Misc.BadCorsHeader"/> <rule ref="Security.Misc.IncludeMismatch"/> <!-- ↓の/ruleを追記 --> </rule> </ruleset>上手く実行できると、コードごとにどんな脆弱性が含んでいるかを下記のように表示してくれます。
FILE: /var/www/html/vendor/pheromone/phpcs-security-audit/tests.php ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ FOUND 22 ERRORS AND 75 WARNINGS AFFECTING 60 LINES ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 6 | WARNING | Possible XSS detected with . on echo 6 | WARNING | User input detetected with $_POST. 6 | ERROR | Easy XSS detected because of direct user input with $_POST on echo 8 | WARNING | db_query() is deprecated except when doing a static query 8 | WARNING | db_query() is deprecated except when doing a static query 8 | WARNING | User input detetected with $_GET. 8 | ERROR | Potential SQL injection found in db_query() 8 | ERROR | Potential SQL injection found in db_query() 9 | WARNING | Usage of preg_replace with /e modifier is not recommended. 10 | WARNING | Usage of preg_replace with /e modifier is not recommended. 10 | ERROR | User input and /e modifier found in preg_replace, remote code execution possible. 10 | WARNING | User input detetected with $_GET. 11 | ERROR | User input found in preg_replace, /e modifier could be used for malicious intent. 11 | WARNING | User input detetected with $_GET. 11 | WARNING | User input detetected with $_GET. 11 | WARNING | User input detetected with $_GET. 12 | WARNING | Dynamic usage of preg_replace, please check manually for /e modifier or user input. 12 | WARNING | User input detetected with $_GET. 13 | WARNING | Weird usage of preg_replace, please check manually for /e modifier. 13 | WARNING | User input detetected with $_GET. 17 | WARNING | Crypto function md5 used. 18 | WARNING | phpinfo() function detected 19 | WARNING | Function handling function create_function() detected with dynamic parameter 20 | WARNING | Unusual function ftp_exec() detected 21 | WARNING | Filesystem function fread() detected with dynamic parameter 22 | WARNING | Function array_map() that supports callback detected 23 | WARNING | System execution with backticks detected with dynamic parameter 24 | ERROR | System execution with backticks detected with dynamic parameter directly from user input 24 | WARNING | User input detetected with $_GET. 25 | ERROR | No file extension has been found in a include/require function. This implies that some PHP code is not scanned by PHPCS. 25 | WARNING | Possible RFI detected with $a on include 26 | WARNING | Assert eval function assert() detected with dynamic parameter 27 | ERROR | Assert eval function assert() detected with dynamic parameter directly from user input 27 | WARNING | User input detetected with $_GET. 28 | WARNING | System program execution function exec() detected with dynamic parameter 29 | ERROR | System program execution function exec() detected with dynamic parameter directly from user input 29 | WARNING | User input detetected with $_GET. 30 | WARNING | SQL function mysql_query() detected with dynamic parameter 31 | ERROR | SQL function mysql_query() detected with dynamic parameter directly from user input 31 | WARNING | User input detetected with $_GET. 35 | WARNING | Crypto function mcrypt_encrypt used. 36 | ERROR | Bad use of openssl_public_encrypt without OPENSSL_PKCS1_OAEP_PADDING 39 | WARNING | CVE-2013-4113 ext/xml/xml.c in PHP before 5.3.27 does not properly consider parsing depth, which allows remote attackers to cause a denial of service (heap | | memory corruption) or possibly have unspecified other impact via a crafted document that is processed by the xml_parse_into_struct function. 40 | WARNING | CVE-2013-2110 Heap-based buffer overflow in the php_quot_print_encode function in ext/standard/quot_print.c in PHP before 5.3.26 and 5.4.x before 5.4.16 | | allows remote attackers to cause a denial of service (application crash) or possibly have unspecified other impact via a crafted argument to the | | quoted_printable_encode function. 43 | WARNING | Bad CORS header detected. 44 | ERROR | The file extension '.xyz' that is not specified by --extensions has been used in a include/require function. Please add it to the scan process. 47 | WARNING | User input detetected with $_GET. 48 | WARNING | Possible XSS detected with . on print 48 | WARNING | User input detetected with $_GET. 48 | ERROR | Easy XSS detected because of direct user input with $_GET on print 49 | WARNING | User input detetected with $_GET. 49 | ERROR | Easy XSS detected because of direct user input with $_GET on echo 50 | WARNING | User input detetected with $_GET. 50 | ERROR | Easy XSS detected because of direct user input with $_GET on echo 51 | WARNING | Possible XSS detected with "{$_GET['a']}" on echo 52 | WARNING | Possible XSS detected with "${_GET['a']}" on print 53 | WARNING | Possible XSS detected with a on echo 53 | WARNING | User input detetected with $_GET. 53 | ERROR | Easy XSS detected because of direct user input with $_GET on echo 54 | WARNING | Possible XSS detected with allo on echo 54 | WARNING | User input detetected with $_GET. 54 | ERROR | Easy XSS detected because of direct user input with $_GET on echo 55 | WARNING | Possible XSS detected with arg on echo 55 | WARNING | User input detetected with arg. 56 | WARNING | Possible XSS detected with . on die 56 | WARNING | User input detetected with $_GET. 56 | ERROR | Easy XSS detected because of direct user input with $_GET on die 57 | WARNING | Possible XSS detected with . on exit 57 | WARNING | User input detetected with $_GET. 57 | ERROR | Easy XSS detected because of direct user input with $_GET on exit 59 | WARNING | User input detetected with $_GET. 59 | ERROR | Easy XSS detected because of direct user input with $_GET on <?= 63 | WARNING | User input detetected with arg. 64 | WARNING | Allowing symlink() while open_basedir is used is actually a security risk. Disabled by default in Suhosin >= 0.9.6 64 | WARNING | Filesystem function symlink() detected with dynamic parameter 65 | WARNING | Filesystem function delete() detected with dynamic parameter 69 | WARNING | Potential SQL injection with direct variable usage in join with param #3 70 | WARNING | Potential SQL injection with direct variable usage in innerJoin with param #3 71 | WARNING | Potential SQL injection with direct variable usage in leftJoin with param #3 72 | WARNING | Potential SQL injection with direct variable usage in rightJoin with param #3 73 | WARNING | Potential SQL injection with direct variable usage in addExpression with param #1 74 | WARNING | Potential SQL injection with direct variable usage in groupBy with param #1 76 | WARNING | Potential SQL injection with direct variable usage in orderBy with param #1 76 | WARNING | Potential SQL injection with direct variable usage in orderBy with param #2 81 | WARNING | User input detetected with $_GET. 81 | ERROR | SQL injection found in condition with param #3 83 | WARNING | Potential SQL injection with direct variable usage in where with param #1 84 | WARNING | Potential SQL injection with direct variable usage in havingCondition with param #3 85 | WARNING | Potential SQL injection with direct variable usage in having with param #1 88 | WARNING | Possible XSS detected with $count on echo 91 | WARNING | Potential SQL injection with direct variable usage in expression with param #1 91 | WARNING | Potential SQL injection with direct variable usage in expression with param #2 96 | WARNING | Potential SQL injection with direct variable usage in fields with param #1 with array key value 97 | WARNING | Potential SQL injection with direct variable usage in fields with param #1 with array key value 106 | WARNING | Dynamic query with db_select on table node should be tagged for access restrictions 108 | WARNING | User input detetected with $_GET. 108 | ERROR | SQL injection found in fields with param #1 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Time: 37ms; Memory: 8MBここまでで、ローカルで実行できることが確認できましたので、あとはGitHub Actionsに組み込んでいくだけです!
GitHub Actionsで実行する方法
今回はsampleのlaravelプロジェクトを作成し、それをGitHubのリポジトリとして作成しておきます。
作成しましたら、一番上の階層にtest.php
とexample_base_ruleset.xml
をコピーして、適当なブランチを作成し、pushしましょう。
※ローカルで実行した時、example_base_ruleset.xml
の<rule ref="Security">
でしたが、<rule ref="vendor/pheromone/phpcs-security-audit/Security">
に変更お願いします。
これをしないと、GitHub Actionsの実行時にSecurity
のルールを読み込んでくれません。cp vendor/pheromone/phpcs-security-audit/tests.php ./ cp vendor/pheromone/phpcs-security-audit/example_base_ruleset.xml ./ git checkout -b test git add tests.php example_base_ruleset.xml git commit -m "add two files" git push origin testこれでプルリク作成の準備ができました。
次に、workflowsにtest.ymlを作成します。test.yml# This is a basic workflow to help you get started with Actions name: CI # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: push: branches: [ master ] pull_request: branches: [ ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 # phpcs-security-auditのパッケージを追加 - name: install phpcs-security-audit run: | composer require --dev pheromone/phpcs-security-audit # reviewdog の setup action を追加 - uses: reviewdog/action-setup@v1 with: reviewdog_version: latest - name: phpcs-security-audit env: # reviewdog が コメントを書き込めるように token をセットする REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} # 構文チェックの結果を reviewdog へ渡す run: ./vendor/bin/phpcs --report=emacs --standard=example_base_ruleset.xml tests.php | reviewdog -reporter=github-pr-review -efm='%f:%l:%c:%m'リポジトリのSecretsにCI_GITHUB_TOKENというNameでリポジトリのフルアクセス権限を付与したPersonal access tokensをValueとして設定してください。
※これをしないと、reviewdogがプルリクにレビューコメントを出力することができません。先ほど、pushしたものをプルリクにだすと、上記のようにGitHub Actionsが実行されて、該当コードのところに犬のマークがついたreviewdogがどんな脆弱性があるかをコメントとして出力してくれます。
最後
私は普段、インフラ担当で、phpのlaravelに詳しくなかったので、今回のCIでの実装はいい勉強になりました。
もし、この記事を見ていただいて、表現がおかしいや誤った個所等ございましたら、コメントお願いします!最後まで読んでいただき、ありがとうございました!
- 投稿日:2020-11-24T18:53:47+09:00
Vue.js と Laravel バリデーションの連携
はじめに
jQuery を極めすぎて、とんと食わず嫌いになっていた Vue.js を本格導入するにあたって、フォームのバリデーションをサーバーサイドの Laravel で実装することだけは譲れなかった。
まして、サーバーサイドとフロントエンドの両方で同じ処理(バリデーション)を、しかも異なる言語(php と javascript)で記述するなど論外。
- Laravel v 6.0
- Vue.js v 2.6.12
- form-backend-validation v 2.4.0
パッケージの選択
Vuetify
- リアルタイムバリデーションのパッケージ。
- フォームエレメントを <v-form や <v-input のように記述し、Vue.js とは別の学習コストが必要。
- そもそも、リアルタイムバリデーションを javascript で定義する気はないので却下。
FormVuelar
- サーバーサイドバリデーションのパッケージ。
- Vuetify の <-v-form が <ivl-from に変わるもので学習コストがさらに増えるので却下。
form-backend-validation
- 同じくサーバーサイドバリデーションのパッケージ。
- Vue.js の data に与える from オブジェクトを拡張するだけなので扱いやすい。
- フォーム Blade が、通常の Laravel の記述に近い。
- ララジャパンの記事 がわかりやすい。
fom-backend-validation の導入
共通処理の切り分け
ララジャパンの記事 の最後にあるように、フォームを持つすべての Blade に Vue.js 初期化のインラインスクリプトを繰り返し記述することは避けたいので、共通処理を切り分けてみた。
resources/js/app.js
- Vue.js の初期化は各ページに任せるので app.js は最小限。
require('./bootstrap'); window.Vue = require('vue'); import Form from 'form-backend-validation'; window.Form = Form;各 Blade の記述
- フォームにはブラウザのバリデーションが発生しないよう
novalidate
を付与。
- インラインスクリプトで定義するのは、フォーム内のカラム要素
data.fields
だけ。
- ページ固有の処理のため
data
に定義を追加したり、watch
等を追加してもよい。@extends('layouts.app') @section('content) (中略) <div class="alert" :class="messageClass" v-if="message"> <a class="float-right close" v-on:click="clearMessage()">x</a> @{{ message }} </div> <form @submit.prevent="onSubmit" @keydown="form.errors.clear($event.target.name);" novalidate> <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">メールアドレス</label> <div class="col-md-6"> <input id="email" type="email" v-model="form.email" class="form-control" :class="{ 'is-invalid': form.errors.has('email') }"> <div class="invalid-feedback" v-if="form.errors.has('email')" v-text="form.errors.first('email')"></div> </div> </div> (中略) @endsection @section('script') import {formBackendValidation} from "{{ mix('/js/libs.js') }}"; formBackendValidation({ data: { fields: { email: "{{ $user->email }}", password: "", name: "{{ $user->name }}", is_active: {{ $user->is_active }}, } } }, "{{ route('admin.user.update', $user) }}"); @endsectionresources/js/libs.js
- 各ページから呼び出される共通処理。
- ページ側で Vue.js に渡す定義が追加できるように、ディープマージしている。
- ページ側で
form
を直接定義せずにfields
で渡してるのは、将来の拡張の可能性のため(後段参照)。/** * Vue.js with form-backend-validation の初期定義 * * @param {object} option data.fields を含む Vue へ渡すオプション * @param {string} url フォームのポスト先URL */ export function formBackendValidation(option, url) { const app = new Vue(merge({ el: '#app', data: { form: new Form(option.data.fields), message: '', messageClass: '' }, methods: { onSubmit() { this.form['post'](url) .then(res => { if (res.redirect) { location.href = res.redirect } else if (res.message) { this.displayMessage(message, true); } else { this.displayMessage('更新しました。', true); } }) .catch(res => this.displayMessage('エラーを確認してください。', false)); }, displayMessage(message, success) { this.messageClass = 'alert-' + (success ? 'success' : 'danger') this.message = message; }, clearMessage() { this.message = ''; }, }, }, option)); return app; } /** * deep merge */ function merge() { return [].reduce.call(arguments, function merge(a, b) { Object.keys(b).forEach(function (key) { a[key] = (typeof a[key] === "object" && typeof b[key] === "object") ? a[key] = merge(a[key], b[key]) : a[key] = b[key]; }); return a; }); }webpack.mix.js
- libs.js を mix.js() で処理すると webpack 変換されて import できなかったので、app.js とは処理を分けた。
mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css'); // モジュールで呼び出される js は webpack変換されないよう .scripts を使う mix.scripts(['resources/js/libs.js'], 'public/js/libs.js'); mix.version();resources/views/layouts/app.blade.php
- CSRFトークン、app.js の組み込みは Laravel 初期通りに必須。
- Balde で定義したインラインスクリプトの
@yield
ディレクティブをtype="module"
で追加。- コンテンツコンテナに
id="app"
。<!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Scripts --> <script src="{{ mix('js/app.js') }}" defer></script> <script type="module"> @yield('script') </script> (中略) </head> <body> <div id="app"> (中略) <main class="py-4"> @yield('content') </main> </div> </body> <body>コントローラ
バリデーションが通りDB更新後は、libs.js の
then
メソッドで返り値を受け取って分岐できるよう、リダイレクト先の URL を json で返す。/** * @param \App\Http\Requests\UserPost $request * @param \App\Models\User $user * @return \Illuminate\Http\JsonResponse */ public function update(UserPost $request, User $user) { $validated = $request->validated(); $user->update($validated); return response()->json(['redirect' => route('admin.user.show', $user)]); }以上で、フォームのサブミットボタンをクリックしたときに、画面遷移を伴わないバリデーション処理が実現できた。万歳。
Laravel によるリアルタイムバリデーションの模索
this.form
の変更を watch して1項目ずつサーバーに問い合わせることができれば、一元管理されたバリデーション定義で、リアルタイムバリデーションも実現できるのでは?・・・と、試してみた。
- libs.js に
computed
とapp.$watch
のテストコードを追加した。/** * Vue.js with form-backend-validation の初期定義 * * @param {object} option data.fields を含む Vue へ渡すオプション * @param {string} url フォームのポスト先URL */ export function formBackendValidation(option, url) { const app = new Vue(merge({ el: '#app', data: { form: new Form(option.data.fields), message: '', messageClass: '' }, methods: { onSubmit() { this.form['post'](url) .then(res => { if (res.redirect) { location.href = res.redirect } else if (res.message) { this.displayMessage(message, true); } else { this.displayMessage('更新しました。', true); } }) .catch(res => this.displayMessage('エラーを確認してください。', false)); }, displayMessage(message, success) { this.messageClass = 'alert-' + (success ? 'success' : 'danger') this.message = message; }, clearMessage() { this.message = ''; }, }, computed: { computedForm() { return JSON.parse(JSON.stringify(this.form)) } } }, option)); app.$watch('computedForm', function (newVal, oldVal) { for (let key in app.fields) { if (newVal[key] != oldVal[key]) { console.log(key, newVal[key], oldVal[key]); } } }, {deep: true}); return app; } (後略)
deep: true
で定義したthis.form
一括 watch では、2つの引数 newVal と oldVal の値が同じになってしまうため、一旦computed
で計算した値を watch する必要があったが、これで変更があったカラムを抽出することが可能となった。次の手順としては・・・
- フロント側
- 変更がなかったカラムを
disabled
にし、変更があったカラムだけでフォームポストする。- または、変更があったカラム名を、
hidden
要素としてフォームに追加したポストする。- Laravel 側
- 2の場合は、変更があったカラムのポストだけに Laravel 側で絞り込む。
- バリデーション定義にはすべて
somtimes
を加え、ポストされたカラムに対してだけ判定を返す。2の方法では、フォームに file 要素があった場合に負荷が大きすぎるので、1の方法がベターか。
なんとか実現の方向性が見えてきたように気がした・・・この時点では。リアルタムバリデーション計画の行方は・・・
1文字入力するたびにサーバーへ通信するわけにはいかないので、v-model に修飾子 lazy を追加する。これにより入力を終えてフォーカスが移ったときの判定になる・・・はずだった。
<input id="email" type="email" v-model.lazy="form.email" class="form-control" :class="{ 'is-invalid': form.errors.has('email') }">ところがなんと、この追加で入力要素において1文字以上入力できない問題が発生した!
ネイティブの Vue.js では生じない症状なので、form-backend-validation 固有の問題であるよう。この問題が解決するまでは、リアルタムバリデーションはお預けとなった。
- 投稿日:2020-11-24T14:57:13+09:00
DockerでLaravelを構築してみた
Laradockとは
Docker環境でLaravel環境を簡単に構築できるDockerイメージです。
Laradockのプロジェクトは大きくなり、SymfonyやWordpress環境までも構築出来るようになりました。機能としては、Nginx, Apacheなどのwebサーバー、PHPの各バージョン、Mysql, PostgreSQL等の各データベースを自由に選択できます。
今回はNginx、PHP-FPMが動く環境を作り、Laravelを立ち上げるまでを記載します。
前提
・dockerインストールしていること
・docker-composeコマンドが実行できるようになっていることが必要。環境
Windows10 Pro
手順
[1] CMD開く。作業ディレクトリを作成してそこまで移動する
cd C:\Users\user[2] フォルダ内でLaradockをgithubからcloneします。
git clone https://github.com/laradock/laradock.git[3] Laravel環境を構築するまでの下準備
nginx(webサーバー。Apacheでも良いが、今回はLaradockの公式マニュアルに沿いました)
php-fpm(PHPを実行する環境。いわゆるfastCGI).envの作成
copy env-example .envdockerコンテナの立ち上げ
docker-compose up -d nginxコンテナが立ち上がっているかを確認する
docker-compose psworkspaceにログインし、Laravelをセットアップする
docker-compose exec workspace bash
workspaceにログインすると、最初の状態では/var/wwwにいる。この場所は、Windows側のprojectsフォルダ直下と同様の場所を表している。(laradockディレクトリ直下ではない)
/var/wwwの位置でLaravelをセットアップする
composer create-project laravel/laravel app01
現時点でディレクトリ構成は以下のようになっているかと思います。
親:projects
子1:laradock … Dockerコンテナ
子2:app1 … Laravel本体Dockerダッシュボード状態
ブラウザで動作確認する
ginx(webサーバー)の設定をします。設定ファイルを編集するため、一度、dockerコンテナを停止します。Windows側のlaradockディレクトリ直下で以下のコマンドを叩く
dockerコンテナが全てストップします。
コンテナを再度立ち上げる
docker-compose up -d nginxコンテナが立ち上がったら、ブラウザでhttp://localhost へアクセス
参考
- http://laradock.io
- http://laradock.io/introduction/
- 環境作成時に次のエラーが出た場合
UnexpectedValueException
The stream or file "/var/www/storage/logs/laravel.log" could not be opened:
failed to open stream: Permission deniedlaravel.logに書き込もうとして権限がなかった時に発生します。
sudo chmod 777 -R storage/参考になったURL
https://teratail.com/questions/236475
- 投稿日:2020-11-24T13:03:45+09:00
laravelでsitemap.xmlからHTTPテストを作る
経緯
「とりあえずHTTPテストやりたいけど、構造はよく変わるしテストの維持がとても大変」
「関係ないけどsitemap.xmlの更新も面倒だなぁ」
「そうだ!sitemap.xmlからテストを作れば1度の修正で済むぞ!」環境
laravel 6.19
PHPUnit 8.2.5備考
前提として、テストを行うルート(sitemap.xmlに載せているルート)にnameが設定されている必要があります。
このテストは「sitemap.xml(このサイトに存在すると世界に示しているページ)にアクセス可能か」ということだけを検証するものです。
テストの管理が難しい環境で、最低限のHTTPテストとしてとりあえず入れてみるのには良いのでは無いかと思います。出来たもの
sitemap/index.blade.php<?php echo '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; ?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>{{ route('home') }}</loc> <changefreq>daily</changefreq> </url> <url> <loc>{{ route('info') }}</loc> <changefreq>daily</changefreq> </url> {{--こういうshowページあったとする--}} @foreach($articles as $article) <url> <loc>{{ route('article.show', ['id' => $article->id]) }}</loc> <changefreq>daily</changefreq> </url> @endforeach </urlset>SiteMapTest.php<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class SiteMapTest extends TestCase { /** * 同一ルートを何個試すか */ private $count = 3; /** * @test * @group sitemap * @dataProvider additionPrivateItems * @param string $url */ public function test(string $url) { $response = $this->get($url); $response->assertStatus(200); } public function additionPrivateItems() { $xmlData = simplexml_load_string(view('sitemap.index')->render()); $data = json_decode(json_encode($xmlData), TRUE)['url']; shuffle($data); foreach ($data as $url) { $url = $url['loc']; $routeName = $this->getRouteName(parse_url($url, PHP_URL_PATH) ?? ''); if (empty($routes[$routeName])) $routes[$routeName] = 1; elseif ($routes[$routeName] >= $this->count) continue; else $routes[$routeName]++; yield [$url]; } } private function getRouteName(string $uri) { return app('router')->getRoutes()->match(app('request')->create($uri))->getName(); } }コードの中身
PHPunitのdataProviderを使ってテストケースを作っています。
laravelなら大体sitemap.xmlはblade使って生成していると思うので、
- viewを
->render()
で文字列として取得- xml文字列を
simplexml_load_string
で取得- あれやこれやして配列で取得
- 毎回ランダムなルートをテスト出来るようにシャッフルする
- foreachで回し、それぞれURIからルート名を取得
- 同じルートだったら個数制限する
という流れです。
PHPunitのgroupを設定しているので
./vendor/bin/phpunit --group=sitemapこれで実行出来ます。
終わり
これは本当に最低限の機能テストなので、フォームの送信テストなどPOST系のテストは別に用意出来ると良いです。
全部ルーティングから自動生成出来たらな。
- 投稿日:2020-11-24T12:43:32+09:00
【Laravel】中間テーブルに値を追加・更新する方法
実現したいこと
中間テーブルに値を追加・更新する。
タグ機能を作る際に、プロジェクトテーブルとタグーテーブル紐付ける際に
中間テーブルを作り、値を追加・更新することができたので、紹介します。テーブル設計
タグテーブル
id name 1 Javascript 2 PHP プロジェクトテーブル
id title 1 フロントエンドを学習しよう 2 バックエンドを学習しよう 中間テーブル(プロジェクトタグテーブル)
id tag_id project_id 1 1 1 2 1 2 3 2 2 中間テーブルはこのように、テーブ同士が多対多の関係の時に必要になります。
中間テーブルに値を追加する方法
中間テーブルに値を入れるためのステップ
- テーブルのリレーションの設定
- 中間テーブルにアクセスし、値の追加
1. テーブルのリレーションの設定
タグのモデルでプロジェクトと紐付けます。belongsToManyは多対多のリレーション
時に使います。これで中間テーブルにアクセスするための準備は完了です。
public function project() { return $this->belongsToMany(Project::class); }2. 中間テーブルにアクセスし、値の追加
実際に値を追加します。値を追加する場合はattach()を使います。
まず、どのプロジェクトにタグを紐ずけるか、設定します。
今回は、projectのid:1にタグid:1を設定し、attachメソッドで追加します。$project = Project::find(1); //idが1番のプロジェクトを取得します。 $project->tags()->attach(1); //プロジェクトid:1にタグid:1を追加します。中間テーブルを確認すると下記のようになります。
id tag_id project_id 1 1 1 このように、中間テーブルに値を追加できます。
中間テーブルの値を更新する場合
更新したい場合は、syncメソッドを使います。
syncメソッドの仕組みは値の削除、追加を行います。
今回の例なら、project_id:1にtag_id:1が登録されています。
syncを実行することで、tag_id:1を削除します。そして、$project->tags()->sync(2);
としているので、project_id:1のtag_id:に2を追加します。
なので、attachを使わなくても、追加、更新はsyncでできてしまうのです。$project = Project::find(1); //idが1番のプロジェクトを取得します。 $project->tags()->sync(2); //中間テーブルのproject_id:1のtag_idを更新中間テーブルを確認すると下記のようになります。
id tag_id project_id 1 2 1 まとめ
中間テーブルへの値の追加はattach()メソッド、値の更新はsync()メソッド。
attach()を使わなくても、syncメソッドは追加、更新ができる。
- 投稿日:2020-11-24T12:03:58+09:00
Laravel学習 Viewとテンプレートについて
はじめに
laravel学習をしていて下記について学んだことをまとめてみました。
①Viewについて
②テンプレートとは
③クエリー文字列
④Bladeについて
⑤ヘルパ関数とは
⑥ディレクティブ
⑦コンポーネント
①Viewとは
・画面表示を担当。画面表示させる部分をわかりやすい形で作れるようになっている。
・HTMLを使ってそのまま表示内容を記述できる仕組み②テンプレートとは
・Laravelの中でもViewを担当する重要な”部品”のこと
・画面表示のベースとなるもの。
・テンプレートを読み込む→変数など必要な情報を当てはめて実際の表示を生成する(レンダリング)
・レンダリングはテンプレートエンジンによって行われる
・テンプレートエンジンとはデータとテンプレートを合体させ、文字列を作る仕組みのこと
・テンプレートエンジンBladeと呼ばれるLravel既存のテンプレート使うか、自分で作成するかで使用する▼値をテンプレートに返す
・コントローラ側から、テンプレート側へ必要な変数などの値を受け渡す。views/作成フォルダ/作成ファイル(テンプレート側) <html> <body> <h1>Hello/index</h1> <p><?php echo $msg?></p> <p><?php echo date("Y年n日j日");?></p> </body> </html>#Http/○○Contoroller.php(コントローラ側) class ○○Contoroller extends Controller { public function index() { $変数=['msg'=>'コントローラから渡されたメッセージ']; return view('フォルダ名.ファイル名',$変数); //この場合のviewメソッドは ”return view('テンプレート',配列);”となる }}この場合のviewメソッドは ”return view('テンプレート',配列);”となるindexアクションのviewメソッドの部分で値をテンプレート側に呼び出している。コントローラ側で配列として用意した値はviewでテンプレート側に渡されて使えるようになる。
③クエリー文字列
・クエリー文字列とはアドレスの後に?○○=✖️✖️と言った形式で付けられたテキスト部分のこと
・クエリー文字列の受け取り方は$request->idというような渡し方をする。④Blade
・Laravel内に独自に用意されているテンプレートエンジン
・Bladeは「○○○.blade.php」という形でファイルを作成する。
・変数は{{$変数}}としてテンプレート内に埋め込める
・同名のファイル名があれば’blade’が優先される。⑤ヘルパ関数
→テンプレートで必要となるコードの生成を手助けしてくれるもの。
▼csr_field()
CSRF対策のために用意されたヘルパ関数。フォームに「トークン」と呼ばれるランダムな文字列を非表示で追加し、そのトークンの値が正しいフォームだけを受け受けるようにすることでセキュリティを強化する。▼CSRF
webサイト攻撃の一つ。外部からのプログラムなどによってフォームを送信する攻撃。※LaravelはCSRF対策がなされていないフォームの送信はエラーが発生し、受け付けない仕組みとなっている。フォームを利用するときは必ず、csr_field()をフォーム内に準備する
#csr_field関数の使い方例 <form method="POST" action="/hello"> {{ csr_field() }} <input type="text" name="msg" > <input type="submit"> </form>Bladeの構文
▼値の表示
{}の間に文を書くことでその文が返す値をその場に書き出すことができる。{{値・変数・式・関数など}} {{!!値・変数・式・関数など!!}} //エスケープ処理されて欲しくない場合⑥ディレクティブ
言語における構文おような役割を担う機能。(@〜の構文)
▼Ifディレクティブ
条件に応じて表示する内容を制御するのが@ifディレクティブ
↓条件がtrueの時に表示する@if(条件) ーーー出力内容ーーー @endif↓条件によって異なる表示
@if(条件) ーーー出力内容ーーー @else ーーー出力内容ーーー @endif↓複数の条件による表示
@if(条件) ーーー出力内容ーーー @elseif(条件) ーーー出力内容ーーー @else ーーー出力内容ーーー @endif▼繰り返しディレクティブ
@for,@foreach,@while$loop変数を使うことで繰り返しの状態を指定できる。
▼@phpディレクティブ
変数の定義などに使用▼@yield()
@section内のテキストを表示させる。セクションの内容をはめこんで使用する・▼@section
セクションの作成。
一番上の@sectionでは@endsectionではなく、@Showを使用する⑦コンポーネント @component
・独立したテンプレート
・一部w切り離して組み込みたいときに使用する。▼@slot
{{}}で指定された変数に値を設定するためのもの@slot(名前(変数名)) 設定する内容 @endslot▼ビューコンポーザ
ビューをレンダリングする際に必要な部品のこと▼サービスプロバイダ
継承(extends ServiceProvider~)して使用
・サービスを提供するための仕組み
・Bootメソッドをしよう
→割り込み処理最後に
認識違いなどありましたらご指摘いただけると幸いです。
- 投稿日:2020-11-24T03:26:55+09:00
Illuminate\Database\QueryException : could not find driver (SQL: PRAGMA foreign_keys = ON;)の解決方法
結論 SQLiteの接続用ドライバーをインストール
まずPHPのバージョン確認
$ php -v PHP 7.4.12 (cli) (built: Oct 31 2020 17:04:25) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.12, Copyright (c), by Zend TechnologiesPHPのバージョンに合わせてインストール
$ sudo apt-get install php7.4-sqlite3無事に "php artisan migrate" できました
$ php artisan migrate Migration table created successfully.
- 投稿日:2020-11-24T03:09:38+09:00
Laravel 環境構築 (Ubuntu 18.04 LTS )
1. PHP7.4のインストール
下準備
$ sudo apt-get update $ sudo apt -y install software-properties-common $ sudo add-apt-repository ppa:ondrej/php $ sudo apt-get updateこれでPHP 7.4 をインストール可能なリポジトリを apt で使えるようになった。
インストール
$ sudo apt install php7.4 php7.4-mbstring php7.4-dom他に必要なものも一緒にインストールしておく。
インストールできたか確認
$ php -v PHP 7.4.12 (cli) (built: Oct 31 2020 17:04:09) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.12, Copyright (c), by Zend TechnologiesこうなればOK!
2. Comporserのインストール
LaravelをインストールするためにはComposerというのもをインストールする必要があります。
ComposerはPHPのパッケージ管理ツールで、Composerを使うとパッケージを効率よく管理することができるらしい。php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');"公式サイトのコマンドを使用 (https://getcomposer.org/download/)
確認
$ ./composer.phar -V Composer version 1.10.1 2020-03-13 20:34:27これでOK
いつでもComposerを使えるようにする
$ sudo mv composer.phar /usr/local/bin/composer $ sudo chmod +x /usr/local/bin/composer $ composer -v ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 2.0.7 2020-11-13 17:31:06以下省略。
zipコマンドをインストール
$ sudo apt install -y zip unzipLaravelのインストールを行う際にunzipコマンドが必要になるため、zipコマンドのインストール。
Laravel6.xをインストール
$composer create-project --prefer-dist laravel/laravel="6.*" laravel6下記を実行すると実行フォルダの下にlaravel6というフォルダが作成され、そのフォルダの中にLaravelに必要なファイルが保存される。
laravel6のところは好きな名前でOK
サーバー起動して確認
$ cd laravel6 $ php artisan serve
http://127.0.0.1:8000/
にアクセスしてこの画面になればOK!
- 投稿日:2020-11-24T03:01:28+09:00
Laravelのバージョンを指定する方法
- 投稿日:2020-11-24T01:13:45+09:00
PHP、Laravel 学習 コントローラーの説明①
コントローラーの説明
まずはこの画像をみてください
ルーティングの先にあるコントローラーを説明していきます。コントローラーはユーザーから送られてきた指令に対してどのようにアクションするかを決める場所です。
要するに総合窓口ですね。。。市役所とか電話すると「そのご相談しでしたら○○課ですね〜」って案内する人いるじゃないですか?それがコントローラーです。
ルーティングから直接ビューを見せることはたまにありますが、基本的にコントローラーを介して見せる場合が多いです
(モデルはルーティングから直接見せることはほぼない)コントローラーの記法、RestFulコントローラー(リソースコントローラー)について
基本的な記法を以下に書いておきます
//HomeController public function top(){ return view('home.top'); }このソースコードは「top」という命令がきたら、「home」フォルダーの中にある「top」という名前のファイルを表示してくださいね、というコントローラーです。これが基本中の基本です。
これ以外で基本的なところで言えば1、リストを表示させる
2、入力した情報をデータベースに保存させる
3、保存したデータを編集する
4、保存したデータを削除する
...etcがありますが
「いや〜、ルーティングに対していちいちコントローラー書いてるのめんどくさいっすよ。っていうかルーティングもコントローラーと同じ数書くんですか〜?」という方に朗報があります。それはRestFulなコントローラー(リソースコントローラー)です
(以下、リソースコントローラーで統一します。)簡単に説明するとルーティングにを一回書いたらその中に大体の人が使うであろうルートとコントローラーを作ってくれると言う代物です。公式が用意してくれてるわけですね。。。
用意してくれている内容は以下になります。
※ほぼ公式からの抜粋
動詞 URI アクション ルート名 GET /photos index photos.index GET /photos/create create photos.create POST /photos store photos.store GET /photos/{id} show photos.show GET /photos/{id}/edit edit photos.edit PUT/PATCH /photos/{id} update photos.update DELETE /photos/{id} destroy photos.destroy こちらはルーティングのことが記載されている表ですが、もちろんこれに合わせたコントローラーも自動生成されてます。(リソースコントローラーの自動生成の仕方は後述)
上から一つづつ説明していきます。
①index
保存したデータのリストを表示する時に使うアクション
②create
データを保存するためのビューを表示させるためのアクション
③store
送られてきたデータを保存するためのアクション(基本的にcreateから送られて来る)
④show
一つのデータの詳細を表示するアクション
⑤edit
一つのデータを編集するためのビューを表示するためのアクション
⑥update
送られてきた「こう言う風に変更してくれ」と言う命令をきくアクション(基本的にeditから送られて来る)
⑦destroy
送られてきたデータを削除するアクション
↑で説明したものは基本的にこう言う風に使われますよと言うものの例で必ずしもこれにのっとった使い方をしろと言うわけではないです、ただこの通りに進めて行けば迷わずに開発が進められるので私はこのリソースコントローラーを使って開発をすることにしました。あと何より記述が少なくて済む!
リソースコントローラーを使っていく
では勿体ぶって申し訳ございません、リソースコントロラーの自動生成をしていきます。
まずはじめに以下のコマンドをterminalで入力しましょう$ php artisan make:controller PhotoController --resourceちなみに公式にのっとってやっているのでコントローラー名(Photo)のところは好きに変えてOKです。
これを入力するだけで↑で説明した7つのコントローラーが自動生成されます。あとはルーティングを書いてきますが。。。
Route::resource('photos', PhotoController::class);こちらもこれを書くだけでこの中に7つのルーティングが内包されています。
いや〜少ない記述で便利。ここから使い方について説明していこうと思ったのですがこの時点で長くなりすぎてしまったので次回にしたいと思います。
次回以降の予定
①コントローラーの残りの説明
②Viewの詳しい説明
③Viewの継承(@extend)について
④Bootstrapについてちゃんと説明しようと思うとつい長くなりがちですね。。。
あっ、ちなみにこの記事で至らない点等ございましたら是非、ご指導ご鞭撻ください。
- 投稿日:2020-11-24T00:14:09+09:00
laravel dumpが原因でphpunit Target class [config] does not exist
備忘録です。
コントローラーを書いている時など、dump()は画面で確認出来て便利なのですが、
そのdumpが残ったままphpunit で当該コントローラーのテストを行うと、
Target class [config] does not exist
のエラーが起こる事があります。Target class [config] does not exist
が起こる状態を色々見ていて、どうもdump()が怪しいと気が付いたのでした。
そこで、テスト時と実行時で切りわけるdumpもどきを作成してみました。class MyFunctions { public static function mydump(...$d){ //$_SERVER['APP_ENV'] は phpunit 実行時のみセットされるみたい if( isset($_SERVER['APP_ENV'])){ foreach($d as $n){ var_dump($n); } }else{ foreach($d as $n){ dump($n); } } } }呼び出し可能な状態にしておいて、dump()と書いている部分を、MyFunctions::Mydump()に書き換えていけばどちらでも通るようになりました。
実運用する際にmydumpの中味書き換えてしまえばdump()が残ることもなさそうです。