20200720のPHPに関する記事は9件です。

正規表現を使うときに盛大にやらかした話

最近Minecraft非公式日本ユーザーフォーラム非公式アーカイブの作業を進めているんですが、その過程で正規表現の使い方を間違えてデータがぶっ飛んだので、同じ過ちを再度繰り返さないように書いておきます()

何をしていたか

wayback machineから取得したデータはHTMLのまま保存しているため、各リンクはweyback machineによって置き換えられています。
そのため、リンクをもとに戻そうとしていました

wayback machineの仕様

HTML -> web.archive.org/web/時間/元のURL
CSS -> web.archive.org/web/時間cs_/元のURL
JS -> web.archive.org/web/時間js_/元のURL
画像 -> web.archive.org/web/時間im_/元のURL

書いていたコード

$出力 = preg_replace('/http ?:\/\/web\.archive\.org\/web\/.*\/http ?:\/\/forum\.minecraftuser\.jp/', "https://forum.minecraftuser.xyz", "データ");

これは当初使用していたコードなのですが、これには重大な欠陥があり、下記のような入力があった際にデータが吹き飛びます。

入力

<a href="https://web.archive.org/web/20190715113557/http://google.com">Google</a>
<a href="https://web.archive.org/web/20190715113557/http://forum.minecraftuser.jp/">Minecraft非公式日本ユーザーフォーラム</a>

出力

<a href="https://forum.minecraftuser.xyz/">Minecraft非公式日本ユーザーフォーラム</a>

だいぶ中身がスッキリしましたね()

原因

このコードを書いた際、仕様に書いたURLの変化に雑に対応させようとして.*を使ってしまったのが原因

対応

$出力 = preg_replace('/http ?:\/\/web\.archive\.org\/web\/...............?.?.?\/http ?:\/\/forum\.minecraftuser\.jp/', "https://forum.minecraftuser.xyz", "データ");

不用意に.*を使用すると、予期しない場所が正規表現の対象になる可能性がありますので、お気おつけください

正規表現を使う際に参考になるサイト

https://userweb.mnet.ne.jp/nakama/

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

AWS SDK のリクエストヘッダを見る方法

  • AWS SDK for PHP の場合
    例えばsqsクライアントの場合
$client = new SqsClient([
    'region'      => ***,
    'version'     => ***,
    'credentials' => [
        'key'    => ***,
        'secret' => ***
    ],
    'debug' => true, # ←コレを追加
]);

debugを true にすることで標準出力にログが出力される。
参考: https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/faq.html

  • AWS SDK for JavaScript の場合
    例えばsesクライアントの場合
new AWS.SES().sendEmail(sesParams, function(err, data){
    if(error){
        console.log(this.httpResponse); // ← コレを追加
    } else {
        console.log(this.httpResponse);
    }
});

this というのは AWS.Response のことらしい。基本的にはレスポンスの情報を参照出来るらしいけど、リクエストした情報( AWS.Request )も見れる。
参考: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-a-callback-function.html

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

【PHP8.0】PHPにヌル安全オペレータが導入される

ユーザの住んでいる国を取得します。
しかし、うっかりユーザがnullでした。
どうなるでしょう。

    $user = null;
    echo $user->address->country ?? '';

PHPのプロパティは元よりnull安全なので、存在しないプロパティだろうがnullだろうがプリミティブ型だろうがいきなりプロパティを取り出せます。
取れない場合はE_NOTICEが発生しますが、??を使えば黙殺できます。

しかしメソッドはだめです。

    echo $user->getAddress()->getCountry() ?? ''; // Fatal error: Call to a member function getAddress()

律儀にエラーを潰すとこんなかんじになります。

    // 1
    if (method_exists($user, 'getAddress')) {
        if (method_exists($user->getAddress(), 'getCountry')) {
            echo $user->getAddress()->getCountry();
        }
    }

    // 2
    if ($user instanceof \User) {
        if ($user->getAddress() instanceof \Address) {
            echo $user->getAddress()->getCountry();
        }
    }

まあ面倒ですね。

ということでPHPにも流行りのnull安全オペレータが導入されることになりました。

以下は該当のRFC、Nullsafe operatorの日本語訳です。

Nullsafe operator

Introduction

このRFCでは、Null安全オペレータ?->を提案します。

Proposal

メソッドを呼び出したり、計算結果からプロパティを取り出したりといった作業は、nullではない対象にだけ行いたいのが普通です。
現在のPHPではnullチェックを入れる必要があるので、入れ子や繰り返しが多くなります。

$country =  null;

if ($session !== null) {
    $user = $session->user;

    if ($user !== null) {
        $address = $user->getAddress();

        if ($address !== null) {
            $country = $address->country;
        }
    }
}

// $countryが取り出せた

