20201006のPHPに関する記事は16件です。

PHP ~アウトプット用語集3~

isset

値が設定されているか確認してくれます。
下記の画像は、値がcolorに設定されていればcolorを
値が設定されていなければNone selectedを返します。
スクリーンショット 2020-10-06 17.06.47.png
※issetで調べてnullだったら他の値を入れる事はよくあるので違う記述ができます。
下記の画像と上記の画像と同じ処理になります。
??の部分はnull合体演算子といいます。
スクリーンショット 2020-10-06 17.16.32.png

GET POST 違い

GET

情報の取得
情報がURLになる(index/php?name=taro)
安全性が低い
サイズや種類に制限あり

POST

情報の追加/更新/削除
index.php
安全性がやや高い
サイズや種類に制限なし

file()関数

ファイル全体を読み込んで配列に格納します。
ちなみに、FILE_IGNORE_NEW_LINES は
配列の各要素の最後の改行を省略します。
他にも、

FILE_USE_INCLUDE_PATH

include_path のファイルを探します。

FILE_SKIP_EMPTY_LINES

空行を読み飛ばします。
スクリーンショット 2020-10-06 18.52.30.png

fopen()

fopen(“開きたいファイル名”, “オープンモード”)fopen関数とは、ファイルまたはURLをオープンしてくれる関数です。

あるデータからファイルにデータの情報を読み込ませたり、ファイルの内容にデータ情報を書き込みする際など、ファイルを指定して、指定したファイルを開いて(持ってきて)くれます。
スクリーンショット 2020-10-06 21.50.08.png

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

php header http 受信

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

PHPをXdebugでデバッグする過程(MAMP)

開発環境

macOS Cataline 10.15.7
PHP7.4.2
xdebug2.9.8
mamp5.7

PHP Debugをインストール

vscodeでPHP Debugというプラグインをインストールします。
スクリーンショット 2020-10-06 19.18.50.png

Xdebugをインストール

上記でPHP Debugをインストール後、下にスクロールすると
XDebug installation wizardというリンクがあるのでそこに飛びます。
スクリーンショット 2020-10-06 21.14.48.png

このリンクを残したままで、次はphpinfoを確認します。
mampでOpen WebStart pageを開き、ヘッダーのTOOLSからPHPINFOを表示します。
スクリーンショット 2020-10-06 21.13.13.png

下記のように表示されるので、⌘ + a, ⌘ + cで全コピーし、先程のリンクに戻り貼り付け、Analyse my phpinfo() outputを押します。
スクリーンショット 2020-10-06 21.12.43.png

スクリーンショット 2020-10-06 21.14.14.png

スクロールし、INSTRUCTIONSの手順に沿って進めていきます。
スクリーンショット 2020-10-06 21.11.08.png

1. xdebug-2.×.×.tgzをダウンロードします。
2. php拡張機能をコンパイルするための前提条件をインストールします。

brew install php && brew install autoconf

3. ダウンロードしたパッケージを解凍します。

tar -xvzf xdebug-2.×.×.tgz

4. xdebug-2.×.×.tgzディレクトリに移動します。

cd xdebug-2.×.×.tgz

5. 拡張拡張モジュールをビルドし、下記の結果を確認します。

phpize
Configuring for:
...
Zend Module Api No:      20190902
Zend Extension Api No:   320190902

6. Makefileファイルを作成します。

./configure

7. Makefileを基にソースコードをコンパイルします。

make

8. xdebug.soファイルをコピーします。 

cp modules/xdebug.so /Applications/MAMP/bin/php/php7.×.×/lib/php/extensions/no-debug-non-zts-20190902

9. php.iniを編集します。
/Applications/MAMP/bin/php/php7.4.2/conf/php.iniを開くと、下記がコメントアウトしているので外します。

zend_extension = /Applications/MAMP/bin/php/php7.4.2/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so

10.最後にWebサーバーを再起動します。

最後に

mampとxamppがごっちゃになって、xamppでphpinfoを参照していたので、debianやらapt-getやらでfinkを導入したりと無駄に時間が掛かってしまった?そこも注意しないとですね。

mampかxamppかどちらを選ぶか悩んでいる方は、macOSであればmampが相性が良いらしいです。

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

Laravel taskscheduleでClass 'ZipArchive' not foundとエラー出たがPHPの指定を間違えていたのが原因だった

初Qiitaです。足りない、誤りがある箇所についてはコメントから指摘いただけますと嬉しいです。

環境:MacOS Catalina, PHP 7.4.11, Laravel 7.28.3

■やりたかったこと
Laravelのタスクスケジュールの設定を行い自動実行できるようにしたかった。

