20200213のPHPに関する記事は18件です。

Moodle 3.8 マニュアル - サーチエンジン最適化

原文

サーチエンジン最適化

このページはアップデートが必要です。そうしてそれが終了したらこのテンプレートを削除してください。

サーチエンジン最適化(SEO)は限定的ですがサーチエンジンの可視性と順位を向上させるためにはいくつかのステップがあります。

  • サイト管理 > セキュリティ > サイトセキュリティ設定 で「Googleに公開する」を有効にする

  • きれいで明白なシンプルなサイト名をつける。例えば、[組織名]E-Learning のように。

  • サイトの記述を設定する(メタデータとして多くのテーマにおいて使用されます)

  • コースの記述を設定する(フロントページにコースのリンクも設定する。そうでなければ記述はクロールされません。)

SEO にはその他の要因、コンテンツの量、ウェブサイトの通信量や信頼性が作用しています。要するに、多くの関連のある内容と通信量です。

Moodle の設定

サイトセキュリティ設定において「Googleに公開する」設定を 'Yes' にすると Google があなたのサイトにゲストで入れるようになります。加えて、Google サーチを通じてあなたのサイトに来た人々は自動的にゲストログインされます。しかしながら、人びとはコンテンツをあなたのサイトを訪れることなく Google のサイトから読むことができるのみです。

Google があなたのウェブサイトのとなりに表示するテキストはメタ記述のタグとなります。あなたはこの情報を Moodle の 管理 > サイト設定の領域において埋めることができます。

他のサイトから/へのリンク

SEO に主に貢献するのはリンクづけです。評判のよいサイトにリンクすることであなたの順位は上がりますがもっと重要なことに、他のサイトがあなたにリンクすることであなたの順位は上がります。

他のサイトがあなたへリンクすることは大変重要ですが Google は賢いので 30 のサイトを作成してあなたのサイトへリンクするということはできません。Google への評判は重要であり小さいサイトから 100 のリンクは何の評判にもならず例えば www.time.com からの一つのリンクのようにはカウントされません。

その他の方法

もちろん、標準的なことが当てはまります。

  • あなたの内容を興味深いものにする
  • 関連のあるサイトへのリンクと関連のあるサイトからあなたのサイト(理由のある)へのリンク
  • サイトがアクセス可能であるようにすること(Google は JavaScript をオフにしたウェブサーフと同じようなもので見ているので)
  • あなたの分野におけるブロガーがあなたのサイトの話をするようにすること(現在のところこれが最もよい方法です)

カテゴリ:requiring updating(翻訳準備中)|管理者
メインページ

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

Moodle 3.8 マニュアル - テーマディレクトリ

原文

テーマディレクトリ

標準的な Moodle ディストリビューションでは、すべてのテーマは theme/ ディレクトリに配置されます。

典型的なテーマディレクトリ/フォルダの内容のさらなる情報に関してはTheme basics(翻訳準備中)に見つけられます。

サイトのためにテーマのデフォルトディレクトリを変更する

$CFG->themedir を使うことにより、テーマのストレージの場所は変わるかもしれません。この変数により特定されるディレクトリの中に置かれるテーマはテーマセレクタを使用することにより選択できます。

For example, should you wish to place themes in a subdirectory called 'my_moodle_themes', your 'config.php' file might look like this:
例えば、あなたは 'my_moodle_themes' と呼ばれるサブディレクトリにテーマを配置したいとすれば 'config.php' は次のようになるでしょう。

$CFG->wwwroot   = 'http://my.moodle.site.edu';
$CFG->dirroot   = '/var/www/my.moodle.site.edu/public_html';
$CFG->themedir  = $CFG->dirroot . '/my_moodle_themes';

あるいは web サーバによって読むことが可能な設定にするのならば、例えば、

$CFG->wwwroot   = 'http://my.moodle.site.edu';
$CFG->dirroot   = '/var/www/my.moodle.site.edu/public_html';
$CFG->themedir  = '/home/me/my_moodle_themes';

開発者のためのテーマディレクトリ

開発者は テーマディレクトリガイド(翻訳準備中)をフォローすべきです。

関連項目

カテゴリ:テーマ(翻訳準備中)|管理者
メインページ

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

Moodle 3.8 マニュアル - DNS

原文

DNS

これはインターネットがアルファベットのURL(例えば、www.bbc.co.uk)を IP アドレス(例えば 241.1.23.7)に解決するシステムについて言及しています。それは効率的なインターネットの電話帳でありブラウザが必要とされた web ページをホストするマシンに接続できる方法です。

あなたの Moodle サイトが外の世界から接続可能であることが重要ですので、あなたのサーバの DNS エントリが重要です。ISP からの基本的なホスティングのプランを使用しているのなら、とても簡単なことです。

  • 登録者からドメイン名を得てください(例えば、www.123-reg.co.jp)。これは電話帳に登録することと等価です。
  • ネームサーバにあなたの ISP の登録者の設定をしてください。(これはもしあなたの ISP と登録者が同じならば不必要です。)これはあなたの電話帳に電話番号を書き込むことと等価です。

カテゴリ:管理者
メインページ

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

【Laravel】マルチログイン(ユーザーと管理者)機能

Laravel6で認証をユーザーと管理者など複数にわける場合のメモ。

まずは普通にログイン機能を実装

Laravel 6.0 ログイン機能を実装する - Qiita

Laravel6.0「make:Auth」が無くなった 〜Laravel6.0でのLogin機能の実装方法〜MyMemo - Qiita

認証 6.x Laravel

ここは参考になる記事が多くあるので説明は省略。

作成したログイン機能に追加

新しく管理者(Admin)の認証機能を追加する。

1. モデルクラスを作成