null安全オペレータ?->を使うと、このコードは以下のように書けます。

$country = $session?->user?->getAddress()?->country;

演算子の左側がnullだった場合はチェーン全体の実行が停止され、nullが返ります。
nullでなかった場合は通常の->と全く同じ動作になります。

Short circuiting

Introduction

ショートサーキット、短絡とは、条件に基づいて式の実行をスキップすることです。
よくある例としては&&||などです。

null安全オペレータについて、短絡を実装する方法は3種類ありました。
どの方法がよいか、以下の同じコードスニペットで見ていきましょう。

null?->foo(bar())->baz();

1. Short circuiting for neither method arguments nor chained method calls

・引数もメソッドも全て実行する、短絡しない。

このような例は、今のところHackくらいでしか見られません。
関数bar()もメソッドbaz()も両方とも実行されるため、baz()Call to a member function on nullのエラーが発生します。
3つの選択肢の中で、最も予想外の結果となるでしょう。
前回のRFCでの大きな問題点でした。

2. Short circuiting for method arguments but not chained method calls

・引数は飛ばすけどメソッドは飛ばさない。

これは、通常は短絡しないと呼ばれるものです。
関数bar()は呼び出されませんが、メソッドbaz()は呼び出されてCall to a member function on nullのエラーが発生します。

3. Short circuiting for both method arguments and chained method calls

・引数もメソッドも全てスルー

これは完全短絡と呼ぶことにしましょう。
関数bar()もメソッドbaz()も呼び出されず、エラーは発生しません。

Proposal

このRFCでは、完全短絡を実装します。
チェーン内のひとつの要素の評価に失敗した場合、それ以降の全てが中断され、チェーン全体がnullと評価されます。
以下の要素はチェーンの一部とみなされます。

・配列[]
・プロパティ->
・null安全プロパティ?->
・静的プロパティ::
・メソッドコール->
・null安全メソッドコール?->
・静的メソッドコール::

以下はチェーンに含まれず、別のチェーンが発生します。

・関数の引数
・配列[]内部の式
{}にプロパティアクセスした場合->{}

チェーンは最も短くなるように自動判別されます。
以下はその例です。

   $foo = $a?->b();
// --------------- chain 1
//        -------- chain 2
// $aがnullだったらchain2が中断、b()は実行されず$foo=nullになる

   $a?->b($c->d());
// --------------- chain 1
//        -------  chain 2
// $aがnullだったらchain1が中断、b()は実行されず$foo=nullになる。chain2も実行されない。

   $a->b($c?->d());
// --------------- chain 1
//       --------  chain 2
// $cがnullだったらchain2が中断、d()は実行されない。chain1の$a->b()は実行されて引数はnullになる。

Rationale

この動作を選択した理由。

1. It avoids surprises

$foo = null;
$foo?->bar(expensive_function());

驚きを最小にします。
$fooがnullであればexpensive_function()の実行は望ましくないでしょう。

2. You can see which methods/properties return null

$foo = null;
$foo?->bar()->baz();

短絡がないと、チェーン内のメソッド呼び出しやプロパティアクセス全てにnull安全オペレータを使用しなければならなくなります。
不要な部分には使わないことで、どのメソッドやプロパティに問題があるのかを特定することもできます。

3. Mixing with other operators

$foo = null;
$baz = $foo?->bar()['baz'];
var_dump($baz);

// 短絡がない場合:Notice: Trying to access array offset on value of type null
// このRFC:null

短絡によって配列アクセス['baz']は完全にスルーされるので、E_NOTICEは発生しません。

Other languages

Stack Overflow 2020 surveyによる人気の高水準言語、および姉妹言語Hackについて、null安全オペレータの実装状況を確認してみます。

言語 有無 表記 短絡
JavaScript ?.
Python
Java
C# ?.
TypeScript ?.
Kotlin ?. ×
Ruby &. ×
Swift ?.
Rust
Objective-C ※1
Dart ?. ×
Scala ※2
Hack ?→ ※3

※1:nilへのプロパティやメソッド呼び出しは常に無視される
※2:DSL経由で可能
※3:メソッド引数も評価する

13のうち8言語がnull安全オペレータを持っており、そのうち4言語は短絡評価します。

Syntax choice

?が、短絡が発生する正確な場所を表します。
これは、null安全オペレータを実装している他の全ての言語と似ています。

Forbidden usages

Nullsafe operator in write context

null安全オペレータを代入に使用することはできません。

$foo?->bar->baz = 'baz';
// Can't use nullsafe operator in write context

foreach ([1, 2, 3] as $foo?->bar->baz) {}
// Can't use nullsafe operator in write context

unset($foo?->bar->baz);
// Can't use nullsafe operator in write context