■問題
php artisan schedule:runをすると問題なく動くが、スケジュールを実行するとZipArchiveが見つからないと以下のエラーが出てしまう。
local.ERROR: Class 'ZipArchive' not found {"exception":"[object] (Error(code: 0): Class 'ZipArchive'

■解決方法
使用したいphpの場所を
which php
で取得してcrontabで明示的に以下のような感じで書く。

* * * * * cd /var/www/ && /usr/bin/php7 /var/www/artisan schedule:run >> /var/www/storage/logs/cron.log 2>&1

こちらの記事を参照すると分かりやすいです。
https://stackoverrun.com/ja/q/12644861

■問題の原因
Localで使用しているbrewでインストールしたphpとcronが使用するPHPは違うものらしい。(ここが曖昧なので詳しい方教えていただけると嬉しいです。)
そのため php -m をしてlocalのphpにzipがインストールされていることを確認しても、cronが使用するphpにはzipがインストールされていないためClass 'ZipArchive' not foundとなってしまった。

以下余談です。
当該エラーでググると多くの記事で「zipをインストールしろ。そしてモジュールを確認しろ」と書いてあり、それはやったのに何故エラーが出るのか。とずっと悩み続けてしまいました。

このエラーで相当な時間を費やして最終的に会社の先輩に解決してもらったのですが、私のようにcronに全然詳しくない・初めて使う。という場合にはいきなり全部組み込むんじゃなくて
1. echoを試す
2. php -vを試す
3. php artisanを試す
みたいに段階的に発展させると大きなつまずきが減りますよ。とのことでした。

精進します。

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

PHPのエラーログに配列の中身をきれいに出す方法

何も考えずに配列をログに吐こうとするとこうなる

ソース

<?php
    $array = array("hoge", "piyo", array(100,200,300));
    error_log($array));
?>

実行したときのエラーログ

[09-Jun-2007 03:44:20] Array