他のモデルクラスと同じようにマイグレーションファイル(DB)とモデルクラス(Admin.php)を作成。

モデルクラスはIlluminate\Database\Eloquent\ModelではなくIlluminate\Foundation\Auth\Userを継承する。

Admin.php
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable // ModelではなくAuthenticatable 
{
}

2. 認証ファイルに追記

下記の内容をconfig/auth.phpに追記する。

auth.php
'guards' => [
...
        'admin' => [
            'driver' => 'session',
            'provider' => 'admin',
        ],
...
],

'providers' => [
...
        'admin' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class, // 1 で作成したモデルを指定
        ],
...
],

'passwords' => [
...
        'admin' => [
            'provider' => 'admin',
            'table' => 'password_resets',
            'expire' => 60,
        ],
...
],

3. Routeに認証機能を追加

web.php
Route::group(['middleware' => 'auth:admin'], function () {
...
});

4. ログインしていない場合のリダイレクト先を指定

ログイン処理が必要なURLにログインせずにアクセスした場合のリダイレクト先を
ユーザー、管理者等で分けたい場合は記述する。

app/Exceptions/Handler.php
   protected function unauthenticated($request, AuthenticationException $exception)
    {
        if($request->expectsJson()){
            return response()->json(['message' => $exception->getMessage()], 401);
        }

        if (in_array('admin', $exception->guards(), true)) {
            return redirect()->guest(route('admin.login'));
        }

        return redirect()->guest(route('login'));
    }

5. ログイン後にログインページにアクセスした場合のリダイレクト先を指定

app/Http/Middleware/RedirectIfAuthenticated
    public function handle($request, Closure $next, $guard = null)
    {
        switch ($guard) {
            case 'admin':
                $redirectPath = '/admin/index';
                break;
            default:
                $redirectPath = '/index';
                break;
        }
        if (Auth::guard($guard)->check()) {
            return redirect($redirectPath);
        }

        return $next($request);
    }

6. ログインコントローラー作成

ユーザーの認証に使用しているLoginControllerとは別に作成する。

LoginController
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = 'admin/index';  // ログイン後のリダイレクト先

    public function __construct(Request $request)
    {
        $this->middleware('guest:admin')->except('logout');;
    }

    protected function guard()
    {
        return Auth::guard('admin');
    }

}

その他username, showLoginFormなどのメソッドはユーザーのLoginControllerと同じように、必要に応じて設定する。

参考

Laravelでマルチ認証(マルチログイン)を実装する

【Laravel】マルチログイン(ユーザーと管理者など)機能を設定してみた【体験談】 | もんプロ~問題発見と解決のためのプログラミング〜

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

textareaでPOSTされたtsvフォーマットのデータを受け取ってパースするメモ

テンポラリファイル作ってうまいことやる。

$temp = tmpfile();
fwrite($temp, $_POST['text']);
rewind($temp);
$meta = stream_get_meta_data($temp);

$files = new SplFileObject($meta['uri']);
$files->setFlags(
    \SplFileObject::READ_CSV |
    \SplFileObject::READ_AHEAD |
    \SplFileObject::SKIP_EMPTY |
    \SplFileObject::DROP_NEW_LINE
);
$files->setCsvControl("\t");