[$foo?->bar->baz] = 'baz';
// Assignments can only happen to writable values

このRFCの以前のバージョンでは、=の左側にnull安全オペレータを使った場合、nullであれば代入をスキップすることが提案されていました。
しかし、技術的な問題からこの仕様は外されました。
今後のRFCで追加されるかもしれません。

References

リファレンスは許可されません。
参照するためには変数やプロパティのメモリ上の値が必要となりますが、null安全オペレータはnullを返すことがあるからです。

$x = &$foo?->bar;

// おおむね↓と同じ
if ($foo !== null) {
    $x = &$foo->bar;
} else {
    $x = &null;
    // Only variables should be assigned by referenceのエラー
}

従って、以下のような例は禁止となります。

$x = &$foo?->bar;
// Compiler error: Cannot take reference of a nullsafe chain

takes_ref($foo?->bar);
// Error: Cannot pass parameter 1 by reference

function &return_by_ref($foo) {
    return $foo?->bar;
    // Compiler error: Cannot take reference of a nullsafe chain
}

引数に参照を渡せるかどうかはコンパイル時にはわからないため、2番目の例は実行時エラーになります。

Backward Incompatible Changes

既知の後方互換性のない変更はありません。

Future Scope

PHP7.4以降、nullに配列アクセスnull["foo"]するとE_NOTICEが発生します。
そのため、演算子?[]も許可し、$foo?["foo"]と書けるようになると有用かもしれません。
しかし三項演算子$foo?["foo"]:["bar"]と曖昧になってしまうため、このRFCには配列へのnull安全オペレータは含まれていません。

投票

期間は2020/07/31まで、投票者の2/3の賛成で受理されます。
2020/07/20時点では賛成44反対2で、まず確実に導入されます。

感想

正直null安全があまり理解できてないので、このRFCがどのくらい有用なのかもよくわかっていません。
個人的には、メソッドチェーンが書きやすくなるくらいしかメリットを感じられていないです。
そもそもPHPでは元々型がアバウトなうえ、うっかり下手にぬるぽが起こってたとしてもメモリを壊したりなんてことはまずできませんからね。
最悪でもせいぜい500 Internal Server Errorになる程度です。

しかし投票を見るに、union型str_containsを遥かに超え、JITmatch式に匹敵するほどの圧倒的賛成多数です。
それだけ有用であると皆に判断されたということで間違いないでしょう。
PHPにおけるnull安全の優秀さはきっと教えてくれるはず。

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

PHPで性格診断アプリを作ってみた

性格診断アプリ

前書き

こんにちは。業務としてWebAppエンジニアリングをかじっている者です。今回はXAMPPで性格診断アプリを作ってみました。今まで自分一人でログイン機能付きのアプリを作ったことがなかったのでいい勉強になりました。XAMPPのインストール手順に関しては別記事をご参照ください。ソースコードはこちらです。

構成

XAMPPをインストールするとC:\xampp\htdocsがドキュメントルートになります。例えばこのhtdocs直下にindex.htmlindex.phpなどのindexという名前のファイルを置いた状態で、ブラウザにhttp://localhostと入力するとローカル環境でAppを起動することができます。(事前にXAMPP Control Panel内のApacheをStartさせておいてください)

htdocs下の構成は以下のようになります。

 C:\xampp\htdocs> ls


    ディレクトリ: C:\xampp\htdocs


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2020/07/13     16:41                css
d-----       2020/06/22     10:47                dashboard
d-----       2020/07/13     12:45                img
d-----       2020/06/22     10:47                webalizer
d-----       2020/06/22     10:47                xampp
-a----       2020/07/14     16:19            152 config.php
-a----       2015/07/17      0:32          30894 favicon.ico
-a----       2020/07/15     22:28           5965 home.html
-a----       2020/07/14     16:14           3241 index.php
-a----       2020/07/15     22:29           1524 login.php
-a----       2020/07/14     19:35            909 logout.php
-a----       2020/07/15     22:29           1971 result-list.php
-a----       2020/07/15     22:30          11434 result.php
-a----       2020/07/15     22:03           2174 signUp.php
C:\xampp\htdocs\css> ls


    ディレクトリ: C:\xampp\htdocs\css


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2020/07/15     13:44           1394 style.css

データベースのテーブル構成については以下のようになります。

dbname=pd_login
テーブル: userdata
email     varchar(255)    utf8mb4_unicode_ci unique key
password    varchar(255)    utf8mb4_unicode_ci

テーブル: diagnosis-result
email   varchar(255)    utf8mb4_unicode_ci
execution_time  datetime
friendly    int(11)
extrovert   int(11)
emotional   int(11)
positive    int(11)
leader      int(11)

ログイン