「Array」しか出ない(´・∀・`)

print_r()をかませる。第2引数をtrueにする。

ソース

<?php
    $array = array("hoge", "piyo", array(100,200,300));
    error_log(print_r($array, true));
?>

実行したときのエラーログ

[09-Jun-2007 03:44:20] Array
(
    [0] => hoge
    [1] => piyo
    [2] => Array
        (
            [0] => 100
            [1] => 200
            [2] => 300
        )

)

きれい!

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

【laravel-admin】【grid】Custom Actions Class

gridの\Encore\Admin\Grid\Displayers\ActionsをoverrideしてCustomActionsを作る

CustomActions.php
namespace App\Admin\Controllers;

use Encore\Admin\Admin;

class CustomActions extends \Encore\Admin\Grid\Displayers\Actions
{
    protected $actions = ['edit'];
    protected function renderEdit()
    {
        if(false !== strpos($_SERVER["REQUEST_URI"], env('ADMIN_ROUTE_PREFIX', 'admin')."/auth")){
            return <<<EOT
<a href="{$this->getResource()}/{$this->getRouteKey()}/edit" class="{$this->grid->getGridRowName()}-edit">
    <i class="fa fa-edit"></i>
</a>
EOT;
        }else{
            $this->setupEditScript();
            return <<<EOT
<a href="{$this->getResource()}/" class="{$this->grid->getGridRowName()}-edit btn btn-xs btn-default">
    <i class="fa fa-edit"></i> 編集
</a>
EOT;
        }
    }
    protected function setupEditScript()
    {
        $trans = [
            'delete_confirm' => trans('admin.delete_confirm'),
            'confirm'        => trans('admin.confirm'),
            'cancel'         => trans('admin.cancel'),
        ];

        $script = <<<SCRIPT

$('.{$this->grid->getGridRowName()}-edit').click(function (e) {
    var id = $(this).closest('tr').find('.column-ID').text();
    id = $.trim(id);
    var currentHref = $(this).attr('href');
    var newHref = currentHref + id + '/edit';
    window.location.href= newHref;
});

SCRIPT;

        Admin::script($script);
    }
}
bootstrap.php
Encore\Admin\Grid\Column::define('__actions__', \App\Admin\Controllers\CustomActions::class);

gridの編集ボタン&それをクリックしたときの挙動を変えたかった時に対応しました。

\Encore\Admin\Grid\Displayers\ActionsをoverrideしたCustomActionsクラスを作って、bootstrap.phpに上記を追記します。

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

LaravelにてDBから取得した画像をツイートする方法

はじめに

今回は認証済みでアクセス情報も取得している前提で進めますので、
まだの方は先にそちらを完了させて下さい!

php.7.0
Laravel 7

本文

 composer require abraham/twitteroauth //twitteroauthをインストール
TweetController.php
 use Abraham\TwitterOAuth\TwitterOAuth;//先程インストールしたのをuse
 public function index(Request $request)
    {
        $api_key = //apiキーをセット;
        $secret_Key = //シークレットキーをセット;
        $access_token = //アクセストークンをセット;
        $access_token_secret = //アクセストークンシークレットをセット;

        //インスタンスを作成
        $twitter = new TwitterOAuth($api_key, $secret_Key, $access_token, $access_token_secret);

        //ここは適宜画像を取得してください
        $user = App\Models\User::inRandomOrder()->first();

        //画像の内容を読み込む
        $post_image = file_get_contents($user->image_path);

        //ローカルの画像フォルダまでのパスを指定し、そこにtweet.jpgという名前で先程読み込んだ画像をセット
        file_put_contents(public_path() . "/images/tweet.jpg", $post_image, FILE_APPEND);

        //画像をアップロード
        $user_image = $twitter->upload('media/upload', ['media' => public_path() . "/images/tweet.jpg"]);

        $twitter->post("statuses/update", [
            //ツイート本文
            "status" =>
                $user->name . PHP_EOL .
                $user->text . PHP_EOL .
                '#ハッシュタグも指定できる'. PHP_EOL,

            'media_ids' => implode(',', [
                $user_image->media_id_string
            ])
        ]);
    }

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

PHPでも他のオブジェクト指向で言うオーバーロードができる!!(ただしコンストラクに限る)

概要

表題の通りですが、PHPでもコンストラクタに限り引数のシグニチャが異なるオーバーライド(他のオブジェクト指向で言うオーバーロード)ができるというのをマニュアルで確認したというだけのお話です。
PHPではオーバーロードができないという記事をよく見かけるのですが、コンストラクタだけは例外ということが明記されている記事を見かけなかったので(マニュアルにあるから当たり前!?)、残しておこうかと。

Manual

PHP Manual(extends)

サンプルコード

class Base
{
        protected $a;
        public function __construct(int $a)
        {
                $this->a = $a;
        }

        public function test(string $a)
        {
                echo sprintf('class Base(%s)', $a) . PHP_EOL;
        }
}

class A extends Base
{
        protected $a;
        protected $b;
        public function __construct(string $a, int $b)
        {
                $this->a = $a;
                $this->b = $b;
                parent::__construct($b);
        }

        public function test(int $a)
        {
                echo sprintf('class A(%d)', $a) . PHP_EOL;
        }
}


$base = new Base(10);
$base->test(10);
$base->test('xxx');

$a = new A('aaa', 10);
$a->test(10);
//$a->test('xxx');

このコードを実行すると、以下のE_WARNINGレベルのエラーが発生しますが、コンストクタについては何もエラーが発生していないことから、コンストラクタのオーバーライドは例外で、正常にオーバーライド(オーバーロード)可能なことが分かります。

Warning: Declaration of A::test(int $a) should be compatible with Base::test(string $a) in /var/www/wonder/test_overload.php on line 32

更なる疑問

ここで気になるのが、コンストラクタ以外のメソッドもWARNINGなので、実行自体は止まらず、オーバーライドできているような挙動となることです。

//$a->test('xxx');

のコメントアウトを外すと、以下のエラーが発生することからもオーバーライドして、引数の型が変更できているように見えます。

Fatal error: Uncaught TypeError: Argument 1 passed to A::test() must be of the type int, string given, called in /var/www/wonder/test_overload.php on line 41 and defined in /var/www/wonder/test_overload.php on line 28

まとめ

以上、新たな疑問は発生しましたが、マニュアル及びE_WARNINGレベルのエラーの発生より、

  • PHPでは引数のシグニチャーが異なるオーバーライド(オーバーロード)は基本的に不可
  • ただしコンストラクタのみオーバーライド(オーバーロード)可能

ということが分かりました。

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

PHPでもコンストラクタに限り、シグニチャーの違うオーバーライドができる!!

タイトル変更しました

ご指摘いただき、勘違いが分かりましたので、タイトルを以下の通り変更させてもらいました。

PHPでも他のオブジェクト指向で言うオーバーロードができる!!(ただしコンストラクに限る)

PHPでもコンストラクタに限り、シグニチャーの違うオーバーライドができる!!

概要

表題の通りですが、PHPでもコンストラクタに限り引数のシグニチャが異なるオーバーライド(他のオブジェクト指向で言うオーバーロード)ができるというのをマニュアルで確認したというだけのお話です。
PHPではオーバーロードができないという記事をよく見かけるのですが、コンストラクタだけは例外ということが明記されている記事を見かけなかったので(マニュアルにあるから当たり前!?)、残しておこうかと。

[2020-10-06追記]
ご指摘によりオーバーロードはできないことが分かりましたので、以下の記事は、「PHPでもコンストラクタに限り、シグニチャーの違うオーバーライドができる!!」という内容になります。
@error_401 さんありがとうございます!

Manual

PHP Manual(extends)

サンプルコード

class Base
{
        protected $a;
        public function __construct(int $a)
        {
                $this->a = $a;
        }

        public function test(string $a)
        {
                echo sprintf('class Base(%s)', $a) . PHP_EOL;
        }
}

class A extends Base
{
        protected $a;
        protected $b;
        public function __construct(string $a, int $b)
        {
                $this->a = $a;
                $this->b = $b;
                parent::__construct($b);
        }

        public function test(int $a)
        {
                echo sprintf('class A(%d)', $a) . PHP_EOL;
        }
}


$base = new Base(10);
$base->test(10);
$base->test('xxx');

$a = new A('aaa', 10);
$a->test(10);
//$a->test('xxx');

このコードを実行すると、以下のE_WARNINGレベルのエラーが発生しますが、コンストクタについては何もエラーが発生していないことから、コンストラクタのオーバーライドは例外で、正常にオーバーライド(オーバーロード)可能なことが分かります。

Warning: Declaration of A::test(int $a) should be compatible with Base::test(string $a) in /var/www/wonder/test_overload.php on line 32

更なる疑問

ここで気になるのが、コンストラクタ以外のメソッドもWARNINGなので、実行自体は止まらず、オーバーライドできているような挙動となることです。

//$a->test('xxx');

のコメントアウトを外すと、以下のエラーが発生することからもオーバーライドして、引数の型が変更できているように見えます。

Fatal error: Uncaught TypeError: Argument 1 passed to A::test() must be of the type int, string given, called in /var/www/wonder/test_overload.php on line 41 and defined in /var/www/wonder/test_overload.php on line 28

まとめ

以上、新たな疑問は発生しましたが、マニュアル及びE_WARNINGレベルのエラーの発生より、

  • PHPでは引数のシグニチャーが異なるオーバーライド(オーバーロード)は基本的に不可
  • ただしコンストラクタのみオーバーライド(オーバーロード)可能

ということが分かりました。

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

【laravel-admin】【controller】javascript

laravel-adminのController側でjavascriptを使う

ちょっとしたjavascriptのためにいちいちファイルを作って、とかviewを変更して、とかが大変な時は以下の方法でcontroller側にjavascriptを書いてページに埋め込むことができます。
viewの一部を変更したいけどviewには手を加えたくない...という場合などに便利です。

SampleController.php
use Encore\Admin\Admin;

class SampleController extends AdminController
{
    /**
     * sample
     */
    public function sample(Content $content)
    {
        $script = <<<SCRIPT
$('.breadcrumb li:first').remove();
$('.breadcrumb .active').html('<i class="fa fa-home"></i>メニュー');
$('.navbar-custom-menu').find('.pull-left').html('');
$('.main-footer').find('strong').html('');
SCRIPT;
        Admin::script($script);
        Admin::style('.form-control {margin-top: 10px;}');
        return $content
        ->title('メニュー')
        ->description('メニューです。')
     ->row('メニュー1')
        ->row('メニュー2')
        ->row('メニュー3');
    }
}

1行目→パンくずリストの1つ目を削除
2行目→自身がいる位置を現在地として表示
3行目→ユーザー設定のボタンを削除
4行目→フッターの文字を削除
こっちはcss→Admin::style('.form-control {margin-top: 10px;}');

参考:Laravel admin | CSS / JavaScript

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

CodeIgniter4のViewの基礎

前回の記事、CodeIgniter4のModelの基礎の続きです。

CodeIgniter4のViewってどんなもの?

CIのviewはphpファイルがそのままテンプレートエンジン的な振る舞いをしています。ある意味潔い実装です。
一応ビューパーサクラスが用意してあり、変数へのアサインや、配列を用いた簡単なループも出来ますが、あまり大した機能は持っていません。
その代わりといってはなんですが、比較的容易にSmartyやTwigといった外部のテンプレートエンジンを組み込んで使うことが出来ます。
これらのテンプレートエンジンが簡単に導入できるということは、viewを担当する人にCIのパーサの学習を強いる必要が無いことから結構なメリットになるものと思われます。

この記事を書いている当人も、CIの内蔵のテンプレートパーサを利用したviewの実装というものは、全くというほどしたことがないので、本記事でもここは最低限の説明にとどめておきます。

viewのファイルの出力の実態

まずviewとして設定されたファイルはどこにあり、どのようにコントローラから指定されて出力されるか?

CIのデフォルトインストールのトップページではapp/Views/welcome_message.phpというViewのファイルをapp/Controllers/Home.phpというコントローラのファイル内のindex()というメソッド上でコールされているview('welcome_message')というCI上のグローバル関数を叩いてその値を返却してあげることで表示を実施します。

app/Views/welcome_message.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Welcome to CodeIgniter 4!</title>

ーーー以下略ーーー

app/Controllers/Home.php
<?php namespace App\Controllers;

class Home extends BaseController
{
    public function index()
    {
        return view('welcome_message');
    }

    //--------------------------------------------------------------------

}

なお、view()の戻り値はレンダリングされたHTMLそのものなので、returnで返す前に、そのままメソッド内でechoすることも可能です。

ここの引数の'welcome_message'はズバリそのまま、app/Views/welcome_message.phpを指しており、引数に拡張子が省略されると.phpが補われます。要は、この関数の実体はapp/Views/welcome_message.phpの中身を返却せよという動作です。
(ちなみにview()のデフォルトパスapp/Views/app/Config/Paths.phpにて定義されている$viewDirectoryが反映されています。)

view()を使って表示する。

前の記事ではreturnに直接文字列を放り込んで、画面出力を行っていましたが、代わりにapp/Views/result.phpを作ってやり、そこでコントローラで受け取った戻り値などをview()に放り込んあげればその内容をビュー側でハンドリングすることが可能です。

簡単な例として「CodeIgniter4でDB接続まで」の記事で記したDBの接続テストの結果をビューで表示するようにしてみます。

app/Controllers/Home.php
<?php namespace App\Controllers;

use Config\Database;

class Home extends BaseController
{
    public function index()
    {
        return view('welcome_message');
    }

    //--------------------------------------------------------------------

    public function connectionTest()
    {
        $db = Database::connect();
        $data['listTables'] = "このDBのテーブル=" . implode( ',' , $db->listTables() );
        return view('result', $data);
    }
}
app/Views/Result.php
<html lang="ja">
<body>
<p><?php echo $listTables; ?></p>
</body>
</html>

このように、view()の第2引数(これはArrayで有ることが必須です)が、キーを変数名としたものに変換されてView上の変数として定義されますのでアクセスの際は$listTablesのような形で参照します。

詳しくはCIのマニュアルのViewsのページを参照してください。
CIのテンプレートエンジンを利用した制御構造や変数等のアサインなどの仕方の記載があります。

次回はCIにViewのテンプレートエンジンとしてSmartyやTwigを組み込んで利用する方法を説明します。

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

ComposerのPHPパスを変更する

PHPアプリケーションを開発する際に、Composerで使用するPHPバージョンを変更したい場合があります。
composerコマンドは環境変数Pathに設定されているものを使うので、実行対象のPHPのパスを変更する必要があります。

1.composer.batを検索します。

where composer.bat

2.プロジェクトフォルダへコピーする
3.コピー先のcomposer.batを下記のように編集する

@ECHO OFF
php “%~dp0composer.phar” %*

@ECHO OFF
【設定対象としたいPHPの絶対パス】 “%~dp0composer.phar” %*

4.コピー先のcomposer.batを実行する

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

PHPにおける配列のループ処理における基本コード

配列の基本コード

PHPで配列を使ったループ処理の基本的なコードを記述する。

$arr = ['a', 'b', 'c'];     //a,b,cの3つの要素を持つ配列
echo $arr[0] . PHP_EOL;     //配列の1番目の要素を表示
echo '-------------' . PHP_EOL;

foreach($arr as $ele) {     //配列をループ処理
    echo $arr[1] . PHP_EOL; //2番目の要素を表示
    echo $ele . PHP_EOL;    //要素を表示
    echo '-------------' . PHP_EOL;
}
var_export($arr) . PHP_EOL; //配列の中身を表示

表示結果

a
-------------
b
a
-------------
b
b
-------------
b
c
-------------
array (
  0 => 'a',
  1 => 'b',
  2 => 'c',
)

説明

配列は0番目から始まるため、$arr[0]が要素の’a'にあたる。

$arr = ['a', 'b', 'c'];     //a,b,cの3つの要素を持つ配列
echo $arr[0] . PHP_EOL;     //配列の1番目の要素を表示

$eleを要素の変数とする。

$arr[1]は'b'であり、要素数は3つのため、ループ処理をするとbが3回表示される。

直後に'echo $ele'があるので、3回表示しているbの次にa,b,cがそれぞれ表示されている。

foreach($arr as $ele) {     //配列をループ処理
    echo $arr[1] . PHP_EOL; //2番目の要素を表示
    echo $ele . PHP_EOL;    //要素を表示
    echo '-------------' . PHP_EOL;
}
var_export($arr) . PHP_EOL; //配列の中身を表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EclipseのXdebugが勝手に起動してしまう

PHPアプリケーションをローカル環境で開発をしている時に、Eclipseを使用して開発中のWebサイトへブラウザからアクセスするたびに、勝手にEclipseのXdebugが起動してしまい、Webサイトへアクセスできないことがあります。
Eclipseを閉じれば再度アクセス可能になりますが、これでは開発効率が悪いと思います。
この事象は以下の方法で、回避することが可能です。

  • ウィンドウ→設定→PHP→デバッグ
  • 「最初の行でブレーク」のチェックを外す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】ガチャを実装してみました【PHP】

概要

ソシャゲのガチャの一部を実装してみました。
以下のようなシンプルな仕様です。
・どのガチャも単発と10連。
・ピックアップガチャに対応。
・結果はテキストで表示。

デモ画面

Gacha

クライアント側のリポジトリです。

バックエンド側のphpのコードです。
Gacha.php
<?php

// 同一レアリティ内のアイテムの抽選
function get_item($item_rarity,$gacha_group_id){

  // ガチャの重みの合計
  $item_weight_sum = 0;

  // ガチャの重みの配列
  $item_weight_array = array();

  // ガチャのIDの配列
  $item_id_array = array();

  // PDOインスタンスを生成 
  $item_pdo = get_PDO();

  // SELECT文を変数に格納
  $item_select_sql = "SELECT item_weight,item_id FROM item_config WHERE item_rarity ='". $item_rarity. "' AND gacha_group_id ='". $gacha_group_id. "'";


  // SQLステートメントを実行し、結果を変数に格納
  $item_select_stmt = $item_pdo->query($item_select_sql);

  // foreach文で配列の中身を一行ずつ出力
  // $rou['列名']で取り出せる
  foreach ($item_select_stmt as $item_row) {
    // データベースのフィールド名で出力
    $item_weight_sum += (int)$item_row['item_weight'];
    $item_weight_array[] = (int)$item_row['item_weight'];
    $item_id_array[] = (int)$item_row['item_id'];
  }

  // 乱数生成
  $random = rand(1,$item_weight_sum);
  $item_weight_total=0;

  // アイテムIDの抽選
  foreach($item_weight_array as $item_key => $item_value ){
    $item_weight_total += $item_value;
    if($random <= $item_weight_total){
      $item_result = $item_id_array[$item_key];
      break;
    }
  }

  // アイテムIDからレアリティとアイテム名を抽出
  $item_sql = "SELECT item_rarity,item_name FROM item WHERE item_id ='". $item_result. "'";
  $item_stmt = $item_pdo->query($item_sql);
  foreach($item_stmt as $item){
    echo 'レアリティ : ' . $item['item_rarity'] . ' ';
    echo 'アイテム名 : ' . $item['item_name'];
    echo "\n"; 

  }

}

// レアリティの抽選
function get_rarity($gacha_group_id){

  try{
    // ガチャの重みの合計
    $gacha_weight_sum = 0;
    // ガチャの重みの配列
    $gacha_weight_array = array();
    // ガチャのレアリティの配列
    $gacha_rarity_array = array();

    // ガチャグループを条件に抽出
    $dbh = get_PDO();
    $sql = "SELECT gacha_weight,gacha_rarity FROM rarity_config WHERE gacha_group_id ='". $gacha_group_id. "'";
    $stmt = $dbh->query($sql);

    // ガチャグループで条件を絞ってガチャの重みを合計する
    foreach ($stmt as $row) {
      // データベースのフィールド名で出力
      $sum += (int)$row['gacha_weight'];
      $gacha_weight_array[] = (int)$row['gacha_weight'];
      $gacha_rarity_array[] = (int)$row['gacha_rarity'];
    }

    // 乱数生成
    $random = rand(1,$sum);
    // ガチャの重みのトータル(順次加算)
    $gacha_weight_total=0;

    // 抽選ロジック(乱数を用いて、範囲内だった重みを探す)
    foreach($gacha_weight_array as $key => $value ){
      $gacha_weight_total += $value;
      if($random <= $gacha_weight_total){
        // 範囲内だった重み
         $rarity_result = $gacha_rarity_array[$key];
        break;
      }
    }

    // アイテムの抽選
    get_item($rarity_result,$gacha_group_id);



  // エラー(例外)が発生した時の処理を記述
  }catch (PDOException $e) {

    // エラーメッセージを表示させる
    echo 'データベースにアクセスできません!' . $e->getMessage();

    // 強制終了  
    exit;
  }
}

// PDOインスタンス生成
function get_PDO(){
  return new PDO(
    'mysql:host=mysql1.php.starfree.ne.jp;dbname=opnvtw_stdb;charset=utf8', 
    'opnvtw_lantitor',
    '1211stst',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
  );
}

// 最初に呼ばれる関数
function get_gacha(){

  // tryにPDOの処理を記述
  try {

    $gacha_id = $_REQUEST['gacha_id'];


    $pdo = get_PDO();
    $stmt = $pdo-> prepare("SELECT * FROM gacha WHERE gacha_id = ?");
    $stmt->bindValue(1,$gacha_id,PDO::PARAM_STR);
    $stmt->execute();


    // gachaテーブルのgacha_times分ガチャを回す。
    foreach ($stmt as $row) {
      for($i = 1; $i <= (int)$row['gacha_times']; $i++){
        // レアリティの抽選からして、その後にアイテムの抽選をする
        get_rarity($row['gacha_group_id']);
      }



    }
  // エラー(例外)が発生した時の処理を記述
  } catch (PDOException $e) {

    // エラーメッセージを表示させる
    echo 'データベースにアクセスできません!' . $e->getMessage();

    // 強制終了  
    exit;

  }

}

get_gacha();

?>

開発環境

MacOS Catalina 10.15.5
クライアントはC#+Unity 2019.2.12f1
バックエンドはPHP
レンタルサーバーStarSeverFreeのフリー PHP+MySQLプラン
今回はレンタルサーバーで実装してますが、ローカルで確認したいならXamppを使います。

バックエンド

抽選ロジック

抽選ロジックは、重み付きランダムアルゴリズムを用いています。
例の図は☆5が5%、☆4が10%、☆3が85%の確率です。
探索範囲はそれぞれ、☆5が1~5、☆4が6~15、☆3が16~100になります。
1~100の間のランダムで値を出し、出た値が60なら16~100の間なので、抽選結果は☆3になります。探索には累積和を使っています。
このロジックを用いて、レア度の抽選→同一レアリティ内のアイテムの抽選を行っています。

RandomRange

GachaLogic.php
  // ガチャの重みの配列
  $gacha_weight_array = array(5,10,85);
  // ガチャのレア度の配列
  $gacha_rarity_array = array(5,4,3);
  // 1~100の間で乱数を生成
  $random = rand(1,100);
  // ガチャの重みのトータル(順次加算)
  $gacha_weight_total=0;

  // 乱数の範囲を探す
  foreach($gacha_weight_array as $key => $value ){
    $gacha_weight_total += $value;
    // 範囲内の数値
    if($random <= $gacha_weight_total){
      // レア度の配列からレア度を取り出す 
      $result = $gacha_rarity_array[$key];
      break;
    }
  }

  echo $result;

マスタデータ・DB

2種類のガチャ(普通のガチャの単発と10連、ピックアップガチャの単発と10連)のデータです。
今回は、ピックアップガチャの10連が押された場合を例に全体の流れを説明したいと思います。

ガチャテーブルは、ガチャの詳細が格納さているテーブルです。
ピックアップガチャが押された場合、gacha_idのpickup_sort_10がサーバ側(PHP)に渡されます。
gacha_idはガチャを特定するためのもので、一意です。
gacha_timesが10は、10回ガチャを回すことを表しているので、抽選は10回行われます。
gacha_group_idは、ガチャの種類を特定することができます。
単発と10連では、回数やコストは違いますが、種類は同じです。
そのため、pickup-sort_1とpickup-sort_10のgacha_group_idは両方ともBです。

ガチャテーブル

gacha_id gacha_group_id gacha_name gacha_cost gacha_times
normal_1 A test_1 10 1
normal_10 A test_10 100 10
pickup_sort_1 B test_pickup_sort_1 10 1
pickup_sort_10 B test_pickup_sort_1 100 10

レアリティ設定テーブルは、gacha_group_idごとの重みを設定しているテーブルです。
ピックアップガチャのgacha_group_idであるBのgacha_weightを用いて、抽選します。
87%の確率で☆3、10%の確率で☆4、3%の確率で☆5が出る設定になっています。
gacha_weightの合計が1000であれば0.1%きざみ、100であれば1%きざみにすることができます。
ここの抽選で出たgacha_rarityを用いて、次にアイテムの抽選を行います。

レアリティ設定テーブル

gacha_group_id gacha_rarity gacha_weight
A 3 870
A 4 100
A 5 30
B 3 870
B 4 100
B 5 30

アイテム設定テーブルは、gacha_group_idとitem_rarityごとの重みを設定しているテーブルです。
gacha_group_idのBと、先ほど抽選したレアリティ(gacha_rarity)を用いて、抽選を行います。
テーブルにデータを挿入するのに手間がかかりますが、一つ一つ設定できるようにすることで、コードの変更をせずにピックアップを実装することができます。
例えば、gacha_group_idがBで、item_idが4001や5001は、同一レアリティ内でも、抽選が当たりやすくしています。

アイテム設定テーブル

gacha_group_id item_id item_rarity item_weight
A 3001 3 100
A 3002 3 100
A 3003 3 100
A 3004 3 100
A 3005 3 100
A 3006 3 100
A 3007 3 100
A 3008 3 100
A 3009 3 100
A 3010 3 100
A 4001 4 500
A 4002 4 125
A 4003 4 125
A 4004 4 125
A 4005 4 125
A 5001 5 333
A 5002 5 333
A 5003 5 334
B 3001 3 100
B 3002 3 100
B 3003 3 100
B 3004 3 100
B 3005 3 100
B 3006 3 100
B 3007 3 100
B 3008 3 100
B 3009 3 100
B 3010 3 100
B 4001 4 500
B 4002 4 125
B 4003 4 125
B 4004 4 125
B 4005 4 125
B 5001 5 500
B 5002 5 250
B 5003 5 250

アイテムテーブルは、アイテムの詳細が格納されているテーブルです。
抽選で出たitem_idを用いて、名前(item_name)やアイテムの種類(item_type)を表示することができます。

アイテムテーブル

item_id item_rarity item_name item_type
3001 3 バブルソート 1
3002 3 ヒープソート 1
3003 3 線形探索 2
3004 3 レイトレーシング 3
3005 3 ユークリッドの互除法 3
3006 3 バケットソート 1
3007 3 シェルソート 1
3008 3 挿入ソート 1
3009 3 選択ソート 1
3010 3 シェーカーソート 1
4001 4 マージソート 1
4002 4 二分探索 2
4003 4 ダイクストラ法 3
4004 4 ベルマンフォード法 3
4005 4 ワーシャルフロイド法 3
5001 5 クイックソート 1
5002 5 幅優先探索 2
5003 5 深さ優先探索 2

以上がバックエンド側の実装になっています。

クライアント

クライアント側はUnity,C#で書いてます。
UIアーキテクチャは簡易的なMV(R)Pで実装しています。
以前にMV(R)Pで実装した際のお話をQiitaに投稿しましたので、共有させて頂きます。
【Unity】アウトゲームでの設計でMV(R)Pパターンを採用しました【設計】
また、今回はバックエンドの実装や考えを中心に行ったので、クライアント側の実装について説明は、省かさせてもらいます。

最後に

実際ガチャを実装するには、もっと細かいテーブル設定やロジックが必要になると思います。
他にもいくつかQiitaで記事を投稿しているのでよければ見てください。

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

GMO-PGのリンクタイプPlusでセキュアなカード決済を実現する

概要

GMO-PG リンクタイプPlusで決済に利用したカード情報を「決済後会員登録」によって自動でGMOに登録するの記事にあるように、
一度利用したクレジットカード情報を登録しておくことで、2回目以降の決済では、
わざわざカード情報を入力しなくても、決済処理を行うことが可能となります。

ただ、ここで問題になるのが登録済みのカード情報がある場合、ログインユーザーが誰であっても決済手続きが完了できてしまう。ということです。

そこで、GMO-PGでは3Dセキュア(本人認証サービス)というものを用意しています。

この記事ではこの3Dセキュアを利用していきます。

テスト環境で動作確認するための3Dセキュア対応クレジットカード情報

3DS1.0対応のカード情報
https://faq.gmo-pg.com/service/detail.aspx?id=1681&a=102&isCrawler=1

3DS2.0対応のカード情報
https://faq.gmo-pg.com/service/detail.aspx?id=2379&a=102&isCrawler=1

サンプルコード

   // GMO決済画面へのURLを生成して返却します。
    public function get_secure_payment_url($flg_conf_member = false)
    {
        // 決済URL取得API
        $url = 'https://pt01.mul-pay.jp/payment/GetLinkplusUrlPayment.json';

        // GMOの決済で指定するオーダーIDは一意でなければいけない(注文ID先頭4桁を会員IDにした。)
        $orderId = str_pad($this->user['user_id'], 4, '0', STR_PAD_LEFT) . 'OR' . date('YmdHis');

        // json形式のパラメータを生成するための配列パラメータ定義
        $arr_param = array(
            'geturlparam'=> array(
                'ShopID'=> SHOP_ID,
                'ShopPass'=> SHOP_PASS,
                'TemplateNo'=> '1'
            ),
            'configid'=> 'test01',
            'transaction'=> array(
                'OrderID'=> $orderId,
                'Amount'=> 10000,
                'Tax'=> 1000,
                'PayMethods'=> ['credit']
            ),
            'credit'=> array(
                'JobCd'=> 'CAPTURE',
                'Method'=> '1',
                'TdFlag'=> '2', // 3Dセキュア認証を契約に従って実施
                'Tds2Type'=> '1'
            )
        );
        // 3Dセキュア認証テスト用カード
        // 3DS1.0用 = https://faq.gmo-pg.com/service/detail.aspx?id=1681&a=102&isCrawler=1
        // 3DS2.0用 = https://faq.gmo-pg.com/service/detail.aspx?id=2379&a=102&isCrawler=1

        // GMO会員IDのパラメータを設定するか判別してパラメータに追加
        $arr_member_id = array(
            'credit'=> array(
                'MemberID'=> $this->user['user_id']  // GMO会員IDの指定
            )
        );

        if ($flg_conf_member) {
            if ($this->gmo_exists_member($this->user['user_id'])) {
                $arr_param = array_merge_recursive($arr_param, $arr_member_id);
            }
        } else {
            // $flg_conf_member=falseの場合は無受験に会員IDを指定する。
            $arr_param = array_merge_recursive($arr_param, $arr_member_id);
        }

        // 配列→json変換
        $param = json_encode($arr_param);
        return $this->get_gmo_linkurl($url, $param);
    }

    // プロトコルタイプのAPIを利用し、キー型のパラメータ指定方法によるAPI実行結果から、URL情報だけを抽出して返却します。
    private function get_gmo_linkurl($url, $param) {
        // リクエストコネクションの設定
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8'));
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_POSTFIELDS, $param);
        curl_setopt($curl, CURLOPT_URL, $url);

        // リクエスト送信
        $response = curl_exec($curl);
        $curlinfo = curl_getinfo($curl);
        curl_close($curl);

        $resJson = json_decode($response, true);

        // LinkUrlが取得できなければエラーとして扱う
        if (!array_key_exists('LinkUrl', $resJson)) {
            $http_stscd = $curlinfo['http_code'];

            log_message('error', print_r($response ,true));

            // GMO エラーコードが返却されていればセットする
            $gmo_errcd = '';
            parse_str($response, $data);
            if(array_key_exists('ErrCode', $data)) {
                $gmo_errcd = $data['ErrCode'];
            }

            $errmsg = <<< EOD
            $url . ' API実行が失敗しました。 : '
            'HTTPステータスコード : ' . $http_stscd
            'GMOエラーコード : ' . $gmo_errcd
            EOD;

            throw new Exception($errmsg);
        }

        // URL取得APIの実行結果からリンク情報を取得し返却
        return $resJson['LinkUrl'];
    }

動作検証

クレジットカード情報を入力する際、テスト用のカード番号を入力する。

スクリーンショット 2020-10-06 8.06.28.png

通常通り決済を完了する。

スクリーンショット 2020-10-06 8.06.32.png

3Dセキュアの認証画面が表示され認証が求められる。

スクリーンショット 2020-10-06 8.06.41.png

正常に認証が通ると決済が完了する。

スクリーンショット 2020-10-06 8.06.48.png

上記は3DS1.0の場合のテスト環境での動作です。
3DS2.0の場合は、本来パスワード入力などが求められるはずなのですが、テスト環境では入力画面が表示される動きはしないようです。

スクリーンショット 2020-10-06 8.09.29.png

ちなみに、新規でカード情報を入力した場合のみではなく、登録済みのカード情報を利用した決済の場合であっても、同じ認証画面が表示され、認証情報を入力しないと決済が完了しないように動作させることが可能です。

最後に

3DS1.0/3DS2.0を導入したとして、ユーザーは自身が保持しているクレジットカードの3DS用の認証情報を把握しているものだろうか。。。

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