foreach($files as $key => $line) {
    //ここでなんやかんや
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XAMPPでApache起動出来ずにハマった話

※windows10、xamppのバージョン7.4.1で環境を作ってます。

WordPressの環境をローカルで作成しようと思いxamppをダウンロードしてApacheを起動したが動かない・・・。
error.logを確認すると以下エラーが。

PHP Warning: 'vcruntime140.dll' 14.0 is not compatible with this PHP build linked with 14.16 in Unknown on line 0

翻訳してみると、vcruntime140.dllがPHPと互換性がないと言われているように見える。

vcruntime140.dllって何?

色々調べてみると、
https://www.php.net/manual/ja/install.windows.requirements.php

PHPを動かすためにはVisualCのランタイムが必要でvcruntime140.dllというのがランタイムにあたるようだが、今入ってるPHPのバージョンと互換性がないよ!と言われているっぽい。

じゃあどうすれば良いの?

以下ページからVisualStudio2019のMicrosoft Visual C++ 再頒布可能パッケージをインストールして再度Apacheを起動したら起動出来た!
https://visualstudio.microsoft.com/ja/downloads/

Apache立ち上げるだけで結構時間がかかってしまった・・・( ;∀;)

エラー内容で調べても出てこなかったので備忘録として。

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

LaravelでSalesForceのREST APIを利用する

概要

LaravelでSalesForceのREST APIを利用する方法について調査したので記事として残しておく。
ここではよく使いそうなCRUD処理のサンプルを記載する。
※ Laravel特有の部分はあまりないので、他のフレームワークや生PHPでもいけます。

前提

CRUD処理サンプル

CREATE

    /**
     * SalesForce REST API Create
     * @Route /sample/sf/api/create
     * @Method POST
     */
    public function sfApiCreateRecord()
    {
        // 環境によって違うので自分の環境の値に変更してください
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account';

        $url   = $sfUrl . $path;
        $token = 'hogefugapiyo'; //tokenを自動で取得してくるような仕組みはこの記事では省略する

        $postData = [
            'Name' => 'nagi125',
            'Description' => 'REST API test',
        ];

        $params = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
            'body' => json_encode($postData),
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('POST', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

READ

    /**
     * SalesForce REST API Read
     * @Route /sample/sf/api/read
     * @Method GET
     */
    public function sfApiReadRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        // このようにSQLの結果も取得できる
        // $path = '/services/data/v47.0/query/?q=SELECT+Id,Name+From+Account';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $params = [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('GET', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

UPDATE

    /**
     * SalesForce REST API Update
     * @Route /sample/sf/api/update
     * @Method PATCH
     */
    public function sfApiUpdateRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $postData = [
            'Name' => 'nagi125_update',
        ];

        $params = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
            'body' => json_encode($postData),
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('PATCH', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

DELETE

    /**
     * SalesForce RestAPI Delete
     * @Route /sample/sf/api/delete
     * @Method DELETE
     */
    public function sfApiDeleteRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $params = [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('DELETE', $url, $params);

        // 結果の確認(deleteの場合、成功時に文字列が返ってこないのでStatusCodeで確認)
        dd($res->getStatusCode());
    }

その他

残りの操作について詳しく知りたい場合はREST APIの仕様書を見るとよいです。

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

PHP(Laravel)でSalesForceのREST APIを利用する

概要

LaravelでSalesForceのRESTAPIを利用する方法について調査したので記事として残しておく。
ここではよく使いそうなCRUD処理のサンプルを記載する。
※ Laravel特有の部分はあまりないので、他のフレームワークや生PHPでもいけます。

前提

CRUD処理サンプル

CREATE

    /**
     * SalesForce REST API Create
     * @Route /sample/sf/api/create
     * @Method POST
     */
    public function sfApiCreateRecord()
    {
        // 環境によって違うので自分の環境の値に変更してください
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account';

        $url   = $sfUrl . $path;
        $token = 'hogefugapiyo'; //tokenを自動で取得してくるような仕組みはこの記事では省略する

        $postData = [
            'Name' => 'nagi125',
            'Description' => 'REST API test',
        ];

        $params = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
            'body' => json_encode($postData),
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('POST', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

READ

    /**
     * SalesForce REST API Read
     * @Route /sample/sf/api/read
     * @Method GET
     */
    public function sfApiReadRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        // このようにSQLの結果も取得できる
        // $path = '/services/data/v47.0/query/?q=SELECT+Id,Name+From+Account';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $params = [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('GET', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

UPDATE

    /**
     * SalesForce REST API Update
     * @Route /sample/sf/api/update
     * @Method PATCH
     */
    public function sfApiUpdateRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $postData = [
            'Name' => 'nagi125_update',
        ];

        $params = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
            'body' => json_encode($postData),
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('PATCH', $url, $params);

        // 結果の確認
        dd($res->getBody()->getContents());
    }

DELETE

    /**
     * SalesForce RestAPI Delete
     * @Route /sample/sf/api/delete
     * @Method DELETE
     */
    public function sfApiDeleteRecord()
    {
        $sfUrl = 'https://hoge.salesforce.com';
        $path  = '/services/data/v47.0/sobjects/account/';
        $sfId  = 'hogefuga';

        $url   = $sfUrl . $path . $sfId;
        $token = 'hogefugapiyo';

        $params = [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
        ];

        $client = new \GuzzleHttp\Client();
        $res = $client->request('DELETE', $url, $params);

        // 結果の確認(deleteの場合、成功時に文字列が返ってこないのでStatusCodeで確認)
        dd($res->getStatusCode());
    }

その他

残りの操作について詳しく知りたい場合はREST APIの仕様書を見るとよいです。

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

SalesForceのaccess_token取得方法

概要

SalesForceのAccessToken取得方法をまとめておく。
※refresh_tokenについてはこちらを参照
同時にLaravel + Guzzuleを利用したPHPのサンプルも載せておく。

結論

取得方法は複数あるがここでは「ユーザ名パスワードフロー」と「更新トークンフロー」の2種類を記載する。
※ 利用しているAPIのバージョンはv47.0です。

共通作業

ユーザー名・パスワードフロー

パラメータをつけて下記のURLに「POST」でアクセスする。
https://login.salesforce.com/services/oauth2/token

パラメータ
grant_type password
client_id *****
client_secret *****
username ****
password ****

01.png

更新トークンフロー

refresh_tokenが必要になるため、refresh_tokenをこちらの記事の方法で取得しておく。
パラメータをつけて下記のURLに「POST」でアクセスする。
https://login.salesforce.com/services/oauth2/token

パラメータ
grant_type refresh_token
client_id *****
client_secret *****
refresh_token *****

02.png

Laravel + Guzzleサンプル

Laravelを利用して書いていますが、機能としてはenvぐらいしか使っていないので生PHPでも普通にいけます。
Guzzleを利用したサンプルと思っていただければと思います。

    private function refreshSFAccessToken(): string
    {
        $path = '/services/oauth2/token';
        $url  = 'https://login.salesforce.com' . $path;

        $params = [
            'form_params' => [
                'grant_type' => 'refresh_token',
                'client_id'  => env('SF_ID'),
                'client_secret' => env('SF_SECRET'),
                'refresh_token' => env('SF_REFRESH_TOKEN'),
            ]
        ];

        $client  = new \GuzzleHttp\Client();
        $resJson = $client->request('POST', $url, $params)->getBody()->getContents();
        $resAry  = json_decode($resJson, true);

        return $resAry['access_token'] ?? '';
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel CSVの出力処理を実装する

概要

Laravelを使って、CSVファイルを出力するサンプルを作成します。

背景

データベースやファイルアクセスをしないテストの方法、インターフェースやジェネレータを使ったコードの書き方等、個別に詳しく書かれた記事はあれど実際使うにはどう書き始めたらいいのかベストプラクティスがわかりませんでした。

日々の業務や副業、勉強会を通じてようやく自分の中で少しずつイメージができてきたので現時点で最高のアウトプットをしていこうと思いました。

目的

この記事ではテストを意識したコードかつ、シンプルに書くことを目標にしてます。
より良いコードにしたいのでアドバイスもらえたらうれしいです。

説明不足なところがあったら補足を追記するので気軽に質問等もいただけたら嬉しいです。

環境

  • PHP 7.4.1
  • Laravel 6.14.0
  • MySQL 8.0.19

サンプルコード

https://github.com/ucan-lab/learn-laravel-export-csv

$ git clone git@github.com:ucan-lab/learn-laravel-export-csv.git
$ cd learn-laravel-export-csv
$ make install
$ make app
$ php artisan migrate:fresh --seed

# csv出力コマンド
$ php artisan export:user

# テスト実行
$ ./vendor/bin/phpunit

今回のゴール

スクリーンショット 2020-02-20 12.18.33.png

users テーブルに入ってるデータをcsv出力する処理を作るところまでゴールとします。

名前,メールアドレス,作成日,更新日
PROF. RACHELLE KUHIC I,leola.rath@example.com,2020-02-01 23:59:59,2020-02-01 23:59:59
JAYLON WOLF,osinski.fernando@example.net,2020-02-01 23:59:59,2020-02-01 23:59:59
LELAND DECKOW,bokon@example.org,2020-02-01 23:59:59,2020-02-01 23:59:59

名前の列は大文字に変換して出力する仕様です。

ベースのコード

環境はこちらのコードを丸コピしてます。

追加したファイル一覧

https://github.com/ucan-lab/learn-laravel-export-csv/pull/1

src/app/Console/Commands/ExportUserCommand.php
src/app/Domain/UserRow.php
src/app/Domain/UserRowHeader.php
src/app/Http/Controllers/Auth/RegisterController.php
src/app/Infrastructure/Adapter/DbUserRepository.php
src/app/Infrastructure/Adapter/FileUserCsvExport.php
src/app/Infrastructure/Adapter/InMemoryUserCsvExport.php
src/app/Infrastructure/Adapter/InMemoryUserRepository.php
src/app/Infrastructure/Eloquent/User.php
src/app/Infrastructure/Port/Export.php
src/app/Infrastructure/Port/UserRepository.php
src/app/Providers/AppServiceProvider.php
src/app/UseCase/UserCsvExportUseCase.php
src/database/factories/UserFactory.php
src/database/seeds/DatabaseSeeder.php
src/database/seeds/UsersTableSeeder.php
src/tests/Unit/UserCsvExportUseCaseTest.php

マイグレーション(テーブル)の確認

今回はLaravelが元々用意してくれている users テーブルをそのまま使います。

src/database/migrations/2014_10_12_000000_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

MySQLのテーブル定義も確認しておきます。

$ make mysql
mysql> desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+

app/User.php => app/Infrastructure/Eloquent/User.php

LaravelのEloquentモデルはデフォルトだとapp直下に配置されます。
app/Infrastructure/Eloquent/User.php へ移動します。
依存するファイルも合わせて修正します。詳細はコミットログ参照

シーダーの作成

Laravelには、シーディングモデルファクトリFakerが用意されており、ダミーデータを簡単に作成できます。

$ php artisan make:seeder UsersTableSeeder

src/database/seeds/UsersTableSeeder.php シーダーのひな形クラスを作ってくれるので下記のように追記します。

src/database/seeds/UsersTableSeeder.php
<?php declare(strict_types=1);

use App\Infrastructure\Eloquent\User;
use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(User::class, 3)->create();
    }
}

上記の用にシーダーを追加するだけで、テストデータを3件作成してくれます。
Userモデルクラスの各プロパティにどんなデータが入るかの定義はモデルファクトリで定義されてます。

src/database/factories/UserFactory.php
<?php declare(strict_types=1);

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Infrastructure\Eloquent\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

元々用意されている src/database/seeds/DatabaseSeeder.phpUsersTableSeeder を呼び出す記述を追記します。

src/database/seeds/DatabaseSeeder.php
<?php declare(strict_types=1);

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
         $this->call(UsersTableSeeder::class);
    }
}

UserRow, UserRowHeader ドメインを定義

この辺りから本題です。

App\Domain\UserRowHeader

app/Domain/UserRowHeader.php
<?php declare(strict_types=1);

namespace App\Domain;

final class UserRowHeader
{
    private const EOF = "\n";
    private const HEADER = [
        '名前',
        'メールアドレス',
        '作成日',
        '更新日',
    ];

    public static function toCsv(): string
    {
        return implode(',', self::HEADER) . self::EOF;
    }
}

UserRowHeader ドメインクラスではCSVのヘッダー行となる1行目の定義をしてます。

App\Domain\UserRow

app/Domain/UserRow.php
<?php declare(strict_types=1);

namespace App\Domain;

use Carbon\Carbon;

final class UserRow
{
    private const EOF = "\n";
    private const DATE_FORMAT = 'Y-m-d H:i:s';

    private string $name;
    private string $email;
    private Carbon $createdAt;
    private Carbon $updatedAt;

    public function __construct(
        string $name,
        string $email,
        Carbon $createdAt,
        Carbon $updatedAt
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->createdAt = $createdAt;
        $this->updatedAt = $updatedAt;
    }

    /**
     * @return string
     */
    public function toCsv(): string
    {
        return implode(',', $this->toArray()) . self::EOF;
    }

    /**
     * @return array
     */
    private function toArray(): array
    {
        return [
            $this->getName(),
            $this->email,
            $this->createdAt->format(self::DATE_FORMAT),
            $this->updatedAt->format(self::DATE_FORMAT),
        ];
    }

    /**
     * @return string
     */
    private function getName(): string
    {
        return strtoupper($this->name);
    }
}

UserRow ドメインクラスではCSVの1行の定義をしてます。
ユーザー名は大文字や日付のフォーマット等の業務ロジックはここにまとめます。

UserCsvExportUseCase を定義

app/Infrastructure/Export.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Port;

interface Export
{
    public function prepare(string $header): void;
    public function write(string $row): void;
    public function disorganize(): void;
}

Export インターフェースを継承するクラスは prepare(前処理)、write(書き込み)、disorganize(後処理)のメソッドを契約します。

app/Infrastructure/UserRepository.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Port;

use Generator;

interface UserRepository
{
    public function findAll(): Generator;
}

UserRepository インターフェースを継承するクラスはfindAll(全件取得)のメソッドを契約します。

app/UseCase/UserCsvExportUseCase.php
<?php declare(strict_types=1);

namespace App\UseCase;

use App\Domain\UserRow;
use App\Domain\UserRowHeader;
use App\Infrastructure\Port\Export;
use App\Infrastructure\Port\UserRepository;

final class UserCsvExportUseCase
{
    /**
     * @var UserRepository
     */
    private UserRepository $repository;

    /**
     * @var Export
     */
    private Export $export;

    /**
     * @param UserRepository $repository
     * @param Export $export
     */
    public function __construct(UserRepository $repository, Export $export)
    {
        $this->repository = $repository;
        $this->export = $export;
    }

    /**
     * @return void
     */
    public function handle(): void
    {
        $this->export->prepare(UserRowHeader::toCsv());

        /** @var UserRow $row */
        foreach ($this->repository->findAll() as $row) {
            $this->export->write($row->toCsv());
        }

        $this->export->disorganize();
    }
}

UserCsvExportUseCase ユースケースクラスはUserRepositoryとExportインターフェースに依存します。
前処理して、全件取得して、書き込みして、後処理して終わるシンプルな作りにできました。
各インターフェースを契約するクラスの中身はあとで書きます。

ユースケースのテストを書く

テストを書く際、データベースやファイルに直接読み書きするようなテストを書いてしまうと
最初は問題ないですが、テストが増えるにつれてテストの実行速度がどんどん落ちてしまいます。

そのため、テストを書く際はデータベースやファイルアクセスが発生しないようメモリ内で良い感じのテストを書きます。

InMemoryUserCsvExport

app/Infrastructure/Adapter/InMemoryUserCsvExport.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Adapter;

use App\Infrastructure\Port\Export;

final class InMemoryUserCsvExport implements Export
{
    /**
     * @var string
     */
    public string $file;

    /**
     * @param string $header
     */
    public function prepare(string $header): void
    {
        $this->file = $header;
    }

    /**
     * @param string $row
     */
    public function write(string $row): void
    {
        $this->file .= $row;
    }

    /**
     * @return void
     */
    public function disorganize(): void
    {
    }
}

Exportインターフェースを契約したInMemoryUserCsvExportクラスを実装します。
やってることは簡単で、prepareメソッドで$fileプロパティに文字列を入れてwriteメソッドが呼ばれたらどんどん追記する形です。
実際にファイルアクセスする場合はdisorganizefclose等の処理を入れますが、ファイルアクセスしないので関数だけ定義してます。

InMemoryUserRepository

app/Infrastructure/Adapter/InMemoryUserRepository.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Adapter;

use App\Domain\UserRow;
use App\Infrastructure\Eloquent\User;
use App\Infrastructure\Port\UserRepository;
use Generator;

final class InMemoryUserRepository implements UserRepository
{
    private array $usersAttributes;

    /**
     * @param array $users
     */
    public function __construct(array $users)
    {
        $this->usersAttributes = $users;
    }

    /**
     * @return Generator
     */
    public function findAll(): Generator
    {
        foreach ($this->usersAttributes as $userAttributes) {
            yield $this->makeUserRow(factory(User::class)->make($userAttributes));
        }
    }

    /**
     * @param User $user
     * @return UserRow
     */
    private function makeUserRow(User $user): UserRow
    {
        return new UserRow(
            $user->name,
            $user->email,
            $user->created_at,
            $user->updated_at
        );
    }
}

補足: ジェネレータ

findAllの戻り値の型としてGeneratorオブジェクトを返すと呼び出した側はforeachを使って順に呼び出すことができます。
returnではなくyieldを指定します。
yieldUserRowのインスタンスを返してます。

// UserCsvExportUseCase で findAll を foreach でループ処理できます。
foreach ($this->repository->findAll() as $row) {
    $this->export->write($row->toCsv());
}

ジェネレータのメリットはforeachでループ処理するために巨大な配列を持つ必要がなく1件処理が終わったらメモリを解放して次の処理を実行してくれるので、バッチ処理等のメモリをたくさん使いそうな場合に効果を発揮します。

UserCsvExportUseCaseTest

先ほど作成したInMemoryUserRepositoryInMemoryUserCsvExportを使ってテストコードを書きます。

tests/Unit/UserCsvExportUseCaseTest.php
<?php declare(strict_types=1);

namespace Tests\Unit;

use App\Infrastructure\Adapter\InMemoryUserCsvExport;
use App\Infrastructure\Adapter\InMemoryUserRepository;
use App\UseCase\UserCsvExportUseCase;
use Tests\TestCase;

final class UserCsvExportUseCaseTest extends TestCase
{
    /**
     * @param array $users
     * @param string $expectedCsv
     * @dataProvider dataResolve
     */
    public function testResolve(array $users, string $expectedCsv): void
    {
        $repository = new InMemoryUserRepository($users);
        $export = new InMemoryUserCsvExport();
        $useCase = new UserCsvExportUseCase($repository, $export);
        $useCase->handle();

        $this->assertEquals($expectedCsv, $export->file);
    }

    /**
     * @return array
     */
    public function dataResolve(): array
    {
        return [
            '正常3件' => $this->case正常3件(),
            '正常0件' => $this->case正常0件(),
        ];
    }

    /**
     * @return array
     */
    public function case正常3件(): array
    {
        $usersAttributes = [
            ['name' => 'yamada', 'email' => 'yamada@example.com', 'created_at' => '2020-01-01 00:00:00', 'updated_at' => '2020-01-01 00:00:00'],
            ['name' => 'suzuki', 'email' => 'suzuki@example.com', 'created_at' => '2020-01-01 00:00:00', 'updated_at' => '2020-01-01 00:00:00'],
            ['name' => 'tanaka', 'email' => 'tanaka@example.com', 'created_at' => '2020-01-01 00:00:00', 'updated_at' => '2020-01-01 00:00:00'],
        ];

        $expectedCsv = <<< EOT
        名前,メールアドレス,作成日,更新日
        YAMADA,yamada@example.com,2020-01-01 00:00:00,2020-01-01 00:00:00
        SUZUKI,suzuki@example.com,2020-01-01 00:00:00,2020-01-01 00:00:00
        TANAKA,tanaka@example.com,2020-01-01 00:00:00,2020-01-01 00:00:00

        EOT;

        return [
            $usersAttributes,
            $expectedCsv,
        ];
    }

    /**
     * @return array
     */
    public function case正常0件(): array
    {
        $usersAttributes = [];

        $expectedCsv = <<< EOT
        名前,メールアドレス,作成日,更新日

        EOT;

        return [
            $usersAttributes,
            $expectedCsv,
        ];
    }
}

想定している $expectedCsv の値とユースケースを実行して作成された値 $export->file が一致すればokです。

補足: dataProvider

PHPUnitのdataProviderについて補足です。
PHPUnitを実行する際に --debug オプションを付けると詳細ログが見れます。
dataProvider で作った引数もログに出てくるので分かりやすくなります。

データプロバイダ | phpunit.readthedocs.io

$ ./vendor/bin/phpunit --debug
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

Test 'Tests\Unit\ExampleTest::testBasicTest' started
Test 'Tests\Unit\ExampleTest::testBasicTest' ended
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常3件" (array(array('yamada', 'yamada@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('suzuki', 'suzuki@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('tanaka', 'tanaka@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00')), '名前,メールアドレス,作成日,更新日\nYAMADA,ya...0:00\n')' started
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常3件" (array(array('yamada', 'yamada@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('suzuki', 'suzuki@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00'), array('tanaka', 'tanaka@example.com', '2020-01-01 00:00:00', '2020-01-01 00:00:00')), '名前,メールアドレス,作成日,更新日\nYAMADA,ya...0:00\n')' ended
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常0件" (array(), '名前,メールアドレス,作成日,更新日\n')' started
Test 'Tests\Unit\UserCsvExportUseCaseTest::testResolve with data set "正常0件" (array(), '名前,メールアドレス,作成日,更新日\n')' ended
Test 'Tests\Feature\ExampleTest::testBasicTest' started
Test 'Tests\Feature\ExampleTest::testBasicTest' ended


Time: 3.04 seconds, Memory: 20.00 MB

OK (4 tests, 4 assertions)

CSVの出力処理を実装する

テストコードが書けたところで、実際にデータベースから取得してCSVファイルを出力する処理を実装します。

DbUserRepository

app/Infrastructure/Adapter/DbUserRepository.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Adapter;

use App\Domain\UserRow;
use App\Infrastructure\Eloquent\User;
use App\Infrastructure\Port\UserRepository;
use Generator;

final class DbUserRepository implements UserRepository
{
    /**
     * @return Generator
     */
    public function findAll(): Generator
    {
        /** @var User $user */
        foreach (User::query()->cursor() as $user) {
            yield new UserRow(
                $user->name,
                $user->email,
                $user->created_at,
                $user->updated_at
            );
        }
    }
}

UserRepositoryを契約したDbUserRepositoryクラスです。

補足: User::query()->cursor()

cursor() を使うとPDOStatement::fetch
結果セットから1行ずつ取得できます。 cursor() の返り値もジェネレータオブジェクトになります。

User::query()->cursor() ではなく User::all() も動作するかと思いますが、一度に大量のデータを取得するのでデータ件数によってはメモリオーバーになってしまう懸念があります。

FileUserCsvExport

app/Infrastructure/Adapter/FileUserCsvExport.php
<?php declare(strict_types=1);

namespace App\Infrastructure\Adapter;

use App\Infrastructure\Port\Export;

final class FileUserCsvExport implements Export
{
    /**
     * @var string
     */
    private string $streamFilePath;

    /**
     * @var resource
     */
    private $handle;

    /**
     * @param string $header
     * @return void
     */
    public function prepare(string $header): void
    {
        $this->streamFilePath = $this->makeStreamFile();
        $this->handle = fopen($this->streamFilePath, 'wb+');
        $this->write($header);
    }

    /**
     * @param string $row
     * @return void
     */
    public function write(string $row): void
    {
        fwrite($this->handle, $row);
    }

    /**
     * @return void
     */
    public function disorganize(): void
    {
        fclose($this->handle);

        // 後処理 配置したい場所へコピーする等
        dump(file_get_contents($this->streamFilePath));

        unlink($this->streamFilePath);
    }

    /**
     * @return string
     */
    private function makeStreamFile(): string
    {
        return tempnam(sys_get_temp_dir(), config('app.name'));
    }
}

Exportを契約したFileUserCsvExportクラスです。

ExportUserCommand

app/Console/Commands/ExportUserCommand.php
<?php declare(strict_types=1);

namespace App\Console\Commands;

use App\UseCase\UserCsvExportUseCase;
use Illuminate\Console\Command;

class ExportUserCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'export:user';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'export user data.';

    /**
     * @var UserCsvExportUseCase
     */
    private UserCsvExportUseCase $useCase;

    /**
     * ExportUserCommand constructor.
     * @param UserCsvExportUseCase $useCase
     */
    public function __construct(UserCsvExportUseCase $useCase)
    {
        parent::__construct();

        $this->useCase = $useCase;
    }

    /**
     * @return void
     */
    public function handle(): void
    {
        $this->useCase->handle();
    }
}

LaravelにはArtisanコンソールというコマンドラインインターフェイスが用意されてます。
コマンドクラスを作るだけで簡単に自作コマンドを追加できます。

ExportUserCommandでやってることは、UserCsvExportUseCaseのインスタンスを受け取って、handleメソッドを呼び出すだけです。

$ php artisan export:user

上記のコマンドが追加されます。

依存性の注入(DI)

app/Providers/AppServiceProvider.php
<?php declare(strict_types=1);

namespace App\Providers;

use App\Infrastructure\Adapter\DbUserRepository;
use App\Infrastructure\Adapter\FileUserCsvExport;
use App\UseCase\UserCsvExportUseCase;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(UserCsvExportUseCase::class, function ($app) {
            return new UserCsvExportUseCase(new DbUserRepository(), new FileUserCsvExport());
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

UserCsvExportUseCaseクラスをサービスコンテナに登録します。
ここで登録しているので、ExportUserCommandはコンストラクタインジェクションでUserCsvExportUseCaseクラスのインスタンスを受け取れます。

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

selenium php-webdriver update

$composer update 
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Package facebook/webdriver is abandoned, you should avoid using it. Use php-webdriver/webdriver instead.
Generating autoload files

version 1.8.0 から名前が facebook/php-webdriver から php-webdriver/webdriverに変わりました
jsonを書き換えます

composer.json
"require": {
-    "facebook/webdriver": "^1.7.1",
+    "php-webdriver/webdriver": "^1.8.0",
}

アップデートすれば新しいバージョンが入ります

$ composer update 
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 1 update, 1 removal
  - Removing facebook/webdriver (1.7.1)
  - Updating symfony/process (v4.4.4 => v5.0.4): Downloading (100%)         
  - Installing symfony/polyfill-mbstring (v1.13.1): Downloading (100%)         
  - Installing php-webdriver/webdriver (1.8.0): Downloading (100%)         
php-webdriver/webdriver suggests installing ext-SimpleXML (For Firefox profile creation)
Writing lock file
Generating autoload files

github php-webdriver
https://github.com/php-webdriver/php-webdriver

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

?【CakePHP2】Error: Call to a member function dispatchMethod() on nullは環境設定で修正

環境

PHP 7.2.21
CakePHP 2.10.18

やりたいこと

下記のエラーを修正

Error: Call to a member function dispatchMethod() on null
#0 (Modelのファイルパス)(223): Model->__call('makeRoleTargetJ...', Array)
#1 [internal function]: (対象のModel名)->__construct(Array)
#2 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Utility/ClassRegistry.php(169): ReflectionClass->newInstance(Array)
#3 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Console/Shell.php(316): ClassRegistry::init('(対象のModel名)')
#4 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Console/Shell.php(269): Shell->loadModel('(対象のModel名)')
#5 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Console/Shell.php(228): Shell->_loadModels()
#6 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Console/ShellDispatcher.php(221): Shell->initialize()
#7 /(CakePHPディレクトリ)/Vendor/cakephp/cakephp/lib/Cake/Console/ShellDispatcher.php(66): ShellDispatcher->dispatch()
#8 /(CakePHPディレクトリ)/(Projectディレクトリ)/Console/cake.php(48): ShellDispatcher::run(Array)
#9 {main}

やったこと

env.phpの環境変数を確認して修正する
ModelやControllerは悪くなかった

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

PHP 勉強用メモ

クラスの中のプロパティやメソッドにアクセスするときには、 $this

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

PHP オブジェクト指向 勉強用

勉強用メモです。

クラスがもつ変数をプロパティ
クラスがもつ関数をメソッド

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

勉強用メモ ローカル変数 PHP

関数内で定義した変数はその関数内でのみ有効

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

構造化データの読み込みにまつわる問題 〜evalインジェクション〜

XMLやJSONなどの構造を持つデータを保存や送受信できるように変換することをシリアライズと呼ぶ。
シリアライズは配列データを配列データとして保存できたりなど、配列の要素や構造が動的に変化するデータを扱う際などに使用される。
ここではシリアライズに関する脆弱性の一つであるevalインジェクションについて記載する。

概要

プログラム言語にはevalと呼ばれる関数・機能があり、これは文字列(各言語のコード)を式として評価したのものである。
evalは使い方によって外部から送り込んだスクリプトが実行されることがある。
それをevalインジェクション攻撃と言い、そのような攻撃を受ける脆弱性をevalインジェクション脆弱性と呼ぶ。

evalの例

JavaScript(MDN)

eval(string)

PHP(php.net)

eval ( string $code ) : mixed

evalインジェクションによる影響は以下のOSコマンド・インジェクション攻撃と同じ。

  • 情報漏洩
  • サイト改ざん
  • 不正な機能実行
  • 他サイトへの攻撃(踏み台)
  • 暗号通貨の採掘(マイニング)

evalインジェクション脆弱性の対策は以下の通り

  • evalに相当する機能を使わない
  • evalの引数には外部からのパラメータを含めない
  • evalに与える外部からのパラメータを英数字に限定する

攻撃手法と影響

シリアライズしてフォーム間を受け渡す場合の脆弱性について。
例としてPHPのvar_export関数を使ってシリアライズした後にbase64にエンコードしデータを受け渡す。受け渡されたデータのbase64をデコードし、evalを用いて元のデータに戻す(デシリアライズする)とする。
参考イメージ

4e-001.php
<?php
  $a = array(1, 2, 3);
  $ex = var_export($a, true);
  $b64 = base64_encode($ex);
?>
<body>
<form action="4e-002.php" method="GET">
<input type="hidden" name="data" value="<?php echo htmlspecialchars($b64) ?>">
<input type="submit" value="次へ">
</form>
</body>
4e-002.php
<?php
  $data = $_GET['data'];
  $str = base64_decode($data);
  eval('$a = ' . $str . ';');
?>
<body>
<?php var_dump($a); ?>
</body>

攻撃手法

evalに渡す式に任意の文を追加(スクリプトを注入)する。

$a = 式: 任意の文;

$a = 0: phpinfo();

OWASP ZAPで上記を試すとphpinfoが表示され、外部から注入したスクリプトが実行された状態となる。
これらの事例から情報漏洩やサイト改ざんなどの影響を受ける可能性がある。

脆弱性が生まれる原因

evalに与えるパラメータをチェックしていないため。
PHPであれば任意のコードを実行できてしまうようにevalはそもそも危険な言語構造であること。

evalインジェクション脆弱性の原因は以下の通り。

  • evalを用いることがそもそも危険
  • evalに与えるパラメータのチェックがされていない

対策

evalインジェクション脆弱性の対策は以下の通り。

  • eval(同等機能含む)を使わない
  • evalの引数に外部からのパラメータを指定しない
  • evalの与える外部からのパラメータを英数字に制限する

evalを使わない

eval(同等機能含む)を使わず、eval以外の選択肢を検討する。
シリアライズが目的であれば以下の選択肢がある。

シリアライズ以外の目的でもevalは極力使わない実装を検討する。
evalを使わずに同等の処理は実装可能。

evalの引数に外部からのパラメータを指定しな

evalを使った場合でも外部からパラメータを指定しない。
外部からスクリプトを注入できなくすれば安全だが、注入経路がファイルやデータベースの場合この対策は使えない。

evalに与える外部からのパラメータを英数字に制限する

外部から与えるパラメータを英数字に限定できればスクリプト注入に必要な記号文字を使えなくすることができる。
正規表現による入力値検証などを行う。

まとめ

evalを使わない実装を行う。
どうしてもevalを使わなければならない場合は外部からのスクリプト注入を防ぐ対策を行う。

さらに進んだ学習のために

phpMyAdminに存在したpreg_replaceによるコード実行脆弱性の実例

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

laravel migration カラム名変更(初心者向け)

phpについての初投稿になります!

リファレンスにも書いてありますが、つい忘れてしまうのでメモ程度に紹介します

laravel 6.x系です
osはmacです

カラム名を変更、削除するには

ターミナルで

$ composer require doctrine/dbal

↑のコマンドを叩き、composerで"doctrine/dbal"

をインストールしましょう!(リファレンスに書いてあるので臆せずインストールしましょう!)

laravelはデフォルトではカラム名の変更や削除はできません!(なんでやねん!まぁなんか理由があるのでしょう)


で、ターミナルで

$ php artisan make:migration rename_変更前のカラム名_to_変更後のカラム名_on_テーブル名_table --table=テーブル名

とコマンドを叩きます(オプションの"--table=テーブル名"により、migtarionファイル作成時にテーブル名が追加され作成されます。また、migrationファイルの名前は正直適当でいいと思いますが、ファイル名をみて何の内容のmigrationファイルが実行されたかわかりやすい名前をつけた方が後々楽になると思います。)

〇〇_rename_変更前のカラム名_to_変更後のカラム名_on_テーブル名_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class RenameNightSpandingToNightSpendingOnCostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('costs', function (Blueprint $table) {
            $table->renameColumn('night_spanding', 'night_spending');//<-記述
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('costs', function (Blueprint $table) {
            $table->renameColumn('night_spending', 'night_spanding');//<-記述
        });
    }
}

こんな感じでマイグレーションファイルを作成します

upは

$ php artisan migrate

↑で実行される内容

downは

$ php artisan migrate:rollback

↑などで実行される内容を記述します
なので、カラム名の変更や、型の変更などは、基本的にはupで書いた内容の逆を記述すればいいと思います。
(php artisan migrate:refreshなども全てロールバックしてmigrateし直すコマンドなどあるので、downはちゃんと書いておかないとエラーの原因になります!)

で、

$ php artisan migrate

でカラム名を変更することができます!

カラム削除する場合でも、"doctrine/dbal"のインストールが必要みたいですので、個人的にはlaravelプロジェクトを立ち上げたらすぐにインストールしておいた方がいいのかな?と感じてます。

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

PHP クラスの定義とインスタンスの作成

目的

  • クラスの定義方法とインスタンスの作成方法を記載する

用語

  • クラス内で定義される変数: プロパティ
  • クラス内で定義される関数: メソッド

クラスの定義

  • 下記にUserクラスを定義する。
  • nameプロパティを定義する。
  • Hi, i am プロパティとなるようなsayHiメソッドを定義する。
class User 
{
    // プロパティの宣言
    public $name;

    //constructの宣言
    public function __construct($name) {
        $this->name = $name;
    }

    // メソッドの宣言
    public function sayHi() {
        echo "Hi, i am $this->name";
    }
}

インスタンスの作成

  • 先に定義したクラスの下で下記の行を記載する。
$miriwo = new User("miriwo");

インスタンスのメソッドを使用する

  • 下記にUserクラスを元に作成したmiriwoインスタンスを使用してsayHiメソッドを実行する。(>の後ろは出力)
$miriwo->sayHi();
>Hi, i am miriwo
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む