htdocs直下にindex.htmlindex.phpなどのindexという名前のファイルを置いた状態で、ブラウザにhttp://localhostと入力するとindex.phpが表示されます。これがログイン機能の大元になるファイルです。新規登録機能と通常ログイン機能を持たせています。新規登録処理について、メールアドレスとパスワードにはフィルターをかけている。パスワードに関しては正規表現で適切でないものをはじいています。メールアドレスに関してはfilter_varを利用して、RFC822で判定しています。RFC822は判定がガバガバ説があるので、ビジネスで使うときは注意です。パスワードはセキュアにデータベースに保存するためpassword_hash()関数を使ってhash化しています。

if (!$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
  echo '入力された値が不正です。';
  return false;
}
if (preg_match('/\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}+\z/i', $_POST['password'])) {
  $password = password_hash($_POST['password'], PASSWORD_DEFAULT);
} else {
  echo 'パスワードは半角英数字をそれぞれ1文字以上含んだ8文字以上で設定してください。';
  return false;
}

通常ログイン機能では以下のようにパスワードを処理します。登録時にhash化したパスワードをpassword_verify()で照合しています。

# login.php
if (password_verify($_POST['password'], $row['password'])) {
  session_regenerate_id(true); //session_idを新しく生成し、置き換える
  $_SESSION['EMAIL'] = $row['email'];
  echo 'ログインしました';
  session_write_close();
  header( "Location: ./home.php" ) ;
  exit();

} else {
  echo 'メールアドレス又はパスワードが間違っています。';
  return false;
}

性格診断

index.php内のinputタグからemailpasswordをpostするとそれをlogin.phpがデータを受け取り、本人確認が済んだらhome.htmlに遷移します。以下がhome.htmlの質問文の箇所です。valueには質問ごとの性格要素の値が入っています。

<form method="post" action="result.php">
    <h2>第一問</h2>
    <p>あなたは外で遊ぶより、家で過ごすほうが好きですか?</p>
    <input type="radio" name="q1" value="1"> かなりそう思う<br>
    <input type="radio" name="q1" value="2"> そう思う<br>
    <input type="radio" name="q1" value="3"> どちらとも言える<br>
    <input type="radio" name="q1" value="4"> あまりそう思わない<br>
    <input type="radio" name="q1" value="5" required> そう思わない<br>

診断結果

home.htmlで入力した値はresult.phpにpostされます。以下はvalueごとの性格要素の値を集計して、4分岐でユーザの性格を判定している箇所です。この例だと質問2と質問6はユーザの興味の対象に関する要素を決定する項目です。この2つの質問のvalueを合算してその合計でユーザの性格を4つに分類しています。

# result.php
if($q2+$q6>8){
              $hito_mono = "非常にモノよりヒト";
              $hito_mono_comment = "あなたは特にこの傾向が強く、ヒトと関わっている時間を楽しみに感じています。
              仕事を選ぶ際にもなるべくヒトと関わる仕事を選ぶとよいでしょう。";
            }elseif($q2+$q6>5){
              $hito_mono = "どちらかといえば、モノよりヒト";
              $hito_mono_comment = "あなたはヒトと関わる時間に楽しみを感じる一方で、モノにも興味をもてます。
              何か自分の興味のあるモノやコトに打ち込みながら、それをヒトとシェアできるバランスの良さをもっています。";
            }elseif($q2+$q6>3){
              $hito_mono = "どちらかといえば、ヒトよりモノ";
              $hito_mono_comment = "あなたはモノやコトに打ち込んでいる瞬間に楽しみを感じる一方で、他人にも興味をもてます。
              何か自分の興味のあるモノやコトに打ち込みながら、それをヒトとシェアできるバランスの良さをもっています。";
            }else{
              $hito_mono = "非常にヒトよりモノ";
              $hito_mono_comment = "あなたは特にこの傾向が強く、モノやコトに打ち込んでいる瞬間を楽しみに感じています。
              仕事を選ぶ際にもなるべく一人で集中できる仕事を選ぶとよいでしょう。";
            }

判定結果の履歴

home.htmlに以下のボタンを設置しています。

<button class="fixed_btn" onclick="location.href='./result-list.php'">過去の診断結果</button>

実はresult.php内でデータベースにそれぞれの結果を保存しています。home.htmlresult-list.phpに遷移した後、このデータベースに接続します。result-list.phpでは以下のような処理を行っており、ログインしているユーザのemailカラムを参考に、データベース内の全データを取得して表として出力しています。

