20201124のPHPに関する記事は15件です。

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-audit

phpcs-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.phpexample_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がプルリクにレビューコメントを出力することができません。

image.png

先ほど、pushしたものをプルリクにだすと、上記のようにGitHub Actionsが実行されて、該当コードのところに犬のマークがついたreviewdogがどんな脆弱性があるかをコメントとして出力してくれます。

最後

私は普段、インフラ担当で、phpのlaravelに詳しくなかったので、今回のCIでの実装はいい勉強になりました。
もし、この記事を見ていただいて、表現がおかしいや誤った個所等ございましたら、コメントお願いします!

最後まで読んでいただき、ありがとうございました!

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

ネットワーク攻撃手法と対策

xss

入力フォームへのjavascript入力など。

解決策

htmlspecialchars($hoge,ENT_QUOTES,'UTF-8)でサニタイズ(消毒)。

クリックジャッキング

解決策

phpファイルに書く場合は、header('X-FRAME-OPTIONS:DENY')をコードの冒頭に入れる。

X-FRAME-OPTIONSとは...?

X-Frame-Options - HTTP | MDN
X-Frame-Options は HTTP のレスポンスヘッダーで、ブラウザーがページを <frame>, <iframe>, <embed>, <object> の中に表示することを許可するかどうかを示すために使用されます。
サイトはコンテンツが他のサイトに埋め込まれないよう保証することで、クリックジャッキング攻撃を防ぐために使用することができます。

CSRF(クロスサイトリクエストフォージェリ)

CSRFは、リクエスト強要やセッションライディングとも呼ばれます。ユーザやブラウザを騙し、ユーザの意図しない場所へユーザのクライアントからリクエストを送信させる攻撃手法。

入力した情報の改竄や、入力したデータを盗まれる恐れがある。

解決策

フォームを入力するページが不正なサイトではないことを確認するために、トークン(合言葉)を使用して、本物のページからリクエストが来たのかを見分ける。

bin2hex(random_bytes(32))

random_bytes(32)で暗号を生成。
bin2hexは暗号を16進数に変換している。
ただし、セッションIDをハッシュ化したものでも問題ない。
セッションIDが漏れるということは、別のところに脆弱性があるわけだから、生成したトークンは同一ユーザで同じものを使い回してよい。

これをセッションに保存&hiddenで送信!
比較して値が正しければ処理を実行。

二重投稿対策

(これは攻撃ではないけど.....)
POSTの値は画面を遷移した際には消えるが、例えばリロードなどで同じ画面を表示した場合、消去されずに残ってしまう。その結果、同じ投稿がまたされてしまう。

解決策

1. ページをリダイレクト
2. トークンを使った対策

2の対策では、トークンを破棄、更新する必要がある。

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

【PHP】XSS攻撃への対策

投稿フォームを作成している際、特に対策をしていない場合XSS攻撃を受けてしまう事を学んだ。

PHPではXSS攻撃の対策としてhtmlspecialchars関数が有効であるのでこの関数を使用してフォームを作成。

この関数を使用することによってフォームに入力された物がただの文字列として認識され、javascripなどを記入されても反応しない様になります。

<html lang="ja">
<body>
<h1>投稿フォーム</h1>
<form action="example.php" method="post">
<input type="text" name="comment"><br/>
<input type="submit" value="送信">
</form>
<?php
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
echo $comment;
?>
</body>
</html>

htmlspecialcharsは名前が長いので関数に入れてしまう事も考えたが、このコード自体は短いものであったので今回はそのまま記述。

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

【PHP】九九表

九九表を作成する問題を説いた際、短時間で綺麗にコードを書く事ができなかったので備忘録として完成させたコードを載せます。

<?php
    //縦方向に出力するためのループ
    for( $i = 1 ; $i <= 9 ; $i++ ){

        //横方向に出力するためのループ
        for( $j = 1 ; $j <= 9 ; $j++ ){

            //一桁の場合空白を入れる様にする
            if( $i * $j < 10) echo " ";
            echo $i * $j, " ";
        }
        echo "\n";
    }
?>

このコードによって表示される結果は下記。

 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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系のテストは別に用意出来ると良いです。
全部ルーティングから自動生成出来たらな。

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

【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. テーブルのリレーションの設定
  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メソッドは追加、更新ができる。

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

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メソッドをしよう
→割り込み処理

最後に

認識違いなどありましたらご指摘いただけると幸いです。

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

mysqldumpで空ファイルしかできないと思ったら

mysqldumpで空ファイルしかできないと思ったら

背景

MySQLでdumpファイルを作りたくmysqldump -uroot -p データベース名 > ファイル名.sqlのコマンドを打っているのにできるのは中身が空のファイル。
今までできていたのに何故???

原因

単純なコマンドミスだった。
dumpファイル(空だが)自体は作成できていたため、指摘してもらうまでコマンドミスをしていることに全く気付かなった。

mysql -uroot -p データベース名 > ファイル名.sqlでdumpの空ファイルはできる

  • 間違い
    mysql -uroot -p データベース名 > ファイル名.sql

  • 正解
    mysqldump -uroot -p データベース名 > ファイル名.sql

mysqldumpと入力するべきところをmysqlと入力していた。

思い込むとなかなか気付けないこのミス。
空ファイルしかできなくて困ったときには一度コマンドを見直してみてください。

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

プログラマ脳を鍛える数学パズル Q.02「数列の四則演算」

基礎アルゴリズム力upのためPHPで解いてみた。
下記の考えのもと組んだ。

  • 1.for文で1000~9999を回す。
  • 2.正規表現指定(0を全て除外)
  • 3.数字を各位(千、百、十、一)に分け、それぞれの間に四則演算を入れたfor文。
  • 4.文字列式を作成。
  • 5.結合文字列をevalで計算 +6.対象数字と反転した結合結果の数字が一致するなら出力
<?php
$four_calc = array('+', '-', '*', '/','');
$count = count($four_calc);
for ($num = 1000; $num < 10000; $num++) {
  if (preg_match("/[0]+/", $num)) {
    continue;
  }
  $arrNum = str_split($num);
  for ($i = 0; $i < $count; $i++) {
        for ($j = 0; $j < $count; $j++) {
            for ($k = 0; $k < $count; $k++) {
        $cal_form = $arrNum[0].$four_calc[$i].
                    $arrNum[1].$four_calc[$j].
                    $arrNum[2].$four_calc[$k].
                    $arrNum[3]; // 文字列式を作成
        if (mb_strlen($cal_form) >= 5) {
          if (eval("return {$cal_form};") == strrev($num)) {
            print $cal_form . "=" . $num . "\n";
          }
        }
      }
    }
  }
}
?>

答えは、5*9*31=5931

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

お問い合わせフォーム、jsでバリデーション

contact.js
window.addEventListener('DOMContentLoaded', () => {
    // 「送信」ボタンの要素を取得
    const submit = document.querySelector('#contact-submit');

    // エラーメッセージと赤枠の削除
    function reset(input_infomation, error_message){
        const input_info = document.querySelector(input_infomation);
        const err_message = document.querySelector(error_message);
        err_message.textContent ='';
        input_info.classList.remove('input-invalid');
    };

    // 「お名前」入力欄の空欄チェック関数
    function invalitName(input_target, error_target, error_message){

        const name = document.querySelector(input_target);
        const errMsgName = document.querySelector(error_target);

        if(!name.value){
            errMsgName.classList.add('form-invalid');
            errMsgName.textContent = error_message;
            name.focus();
            name.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };

    // 「ふりがな」入力欄の空欄チェック関数
    function invalitHurigana(input_target, error_target, error_message){

        const hurigana = document.querySelector(input_target);
        const errMsgHurigana = document.querySelector(error_target);

        if(!hurigana.value){
            errMsgHurigana.classList.add('form-invalid');
            errMsgHurigana.textContent = error_message;
            hurigana.focus();
            hurigana.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;

    };

    // 「郵便番号」入力欄の空欄チェック関数
    function invalitPostal(input_target, error_target, error_message){

        const postal = document.querySelector(input_target);
        const errMsgPostal = document.querySelector(error_target);

        if(!postal.value){
            errMsgPostal.classList.add('form-invalid');
            errMsgPostal.textContent = error_message;
            postal.focus();
            postal.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;

    };

    // 「住所」入力欄の空欄チェック関数
    function invalitAddress(input_target, error_target, error_message){

        const address = document.querySelector(input_target);
        const errMsgAddress = document.querySelector(error_target);

        if(!address.value){
            errMsgAddress.classList.add('form-invalid');
            errMsgAddress.textContent = error_message;
            address.focus();
            address.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };

    // 「電話番号」入力欄の空欄チェック関数
    function invalitTel(input_target, error_target, error_message){

        const tel = document.querySelector(input_target);
        const errMsgTel = document.querySelector(error_target);

        if(!tel.value){
            errMsgTel.classList.add('form-invalid');
            errMsgTel.textContent = error_message;
            tel.focus();
            tel.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };

    // 「メールアドレス」入力欄の空欄チェック関数    
    function invalitEmail(input_target, error_target, error_message){

        const email = document.querySelector(input_target);
        const errMsgEmail = document.querySelector(error_target);

        if(!email.value){
            errMsgEmail.classList.add('form-invalid');
            errMsgEmail.textContent = error_message;
            email.focus();
            email.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };

    // 「会社名」入力欄の空欄チェック関数
    function invalitCompany(input_target, error_target, error_message){

        const company = document.querySelector(input_target);
        const errMsgCompany = document.querySelector(error_target);

        if(!company.value){
            errMsgCompany.classList.add('form-invalid');
            errMsgCompany.textContent = error_message;
            company.focus();
            company.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };

    // 「お問い合わせ内容」入力欄の空欄チェック関数
    function invalitContent(input_target, error_target, error_message){

        const content = document.querySelector(input_target);
        const errMsgContent = document.querySelector(error_target);

        if(!content.value){
            errMsgContent.classList.add('form-invalid');
            errMsgContent.textContent = error_message;
            content.focus();
            content.classList.add('input-invalid');          
            // 動作を止める
            return false;
        };
        // 動作を進める
        return true;
    };


    // 「送信」ボタンの要素にクリックイベントを設定する
    submit.addEventListener('click', (e) => {
        // デフォルトアクションをキャンセル
        e.preventDefault();

        reset('#name-js', '#err-msg-name');
        reset('#hurigana-js', '#err-msg-hurigana');
        reset('#postal-js', '#err-msg-postal');
        reset('#address-js', '#err-msg-address');
        reset('#tel-js', '#err-msg-tel');
        reset('#email-js', '#err-msg-email');
        reset('#company-js', '#err-msg-company');
        reset('#content-js', '#err-msg-content');

        const focus = () => document.querySelector('#name-js').focus();

        // 「お名前」入力欄の空欄チェック
        if(invalitName('#name-js', '#err-msg-name', 'お名前が入力されていません')===false){
            return;
        };
        // 「ふりがな」入力欄の空欄チェック
        if(invalitHurigana('#hurigana-js', '#err-msg-hurigana', '入力必須です')===false){
            return;
        };

        // ひらがなチェック
        const hurigana = document.querySelector("#hurigana-js");
        const errMsgHurigana = document.querySelector("#err-msg-hurigana");
        const huriganaCheck = /[^ぁ-んー  ]/u; 
        if(hurigana.value.match(huriganaCheck)){
            errMsgHurigana.classList.add('form-invalid');
            errMsgHurigana.textContent = 'ひらがなで入力してください';
            hurigana.focus();
            hurigana.classList.add('input-invalid');
            return;
        }else{
            errMsgHurigana.textContent ='';
            hurigana.classList.remove('input-invalid');
            hurigana.blur();
        };

        // 「郵便番号」入力欄の空欄チェック
        if(invalitPostal('#postal-js', '#err-msg-postal', '入力必須です')===false){
            return;
        };

        // 郵便番号形式チェック
        const postal = document.querySelector("#postal-js");
        const errMsgPostal = document.querySelector("#err-msg-postal");
        const postalCheck = /([0-9]{7})$/; 
        // const postalCheck = /^\d{7}$/; 
        if(postal.value.match(postalCheck)){
            errMsgPostal.textContent ='';
            postal.classList.remove('input-invalid');
            postal.blur();
        }else{
            errMsgPostal.classList.add('form-invalid');
            errMsgPostal.textContent = '郵便番号の形式が違います';
            postal.focus();
            postal.classList.add('input-invalid');
            return;
        };

        // 「住所」入力欄の空欄チェック
        if(invalitAddress('#address-js', '#err-msg-address', '入力必須です')===false){
            return;
        };
        // 「電話番号」入力欄の空欄チェック
        if(invalitTel('#tel-js', '#err-msg-tel', '入力必須です')===false){
            return;
        };

        //電話番号形式チェック
        const tel = document.querySelector("#tel-js");
        const errMsgTel = document.querySelector("#err-msg-tel");
        const telCheck = /0\d{1,4}\d{1,4}\d{4}/; 
        if(tel.value.match(telCheck)){
            errMsgTel.textContent ='';
            tel.classList.remove('input-invalid');
            tel.blur();
        }else{
            errMsgTel.classList.add('form-invalid');
            errMsgTel.textContent = '電話番号の形式が違います';
            tel.focus();
            tel.classList.add('input-invalid');
            return;
        };

        // 「メールアドレス」入力欄の空欄チェック
        if(invalitEmail('#email-js', '#err-msg-email', '入力必須です')===false){
            return;
        };

        // Email形式チェック
        const email = document.querySelector("#email-js");
        const errMsgEmail = document.querySelector("#err-msg-email");
        const emailCheck = /^[-a-z0-9~#&'*/?`\|!$%^&*_=+}{\'?]+(\.[-a-z0-9~#&'*/?`\|!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)|(docomo\ezweb\softbank)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i; 
        if(email.value.match(emailCheck)){
            errMsgEmail.textContent ='';
            email.classList.remove('input-invalid');
            email.blur();
        }else{
            errMsgEmail.classList.add('form-invalid');
            errMsgEmail.textContent = 'Emailの形式で入力してください';
            email.focus();
            email.classList.add('input-invalid');
            return;
        };

        // 「会社名」入力欄の空欄チェック
        if(invalitCompany('#company-js', '#err-msg-company', '入力必須です')===false){
            return;
        };
        // 「お問い合わせ内容」入力欄の空欄チェック
        if(invalitContent('#content-js', '#err-msg-content', '入力必須です')===false){
            return;
        };

        document.customerinfo.submit();

    }, false);  
}, false);
contact.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title></title>
  <link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet">
  <link type="text/css" rel="stylesheet" href="./contact.css">
    <script src="https://yubinbango.github.io/yubinbango/yubinbango.js" charset="UTF-8"></script>
</head>
<body>
<?php 
    // require "header.php";
?>
<main>


  <section class="container container-ornament" id="contact">
    <h2 class="container-title"><span>お問い合わせ</span></h2>
    <div class="container-body">
        <div class="container-required">
        <p class="Required-title"><span class="Required"></span>は入力必須項目になります。</p>
        </div>

      <form action="contact_db_connect.php" class="form form-m h-adr" method="post" name="customerinfo">
      <!-- <form action="" class="form form-m h-adr" method="post" name="customerinfo"> -->
          <table>

              <tr>
                  <th class="th"><span class="Required"></span>お名前</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-name"><?php if(!empty($err_msg['name'])) echo $err_msg['name']; ?></span>
                        <input class="input input-l" id="name-js" name="name" type="text" placeholder="例)神戸 太郎" value="<?php if(!empty($_POST['name'])) echo $_POST['name']; ?>" >
                    </td>
              </tr>

              <tr>
                  <th class="th"><span class="Required"></span>ふりがな</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-hurigana"><?php if(!empty($err_msg['kana'])) echo $err_msg['kana']; ?></span>
                        <input class="input input-l" id="hurigana-js" name="kana" type="text" placeholder="例)こうべ たろう" value="<?php if(!empty($_POST['kana'])) echo $_POST['kana']; ?>" >
                    </td>
              </tr>

                <span class="p-country-name" style="display:none;">Japan</span>

              <tr>
                  <th class="th"><span class="Required"></span>郵便番号</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-postal"><?php if(!empty($err_msg['zip'])) echo $err_msg['zip']; ?></span>
                        <input type="text" class="input input-l p-postal-code" id="postal-js" name="zip" size="8" maxlength="8" placeholder="ハイフン無し" value="<?php if(!empty($_POST['zip'])) echo $_POST['zip']; ?>" >
                    </td>
              </tr>

              <tr>
                  <th class="th"><span class="Required"></span>住所</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-address"><?php if(!empty($err_msg['addr'])) echo $err_msg['addr']; ?></span>
                        <input type="text" class="input input-l p-region p-locality p-street-address p-extended-address" id="address-js" name="addr" placeholder="住所" value="<?php if(!empty($_POST['addr'])) echo $_POST['addr']; ?>" >
                    </td>
              </tr>

              <tr>
                  <th class="th"><span class="Required"></span>電話番号</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-tel"><?php if(!empty($err_msg['tel'])) echo $err_msg['tel']; ?></span>
                        <input class="input input-l" id="tel-js" name="tel" type="tel" placeholder="例)09012345678 半角 ハイフンなし" maxlength="13" value="<?php if(!empty($_POST['tel'])) echo $_POST['tel']; ?>" >
                    </td>
              </tr>    

              <tr>
                  <th class="th sp-br"><span class="Required"></span>メール<br>アドレス</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-email"><?php if(!empty($err_msg['email'])) echo $err_msg['email']; ?></span>
                        <input class="input input-l" id="email-js" name="email" type="email" placeholder="例)example@.com" value="<?php if(!empty($_POST['email'])) echo $_POST['email']; ?>" >
                    </td>
              </tr>    

              <tr>
                  <th class="th"><span class="Required"></span>会社名</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-company"><?php if(!empty($err_msg['company'])) echo $err_msg['company']; ?></span>
                        <input type="text" class="input input-l" id="company-js" name="company" placeholder="例)〇〇〇〇株式会社" value="<?php if(!empty($_POST['company'])) echo $_POST['company']; ?>" >
                    </td>
              </tr>
                            <tr>
                  <th class="th">部署名</th>
                    <td class="td">
                        <input type="text" class="input input-l" name="department" placeholder=""  value="<?php if(!empty($_POST['company'])) echo $_POST['company']; ?>" >
                    </td>
              </tr>

              <tr>
                  <th class="th"><span class="Required"></span>お問い合わせ内容</th>
                    <td class="td">
                        <span class="err_msg" id="err-msg-content"><?php if(!empty($err_msg['text'])) echo $err_msg['text']; ?></span>
                        <textarea class="input input-l input-textarea mb-xxl" id="content-js" name="text" placeholder="お問い合わせ内容" value="<?php if(!empty($_POST['text'])) echo $_POST['name']; ?>" ></textarea>
                    </td>
              </tr>     

          </table>

        <button class="btn btn-corp btn-l" id="contact-submit">送信</button>
      </form>


    </div>
  </section>
</main>

<footer class="footer">
</footer>

<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"></script>
<script src="./app.js"></script>
<script src="./contact.js"></script>
</body>

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

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 Technologies

PHPのバージョンに合わせてインストール

$ sudo apt-get install php7.4-sqlite3

無事に "php artisan migrate" できました

$  php artisan migrate
Migration table created successfully.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 unzip

Laravelのインストールを行う際にunzipコマンドが必要になるため、zipコマンドのインストール。

Laravel6.xをインストール

$composer create-project --prefer-dist laravel/laravel="6.*" laravel6

下記を実行すると実行フォルダの下にlaravel6というフォルダが作成され、そのフォルダの中にLaravelに必要なファイルが保存される。

laravel6のところは好きな名前でOK

サーバー起動して確認

$ cd laravel6
$ php artisan serve

SharedScreenshot.jpg
http://127.0.0.1:8000/
にアクセスしてこの画面になればOK!

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

Laravelのバージョンを指定する方法

結論 composerコマンドで指定する

ターミナル
$ composer create-project --prefer-dist laravel/laravel="6.*" sample_app

これでLaravel6系統がインストールされる。

バージョン確認

ターミナル
$ php artisan -V
Laravel Framework 6.20.4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP、Laravel 学習 コントローラーの説明①

コントローラーの説明

まずはこの画像をみてください
MVCmodel-1024x492.jpg
ルーティングの先にあるコントローラーを説明していきます。

コントローラーはユーザーから送られてきた指令に対してどのようにアクションするかを決める場所です。
要するに総合窓口ですね。。。市役所とか電話すると「そのご相談しでしたら○○課ですね〜」って案内する人いるじゃないですか?それがコントローラーです。
ルーティングから直接ビューを見せることはたまにありますが、基本的にコントローラーを介して見せる場合が多いです
(モデルはルーティングから直接見せることはほぼない)

コントローラーの記法、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について

ちゃんと説明しようと思うとつい長くなりがちですね。。。
あっ、ちなみにこの記事で至らない点等ございましたら是非、ご指導ご鞭撻ください。

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

Laravelのユーザー登録を入力・確認・完了の3画面に分けた際のメモ

ユーザー登録画面を入力・確認・完了の3画面に分割する要件があり、Laravelで実装した際のメモになります。(あまりLaravelに慣れていないため、あまり綺麗なコードではないかもしれませんが、ご了承いただければと思います。)

ユーザー登録のルーティングは以下のようにしてみました。

routes/web.php
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('user.resister_show');
Route::post('register', 'Auth\RegisterController@post')->name('user.resister_post');
Route::get('register/confirm', 'Auth\RegisterController@confirm')->name('user.register_confirm');
Route::post('register/confirm', 'Auth\RegisterController@register')->name('user.resister_resister');
Route::get('register/complete', 'Auth\RegisterController@complete')->name('user.register_complete');

ユーザー登録後に自動的にログインさせるために、登録処理後にguardでログインしていますが、そのままログイン処理を実施してしまうと、guestミドルウェアの影響で完了画面が表示されないため、completeではguestミドルウェアを無効にしています。

app/Http/Controllers/Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    private $form_show = 'Auth\RegisterController@showRegistrationForm';
    private $form_confirm = 'Auth\RegisterController@confirm';
    private $form_complete = 'Auth\RegisterController@complete';

    private $formItems = ["name", "email", "password"];


    use RegistersUsers;

    protected $redirectTo = RouteServiceProvider::HOME;

    public function __construct()
    {
        // ユーザー登録後、ログインをした状態にして、完了画面を表示するため、completeではguestミドルウェアを無効にする
        $this->middleware('guest', ['except' => 'complete']);
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'min:8', 'max:16', 'unique:users'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'max:16'],
        ]);
    }

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }

    /*
     * 入力から確認へ遷移する際の処理
     */
    function post(Request $request)
    {
        $this->validator($request->all())->validate();

        $input = $request->only($this->formItems);

        //セッションに書き込む
        $request->session()->put("form_input", $input);

        return redirect()->action($this->form_confirm);
    }

    /**
     * 登録処理
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        //セッションから値を取り出す
        $input = $request->session()->get("form_input");

        // 戻るボタン
        if ($request->has("back")) {
            return redirect()->action($this->form_show)
                ->withInput($input);
        }

        //セッションに値が無い時はフォームに戻る
        if (!$input) {
            return redirect()->action($this->form_show);
        }

        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        //セッションを空にする
        $request->session()->forget("form_input");

        // 登録データーでログイン
        $this->guard()->login($user, true);

        return $this->registered($request, $user)
            ?  : redirect($this->redirectPath());
    }

    /*
     * 登録完了後
     */
    function registered(Request $request, $user)
    {
        return redirect()->action($this->form_complete);
    }

    /**
     * 会員登録入力フォーム出力
     *
     * @return \Illuminate\Http\Response
     */
    public function showRegistrationForm()
    {
        return view('auth.register.register');
    }

    /*
     * 確認画面出力
     */
    public function confirm(Request $request)
    {
        //セッションから値を取り出す
        $input = $request->session()->get("form_input");

        //セッションに値が無い時はフォームに戻る
        if (!$input) {
            return redirect()->action("Auth\RegisterController");
        }

        return view('auth.register.confirm', ["input" => $input]);
    }

    /*
     * 完了画面出力
     */
    public function complete()
    {
        return view('auth.register.complete');
    }

}

テンプレートについては、入力、確認、完了の3画面分用意することで、とりあえず動作しました。

/resources/views/auth/register/register.blade.php
@include('error_card_list')

<div class="card-text">
   <form method="POST" action="{{ route('user.resister_post') }}">
       @csrf
       <div class="md-form">
           <label for="name">ユーザー名</label>
           <input class="form-control" type="text" id="name" name="name" required value="{{ old('name') }}">
       </div>

       <div class="md-form">
           <label for="name">メールアドレス</label>
           <input class="form-control" type="text" id="email" name="email" required value="{{ old('email') }}">
       </div>

       <div class="md-form">
           <label for="password">パスワード</label>
           <input class="form-control" type="text" id="password" name="password" required value="{{ old('password') }}">
       </div>
       <button class="btn btn-block blue-gradient mt-2 mb-2" type="submit">新規登録</button>
   </form>
</div>
/resources/views/auth/register/confirm.blade.php
<div class="card-text">
    <form method="POST" action="{{ route('user.resister_resister') }}">
        @csrf
        <div class="md-form">
            <label for="name">ユーザー名</label>
            {{ $input["name"] }}
            <input class="form-control" type="hidden" id="name" name="name" required value="{{ $input["name"] }}">
        </div>

        <div class="md-form">
            <label for="name">メールアドレス</label>
            {{ $input["email] }}
            <input class="form-control" type="hidden" id="email" name="email" required value="{{ $input["email"] }}">
        </div>

        <div class="md-form">
            <label for="password">パスワード</label>
            {{ $input["password"] }}
            <input class="form-control" type="hidden" id="password" name="password" required value="{{ $input["password"] }}">
        </div>

        <button class="btn btn-block blue-gradient mt-2 mb-2" type="submit" name="back"">戻って変更する</button>
        <button class="btn btn-block blue-gradient mt-2 mb-2" type="submit">確認して登録する</button>
    </form>
</div>
/resources/views/auth/register/complete.blade.php
<div class="card-body pt-0 pb-2">
    <h3 class="h4 card-title">
        ご登録ありがとうございました!
    </h3>
</div>

以上になります。

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