# result-list.php
function createHtmlTable($result) {

    $html = "<table border='3' cellspacing='4' cellpadding='4'>";

    $ffields = $result->fetch_fields();
    $html .= "<tr>";
    foreach ($ffields as $val) {
        $html .= "<th>" . $val->name . "</th>";
    }
    $html .= "</tr>";

    foreach ($result as $row) {
        $html .= "<tr>";
        foreach ($ffields as $val) {
            $value = $row[$val->name];
            $html .= "<td>${value}</td>";
        }
        $html .= "</tr>";
    }
    $html .= "</table>";

    return $html;
}

Session

ちなみに、ページ遷移してもログイン時のemailを利用できるのはSessionを利用しているからです。login.php内では、以下のような処理をしています。

# login.php
$_SESSION['EMAIL'] = $row['email'];

遷移した後のページでこの$_SESSION['EMAIL']を使うときは、以下のような記述をする必要があります。

  <?php
      session_start();

ハマったこと

データベースの接続

文字列の変数はちゃんと'$hoge'などシングルクオーテーションマーク(あるいはダブルクォーテーションマーク)で囲む必要があります。

# result.php
$pdo = new PDO('mysql:host=localhost;dbname=pd_login;charset=utf8mb4','root','');
            $sql = "INSERT INTO `diagnosis-result`(`email`, `execution_time`, `friendly`, `extrovert`, `emotional`, `positive`, `leader`) VALUES ('$user_email','$current_time',$q2+$q6,$q1+$q7,$q3+$q8,$q5+$q9,$q4+$q10)";
            $qry = $pdo->prepare($sql);
            $qry->execute();
            $pdo = null;

データベースのデータ型

パスワードをhash化していますが、長さには余裕をもたせる必要があります。私はvarchar(16)とやってしばらくハマっていました。varchar(255)にしたら通りました。

参考

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

【PHP】Phan の静的コード解析で use 指定したクラスオブジェクトを認識してくれない

Phan から PhanUndeclaredTypeProperty やら PhanUndeclaredMethod やら PhanUndeclaredClassMethod やら PhanUndeclaredFunction と叱られる

助っ人メンバーを入れたのに、一部の熱狂的なファンが認めてくれず活動できません。

sample.php
<?php

declare(strict_types=1);

namespace MYNAME\MYPACKAGE;

use Symfony\Component\Cache\Adapter\FilesystemAdapter; //助っ人。でも Phan からは「こんなやつ、聞いとらん」と怒られる

class MyClass
{
    /** @var FilesystemAdapter */ //← Phan から「こんなやつ、しらん」と怒られる
    protected $my_property;

    public function __construct()
    {
        $this->my_property = new FilesystemAdapter();
    }

    public function set(string $key, $value)
    {
        $cache_item = $this->cache_pool->getItem($key); //← Phan から「そんなん、できん」と叱られる

        $cache_item->set($value);              //← Phan から「なんやそれ」と叱られる
        $this->cache_pool->save($cache_item);  //← Phan から「出なおしてこい」と叱られる
    }
}
エラー内容
PhanUndeclaredTypeProperty Property \MYNAME\MYPACKAGE\MyClass->my_property has undeclared type \Symfony\Component\Cache\Adapter\FilesystemAdapter

FilesystemAdapter オブジェクトのメソッド、例えば getItem() を使っても、「そんなメンバー(メソッド)しらんし、聞いとらん」と叱られます。use で告知したよ?

エラー内容
PhanUndeclaredMethod Call to undeclared method \MYNAME\MYPACKAGE\MyClass->my_property->getItem($key)

TS; DR

Phan が vendor ディレクトリなどにある依存パッケージ(ライブラリ)を読み込んでから静的解析をしていないことが原因です。

各メソッドの PHPDoc コメントに @suppress PhanUndeclaredClassMethod を追加してもいいのですが、config.php ファイルを作成して、対象ディレクトリに vendor を追加しつつ、解析対象対象から外すと、パッケージがインポートされるようになります。

phan.php
<?php
return [
    'directory_list' => [
        'src',
        'vendor/symfony', // use で使うパッケージを追加
    ],
    'exclude_analysis_directory_list' => [
        'vendor/' // 解析対象から除外
    ],
];
解析の実行例
$ # --allow-polyfill-parser  → php-ast がなくても解析する設定
$ # --config-file ./phan.php → 設定ファイルを指定
$ ./vendor/bin/phan --allow-polyfill-parser --config-file ./phan.php
...

TL; DR

作曲家(composer)の言うことなんか聞かない Phan

PHP の静的解析ツール「Phan」は静的解析時、composer などの autoloader によるダイナミックなインポート先を参照しません

そのため、use で宣言しても解析時に必要なパッケージ/ライブラリがインポートされていないため解析エラーが出ます。

suppress コメント(エラー無視コメント)の「@suppress PhanUndeclaredClassMethod」を PHPDoc のブロックコメントに入れて黙殺してもいいのですが、やはり Phan の意見は尊重したいものです。

色々ググってみたのですが、Phan の公式 Wiki の FAQ にちゃんと載ってました。

Phan says that a composer dependency of my project(a class, constant, or function) is undeclared

  • You must add the directory (or file) of that dependency to 'directory_list' or 'file_list' in .phan/config.php. Usually, you will want to add 'vendor' to 'exclude_analysis_directory_list' as well. See https://github.com/phan/phan/wiki/Getting-Started#creating-a-config-file
  • Make sure that the file exists in vendor/ (e.g. make sure that composer.phar install was executed if this is running in CI)
  • Make sure that there are no typos in the variable name, that the namespace of the class is correct, and that the file containing the class/constant/function exists.

This is a common cause of PhanUndeclaredClassMethod, PhanUndeclaredClass, PhanUndeclaredFunction, etc.
Frequently Asked Questions | Phan 公式 Wiki @ GitHub より)

(筆者適当訳)
作曲家による(クラス、定数、または関数)の依存関係に対し、熱狂的なファンが「宣言していない」と怒っています。

  • 依存関係のディレクトリ(またはファイル)を .phan/config.php'directory_list' もしくは 'file_list' に追加する必要があります。その場合、一般的に 'vendor' ディレクトリを 'exclude_analysis_directory_list' にも追加します。詳しくは https://github.com/phan/phan/wiki/Getting-Started#creating-a-config-file を参照ください。
  • ファイルが vendor/ 以下に存在することを確認してください。(たとえば CI で解析を実行する場合に「あらかじめ composer.phar install が実行されているか」などです)
  • 変数名にタイプミスがないこと、クラスの名前空間が正しいこと、およびクラス/定数/関数を含むファイルが存在することを確認してください。

これは PhanUndeclaredClassMethodPhanUndeclaredClassPhanUndeclaredFunction などが発生する「あるある」な原因です。

とほほ。

参考文献

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

AWS Amazon linux 2でLaravel7の環境構築

新たにLaravel7でプロジェクトを作成する事になったため、今回必要な環境の構築覚書。

php :php 7.3
フレームワーク :Laravel 7
データベース  :MariaDB 10.5.4
テンプレートエンジン:twig
ログイン管理 :sentinel 4.x

以前素のphpで書かれていたプロジェクトの改修なのでテンプレートエンジンとログイン管理は該当プロジェクトと同じものを使用する事に。

EC2立ち上げからphp,apache,Composer,Laravelのインストールまで

AmazonLinux2でLaravelの開発環境構築
上記ページを参考に行う。

MariaDBのインストールと設定

 yumデフォルトでインストールできるmaria-dbはバージョン5.5の為、レポジトリを設定してダウンロードする。
 インストール時は MariaDB-server の綴りに注意。(MとDBが大文字)

$ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
$ sudo yum install MariaDB-server

データベースの文字列をUTF-8に設定する。/etc/my.cnf.d/server.cnf の [mariadb] セクションに以下の設定を追加。

/etc/my.cnf.d/server.cnf
[mariadb]
character-set-server=utf8

有効化~開始~セキュリティ設定

$ sudo systemctl enable mariadb
$ sudo systemctl start mariadb

$ sudo mysql_secure_installation
Set root password? [Y/n]
Remove anonymous users? [Y/n]
Disallow root login remotely? [Y/n]
Remove test database and access to it? [Y/n] n
Reload privilege tables now? [Y/n]
 ...
Thanks for using MariaDB!

ログイン~文字コード確認

#ログイン
mysql -u root  -p

Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 23
Server version: 10.5.4-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

#文字コード確認
MariaDB [(none)]> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8                       |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

#ログアウト
MariaDB [(none)]> exit
Bye

twigインストール(LaravelでTwigを利用する専用パッケージrcrowe/twigbridgeのインストール)

$ composer require rcrowe/twigbridge

Sentinelインストール

$ composer require caratalyst/sentinel "^4.0"

参考文献

AmazonLinux2でLaravelの開発環境構築
CentOS 7 に MariaDB Community Server 最新版をインストール
EC2(Amazon Linux 2)にmariaDBを導入する
簡単!Laravelでtwigテンプレートエンジンを使う実例

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

Laravel CRUD処理を使った投稿アプリを作成する その5 投稿削除機能

目的

  • アプリを作成する上で基本となるCRUD処理を有したLaravelアプリをチュートリアル的に作成する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • DockerやAWSなどは使用せずにMacのローカルに実施環境と同じLaravel開発環境を構築して実施する。
  • チュートリアルで実際に筆者が作成したソースコードをGitHubにて公開予定である。
  • CRUD処理の作成完了を最短目標にしてバリデーションなどは後々設定することとする。
  • 実施環境と同じ環境がDockerやAWSで用意できるなら都度読み替えていただければ実施が可能だと思う。
  • 公式ドキュメントと一冊の技術書を元に本記事を記載する。

この記事の読後感

  • 投稿内容の削除機能が実装できる。

全ての記事(miriwo_laravelチュートリアル)を通した読後感

  • Laravelアプリでログインなどのユーザ認証付き投稿アプリの作成ができる。

概要

  1. ルーティングの記載
  2. コントローラの記載
  3. ビューファイルの修正
  4. 確認

詳細

  1. ルーティングの記載

    1. laravel_crudディレクトリで下記コマンドを実行してルーティングファイルを開く。

      $ vi routes/web.php 
      
    2. 開いたファイルに下記の行を追記する。

      laravel_crud/routes/web.php
      Route::post('/delete', 'ContentController@delete')->name('delete');
      
  2. コントローラの記載

    1. laravel_crudディレクトリで下記コマンドを実行して作成したコントローラファイルを開く。

      $ vi app/Http/Controllers/ContentController.php
      
    2. 下記の内容をクラス内に追記する。

      laravel_crud/app/Http/Controllers/ContentController.php
      public function delete(Request $request)
      {
          $contents_delete_query = Content::select('*');
          $contents_delete_query->where('id', $request['content_id']);
          $contents_delete_query->delete();
      
          return redirect('/output');
      }
      
    3. 記載後のコントローラファイルの内容を下記に記載する。

      laravel_crud/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Models\Content;
      
      class ContentController extends Controller
      {
          public function input()
          {
              return view('contents.input');
          }
      
          public function save(Request $request)
          {
              $input_content = new Content();
              $input_content->content = $request['content'];
              $input_content->save();
      
              return redirect('/output');
          }
      
          public function output()
          {
              $contents_get_query = Content::select('*');
              $all_contents = $contents_get_query->get();
      
              return view('contents.output', [
                  'all_contents' => $all_contents,
              ]);
          }
      
          public function delete(Request $request)
          {
              $contents_delete_query = Content::select('*');
              $contents_delete_query->where('id', $request['content_id']);
              $contents_delete_query->delete();
      
              return redirect('/output');
          }
      }
      
  3. ビューファイルの修正

    1. laravel_crudディレクトリで下記コマンドを実行してビューファイルを開く。

      vi resources/views/contents/output.blade.php
      
    2. 開いたビューファイルに下記の内容を追記する。

      laravel_crud/resources/views/contents/output.blade.php
      <h1>output</h1>
      
      @foreach ($all_contents as $item)
          <hr>
          <p>{{$item['content']}}</p>
          <!-- 下記を追記 -->
          <form action="{{route('delete')}}" method="post">
              @csrf
              <input type="hidden" name="content_id" value="{{$item['id']}}">
              <input type="submit" value="削除">
          </form>
      @endforeach
      
  4. 確認

    1. laravel_crudディレクトリで下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve
      
    2. ブラウザで下記にアクセスする。

    3. 「削除」ボタンをクリックして投稿内容が削除されることを確認する。

      127_0_0_1_8000_output.png

    4. 下記の様に投稿内容が削除されれば本記事の内容は完了である。

      127_0_0_1_8000_output.png

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

Laravel学習用備忘録 その2

雑多にLaravelの作成に関し、綴る。

CSSの適用

  • プロジェクト直下のpublicにCSSフォルダを作成し、style.cssファイルを作成
  • viewファイルのhead内に<link rel="stylesheet" href="{{ asset('css/style.css') }}">を記述し、読み込ませる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録です】【PHP】DB操作

備忘録です

DB操作のイメージを掴むために残します。

phpMyAdminを使ってWebブラウザ上からDB操作を行ってきましたが、コードを書いてDB操作を行うこともできます。

sample.php


<pre>
<?php
$host = 'localhost'; // データベースのホスト名又はIPアドレス ※CodeCampでは「localhost」で接続できます
$username = 'username';  // MySQLのユーザ名
$passwd   = 'passwd';    // MySQLのパスワード
$dbname   = 'dbname';    // データベース名
$link = mysqli_connect($host, $username, $passwd, $dbname);
// 接続成功した場合
if ($link) {
   // 文字化け防止
   mysqli_set_charset($link, 'utf8');
   $query = 'SELECT goods_id, goods_name, price FROM goods_table';
   // クエリを実行します
   $result = mysqli_query($link, $query);
   // 1行ずつ結果を配列で取得します
   while ($row = mysqli_fetch_array($result)) {
       print $row['goods_id'];
       print $row['goods_name'];
       print $row['price'];
       print "\n";
   }
   // 結果セットを開放します
   mysqli_free_result($result);
   // 接続を閉じます
   mysqli_close($link);
// 接続失敗した場合
} else {
   print 'DB接続失敗';
}
?>
</pre>

クエリ・・・DBに対する命令文のことです。
mysqli_set_charset・・・クライアントのデフォルト文字セットを設定しています。
mysqli_query・・・データベース上でクエリを実行します。
mysqli_fetch_array・・・結果の行を連想配列・数値添字配列あるいはその両方で取得します。
mysqli_free_result・・・結果に関連付けられたメモリを解放します。

上記のコードでは
1.DB接続
2.文字コード設定
3.クエリ実行
4.結果を取得
5.メモリ解放
6.DB切断

という処理を行っており、
DBに接続してSELECT文を実行し全レコードを表示ということをしています。phpMyAdminでは裏で自動でやってくれていましたが、今回の場合は書く必要があります。

DB接続

sample.php
mysqli_connect($host, $username, $passwd, $dbname)

$host
   データベースのホスト名又はIPアドレス
$username
   MySQLのユーザ名
$passwd
   MySQLのパスワード
$dbname
   データベース名

//機能
   //データベースへ接続
//返り値
   //リンクID(サーバへの接続を表すオブジェクト)
   //失敗するとFALSEを返す

mysqli_connect()にデータべースの情報を渡すことで、接続ができます。
返り値はリンクIDで、「接続したデータベースを特定するためのID」のようなものです。
DB接続は、phpMyAdminにおけるログインと同じ動作になります。

sample.php
$link = mysqli_connect($host, $username, $passwd, $dbname);
// 接続成功した場合
if ($link) {
   // 文字化け防止
   mysqli_set_charset($link, 'utf8');
   $query = 'SELECT goods_id, goods_name, price FROM goods_table';
   // クエリを実行します
   $result = mysqli_query($link, $query);

上記のif文で接続に成功したか失敗したかの判定を行っています。
もしDB接続に失敗しても処理が止まるわけではなく、ソースコード従って以降も順に実行されていきます。

DB接続に失敗した後に、クエリー実行(mysqli_query)やDB切断(mysqli_cliose)などを行っても必ず失敗するため、無駄な処理となってしまいます。

そのため、if文を用いて以降の処理を行うかどうかを決定しています。

クエリ実行

クエリ実行は以下のように行れます。

sample.php
mysqli_query($link, $query)

$link
   リンクID
$query
   実行するクエリ

//機能
   //クエリを実行する
//返り値
   //成功すると結果セット、もしくはTRUEを返す
   //失敗するとFALSEを返す

mysqli_query()は、実行する命令によって成功時の返り値が変わります。

SELECT文を発行した場合は、結果セットが返りますが、INSERET文やUPDATE文、 DELETE文の場合はTRUEが返ります。

結果セットとは、「SELECTにより選択されたデータのかたまり」のようなものです。

phpMyAdminではSELECT文を入力し実行すれば結果を表示まで自動でやってくれましたが、プログラムの場合は実行結果の何の情報をどのように取得するか、別途指定する必要があります。

結果を取得

実行したクエリの結果取得は、以下のように行います。

sample.php
mysqli_fetch_array($result)

$result 
   結果セット

//機能
   //結果セットを配列として返す

//返り値
   //成功すると行の配列を返す
   //読み込むデータがなくなるとNULLを返す
   //失敗するとFALSEを返す

fetchとは「取ってくる」という意味で、SELECTした結果を1行だけ配列として取得します。
取得は1行だけなので、全てのファイルを読み込むにはwhile文と組み合わせて使います。
phpMyAdminでは裏で自動的にやってくれていた処理ですが、プログラムを書く場合、SELECT文はmysqli_query()mysqli_fetch_array()がセットになります。

メモリ開放

sample.php
mysqli_free_result($result)

$result
   結果セット

//機能
   //結果に関連付けられたメモリを開放する

//返り値
   //値を返さない

メモリとは、コンピュータ内でデータやプログラムを記憶する装置です。
メモリはデータの保存や取り出しが高速で行え、一時的なデータの保存場所として利用します。
またメモリには容量があり、容量を越えると急激に処理が遅くなります。
結果セットはこのメモリに保存されており、メモリ開放をしなくても現時点では問題はないですが、
後々のためmysqli_query()で結果セットを取得した場合、利用し終わったらすぐにmysqli_free_result()で開放しましょう。

DB切断

DB切断は以下のように行います。

sample.php

mysqli_close($link)

   $link: リンクID

//機能
    //データベースから切断する

//返り値
   //成功するとTRUEを返す
   //失敗するとFALSEを返す

ファイル操作と同様、データベースに接続している時間は可能な限り短いほうがいいためSELECTや INSERTなどの必要な処理が終わったらすぐに切断します。

DB切断は、phpMyAdminにおけるログアウトと同じ動作となります。

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