20201016のPHPに関する記事は17件です。

LambdaでLaravelのコマンドを定期実行する

Laravelで書いたバッチを実行するにあたって、EC2インスタンスだと実行タイミング以外もコストがかかるし、ECSタスクだとコンテナの管理がちょっと面倒。Lambdaで動かせればメンテも楽だし安く上がりそうなのでやってみた。

前提

brefphp を導入済の環境。

実行するのは Laravel で作成済みのコマンド、croncmd:exec。通常は php artisan croncmd:execで実行。

brefphp の導入については brefphpを使って簡単にLaravelをサーバレス環境で動かす - Qiita がとてもわかりやすいです。

serverless.yml

今回は serverles.yml の設定のみです。例として月曜から金曜まで 09:00-17:00まで5分ごとに定期実行する設定を記載します。

input 部分で実行するコマンドを、rate で実行タイミングを指定します。時間についてはGMTで設定することに注意してください。

service: test-app

provider:
    name: aws
    region: ap-northeast-1
    runtime: provided

plugins:
    - ./vendor/bref/bref

functions:
    artisan:
        handler: artisan
        timeout: 120 # in seconds
        layers:
            - ${bref:layer.php-73}
            - ${bref:layer.console}
        events:
          - schedule:
              rate: cron(0/5 0-8 ? * MON-FRI *) # GMT
              input: '"croncmd:exec"'

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

配列内スペースを余分にとってソースの見栄えをよくする

PHPを使っているとよく配列を使うのですが、keyと要素を揃えて書くのが好きです。

人により好みが分かれるところだと思いますが私の場合以下の二つであれば下の書き方(sample2.php)を好みます。

sample1.php
$input = array
(
    "id" => 1,
    "name" => "sato",
    "postal_code" => "000-0000",
    "address" => "Tokyo",
    "phone_number" => "09012345678"
);
sample2.php
$input = array
(
    "id"           => 1,
    "name"         => "sato",
    "postal_code"  => "000-0000",
    "address"      => "Tokyo",
    "phone_number" => "09012345678"  // 揃っていて見やすい!
);

しかし後から長い要素が増えると全てをいちいち揃えることになってしまい面倒です。
そんな時はあらかじめスペースを余分に取っておくと楽かもしれません。

sample2.php
$input = array
(
    "id"                 => 1,
    "name"               => "sato",
    "postal_code"        => "000-0000",
    "address"            => "Tokyo",
    "phone_number"       => "09012345678"
);

こうしておけば例え長い要素が増えたとしても対応できます。

sample3.php
$input = array
(
    "id"                 => 1,
    "name"               => "sato",
    "postal_code"        => "000-0000",
    "address"            => "Tokyo",
    "phone_number"       => "09012345678",
    "Family structure"   => "single"   // 追加要素
);

大した話ではないですが、他人の書いたソースを見ていて自分なりにテンションが上がったので書いてみました。色々応用が効きそうです。

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

phpMyAdminがやたらに遅い。

phpMyAdminのテーブル一覧表示が遅い時に取った対応

phpMyAdmin(Ver.4.0.10.20)を業務上必要だったので、設定してみたのですがテーブル一覧表示がやたらに遅い。。。テーブル一覧表示がされるまでに3分近くかかる。いつもかかる。

$cfg['MaxExactCount']と$cfg['MaxExactCountViews']の設定

いろいろ調べると、config.inc.phpの$cfg['MaxExactCount']$cfg['MaxExactCountViews']の設定値を「0」にしてテーブル一覧表示のレコード数を正確に取らなければいいとの事。

$cfg['MaxExactCount']=0;
$cfg['MaxExactCountViews']=0;

設定してみました。でも、全然変わりませんでした。
いろいろ、config.inc.phpの設定を変えたり、ブラウザのcookieやキャッシュをクリアしたり、ブラウザを変えたりしたのですが、結果は変わりませんでした。
原因が特定できず、仕方なく、しばらくそのまま使っていきました。

原因が見つかった!(version_check.php、君でしたか。。)

我慢して使っていたのですが、2か月ぐらいたった時、「もういい加減にしろー!!やってられん!!」と切れ気味モードで原因調査を再び開始しました。

DBサーバへガリガリとリクエストを行っていないのは、特定済みだったので原因はWebサーバ側との推測で調査を行う。
apacheのアクセスログにリクエストを処理するのにかかった時間、マイクロ秒単位で出力(フォーマットに %Dを指定)しているのですが、よくよくログを確認すると、version_check.phpが処理終了まで数分かかっていた。こいつが原因だったようです。

version_check.phpって何?必要なの?と、phpMyAdminのドキュメントを確認する。

https://docs.phpmyadmin.net/en/latest/config.html#cfg_VersionCheck

最新のバージョンをチェックするためのプログラムらしい。下記のように設定するとバージョンチェックしないようにできる。

$cfg['VersionCheck']=false;

config.inc.phpに上記を記述したら、長時間待たされることは無くなりました。
動作環境がPHP 5.4までしか使えず、必然的にphpMyAdminも古いものしか利用できないため、その影響だったのかも知れません。

ようやく、ストレス無く使えるようになりました。やれやれです。

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

javascriptのLocalStorage を使ってお気に入り機能を自作してみた ③実装編-基盤作り-

どうも7noteです。プラグインより多機能なお気に入り機能を作成。前回の続き

①準備編がまだの方はこちら
②実装編-基盤作り-がまだの方はこちら

wordpressで自作のお気に入り機能を使いたい人はこちら

実運用レベルで使ってみる(コピペで動くよ)

押したらお気に入りに追加。もう一度押したら削除されるように作ります。

完成イメージ

movie.gif

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>

<ul>
  <li no="1" class="add" onclick="addfav('1')"><span></span>No:1 りんご</li>
  <li no="1" class="remove" onclick="removefav('1')"><span></span>No:1 りんご</li>
  <li no="2" class="add" onclick="addfav('2')"><span></span>No:2 みかん</li>
  <li no="2" class="remove" onclick="removefav('2')"><span></span>No:2 みかん</li>
  <li no="3" class="add" onclick="addfav('3')"><span></span>No:3 ばなな</li>
  <li no="3" class="remove" onclick="removefav('3')"><span></span>No:3 ばなな</li>
</ul>

<style>
ul li.remove span {
  color: #F00;
}
</style>

<script>
/* 【解説Aパート】 "表示ソースの切り替え" */
function togleitem(oid,event){
    if(event == 'add'){
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}


/* 【解説Bパート】 "ページロード時にローカルストレージの中をチェック" */
$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';
  var getjson = localStorage.getItem(key);
  var oidlist = JSON.parse(getjson);
  if(oidlist != null){
    oidlist.forEach( function( oid ) {
      togleitem(oid,'add');
    });
  }

})  

/* 【解説Cパート】 "お気に入りに追加機能" */
function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);
        var setjson = JSON.stringify(oidary);
        localStorage.setItem(key, setjson);

        togleitem(oid,'add');

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'add');
        }
    }
}

/* 【解説Dパート】 "お気に入りから削除機能" */
function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'remove');
        }
    }
}
</script>

</body>
</html>

〜〜解説〜〜

javascriptの処理を大きく4つの機能に分けて解説していきます。

【解説Aパート】 表示ソースの切り替え

function togleitem(oid,event){
    if(event == 'add'){  // 引数の[event]が`add`の時
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){  // 引数の[event]が`remove`の時
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}

パートAでは別段ややこしい処理を書いているわけではありませんが、no属性とクラス(addもしくはremove)で特定の<li>を見つけだし、eventが[add]ならお気に入りに追加済みの見た目に変更し、逆に[remove]なら未追加の表示にします。

【解説Bパート】 ページロード時にローカルストレージの中をチェック

$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';  // ローカルストレージのキーを指定
  var getjson = localStorage.getItem(key);  // ローカルストレージから値を取得
  var oidlist = JSON.parse(getjson);        // JSON形式から配列に直す
  if(oidlist != null){                      // 配列が空でなければ
    oidlist.forEach( function( oid ) {      // 配列の中身を1つずつ[oid]に入れてループを回す
      togleitem(oid,'add');                 // togleitem()関数を動かす ※パートAで書いたやつ
    });
  }

})

パートBでは、ページロードした時に過去にお気に入りにチェックを入れた情報を元に表示するソースをお気に入り状態に変更する処理が書いています。

まず、2行目でデフォルトの状態(つまり全てお気に入り非チェック)にします。そして、ローカルストレージからお気に入りリストを取得してきて、もしお気に入りリストが登録されていれば、登録されているIDの<li>はチェック済の状態に切り替える。という処理を書いています。

【解説Cパート】 お気に入りに追加機能

function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);               // 初めてなので、新しい配列を作成
        var setjson = JSON.stringify(oidary);  // 配列をJSON形式に変換
        localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

        togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);                     // 配列にクリックされたIDを追加
            var setjson = JSON.stringify(oidlist); // またJSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

            togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え
        }
    }
}

パートCでは、お気に入りにIDを登録(保存)する時の処理をかいています。

まず、お気に入りが追加されるシーンとしては2パターンあります。

1.ローカルストレージに初めてお気に入りIDが登録される時
2.既にお気に入りリストが登録されていて、他のIDがさらに追加される時

この2パターンがあるため、まず最初にローカルストレージの中身を確認し、「お気に入りID」というキーが既に登録されているかどうかをif文で分岐させます。
まだ登録されていなければ、新しく「お気に入りID」というキーを作成し、値にクリックしたIDを入れます。
既に登録されているのであれば、取得したお気に入りリストの配列の最後にクリックしたIDを追加します。
どちらのパターンでもパートAの関数を使い、ソースを切り替えて完了です。

【解説Dパート】 お気に入りから削除機能

function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );   // 配列から該当IDを削除
            var setjson = JSON.stringify(oidlist); // JSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージに保存

            togleitem(oid,'remove');               // クリックされた対象の<li>の表示を切り替え
        }
    }
}

ここまで読んでいる方であればほとんど説明は不要かもしれません。

削除をする場合は、配列の何番目にIDがあるかを調べないといけないので、indexOf()を使って、配列の何番目に該当IDがあるのかを調べます。
あとはspliceを使って該当IDを削除し、またJSON形式にしてからローカルストレージに保存。その後、ソースの表示を切り替えています。

おまけ php編 〜wordpressの投稿IDに応用〜

ここからは初級〜中級者向けのwordpressでお気に入り機能を自作した人向けの内容になります。

wordpressでローカルストレージを使ったお気に入り機能を実装

仕組み

1.onclickでリンク先を指定して起動
2.ローカルストレージからデータを取得
3.取得したデータをpostで渡す
4.postのデータを受け取って変数に格納
5.変数のIDを使ってwordpressのループを作成

xxxx.php
<!-- 飛び先のURLを引数に指定 -->
<div onclick="oidlisttrans('http://hogehoge.com/xxx/yyyy.php')">お気に入りリストを見る</div>

<script>
    // ローカルストレージにあるparamsのデータをpostで送信
    function post(path, params, method='post') {
        const form = document.createElement('form');
        form.method = method;
        form.action = path;
        for (const key in params) {
            if (params.hasOwnProperty(key)) {
                const hiddenField = document.createElement('input');
                hiddenField.type = 'hidden';
                hiddenField.name = key;
                hiddenField.value = params[key];
                form.appendChild(hiddenField);
            }
        }
        document.body.appendChild(form);
        form.submit();
    }

    // お気に入りリストにデータをpost()を使って送信
    function oidlisttrans(link,data='all'){
        var key = 'お気に入りID';
        var getjson = localStorage.getItem(key);
        var oidlist = JSON.parse(getjson);
        obj = {
            'IDリスト': oidlist
        };
        post(link, obj);
    }


飛び先のページ(上と同一ページでも可能)

yyyy.php
<?php
$idlist = explode( ',', $_POST['IDリスト'] );

$wp_query = new WP_Query();
$args = array(
    'post_status' => 'publish',
    'post__in' => $idlist,
    'orderby' => 'post__in',
);
$wp_query->query($args);
?>
<?php if (have_posts()) : ?>
    <?php while (have_posts()) : the_post(); ?>
        //ループ内の処理を自由に書く
    <?php endwhile; ?>
<?php else: ?>
    // 投稿がなかった時の処理
<?php endif; ?>

こんな感じの書き方でwordpressでも応用して使えるかな?

ちょっと長丁場になりましたがいろいろ使い道があると思います!

まとめ

長くなるためCSSはほとんど省きましたが、自由に表示を切り替えたり見た目を変更できるので、
好きな位置に好きなボタンを配置できるので、プラグインなどを使ったけどうまくいかない方の参考に少しでもなればいいかなと思います。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ

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

javascriptのLocalStorage を使ってお気に入り機能を自作してみた ③実装編-組み込み-

どうも7noteです。プラグインより多機能なお気に入り機能を作成。前回の続き

①準備編がまだの方はこちら
②実装編-基盤作り-がまだの方はこちら

wordpressで自作のお気に入り機能を使いたい人はこちら

実運用レベルで使ってみる(コピペで動くよ)

押したらお気に入りに追加。もう一度押したら削除されるように作ります。

完成イメージ

movie.gif

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>

<ul>
  <li no="1" class="add" onclick="addfav('1')"><span></span>No:1 りんご</li>
  <li no="1" class="remove" onclick="removefav('1')"><span></span>No:1 りんご</li>
  <li no="2" class="add" onclick="addfav('2')"><span></span>No:2 みかん</li>
  <li no="2" class="remove" onclick="removefav('2')"><span></span>No:2 みかん</li>
  <li no="3" class="add" onclick="addfav('3')"><span></span>No:3 ばなな</li>
  <li no="3" class="remove" onclick="removefav('3')"><span></span>No:3 ばなな</li>
</ul>

<style>
ul li.remove span {
  color: #F00;
}
</style>

<script>
/* 【解説Aパート】 "表示ソースの切り替え" */
function togleitem(oid,event){
    if(event == 'add'){
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}


/* 【解説Bパート】 "ページロード時にローカルストレージの中をチェック" */
$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';
  var getjson = localStorage.getItem(key);
  var oidlist = JSON.parse(getjson);
  if(oidlist != null){
    oidlist.forEach( function( oid ) {
      togleitem(oid,'add');
    });
  }

})  

/* 【解説Cパート】 "お気に入りに追加機能" */
function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);
        var setjson = JSON.stringify(oidary);
        localStorage.setItem(key, setjson);

        togleitem(oid,'add');

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'add');
        }
    }
}

/* 【解説Dパート】 "お気に入りから削除機能" */
function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );
            var setjson = JSON.stringify(oidlist);
            localStorage.setItem(key, setjson);

            togleitem(oid,'remove');
        }
    }
}
</script>

</body>
</html>

コメント欄@il9437さんから他の書き方もご提示いただきました。

〜〜解説〜〜

javascriptの処理を大きく4つの機能に分けて解説していきます。

【解説Aパート】 表示ソースの切り替え

function togleitem(oid,event){
    if(event == 'add'){  // 引数の[event]が`add`の時
        // 未チェック(class="add")を非表示にして、チェック済(class="remove")を表示する
        $('li.add[no=' + oid + ']').hide();
        $('li.remove[no=' + oid + ']').show();
    } else if(event == 'remove'){  // 引数の[event]が`remove`の時
        // チェック済(class="remove")を非表示にして、未チェック(class="add")を表示する
        $('li.add[no=' + oid + ']').show();
        $('li.remove[no=' + oid + ']').hide();
    }
}

パートAでは別段ややこしい処理を書いているわけではありませんが、no属性とクラス(addもしくはremove)で特定の<li>を見つけだし、eventが[add]ならお気に入りに追加済みの見た目に変更し、逆に[remove]なら未追加の表示にします。

【解説Bパート】 ページロード時にローカルストレージの中をチェック

$(function(){
  $('ul .remove').hide();  // お気に入り中の表示は一時的に非表示に。

  // お気に入りリストに存在するか確認
  var key = 'お気に入りID';  // ローカルストレージのキーを指定
  var getjson = localStorage.getItem(key);  // ローカルストレージから値を取得
  var oidlist = JSON.parse(getjson);        // JSON形式から配列に直す
  if(oidlist != null){                      // 配列が空でなければ
    oidlist.forEach( function( oid ) {      // 配列の中身を1つずつ[oid]に入れてループを回す
      togleitem(oid,'add');                 // togleitem()関数を動かす ※パートAで書いたやつ
    });
  }

})

パートBでは、ページロードした時に過去にお気に入りにチェックを入れた情報を元に表示するソースをお気に入り状態に変更する処理が書いています。

まず、2行目でデフォルトの状態(つまり全てお気に入り非チェック)にします。そして、ローカルストレージからお気に入りリストを取得してきて、もしお気に入りリストが登録されていれば、登録されているIDの<li>はチェック済の状態に切り替える。という処理を書いています。

【解説Cパート】 お気に入りに追加機能

function addfav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist == null){
        // 初めて「お気に入りID」というキーがローカルストレージに登録される時の処理
        oidary = new Array(oid);               // 初めてなので、新しい配列を作成
        var setjson = JSON.stringify(oidary);  // 配列をJSON形式に変換
        localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

        togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え

    } else {
        // 既に「お気に入りID」というキーが存在する時
        if(oidlist.indexOf(oid) == -1){
            // 且つ、まだお気に入りIDに登録されていない時
            oidlist.push(oid);                     // 配列にクリックされたIDを追加
            var setjson = JSON.stringify(oidlist); // またJSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージにお気に入りIDを保存

            togleitem(oid,'add');                  // クリックされた対象の<li>の表示を切り替え
        }
    }
}

パートCでは、お気に入りにIDを登録(保存)する時の処理をかいています。

まず、お気に入りが追加されるシーンとしては2パターンあります。

1.ローカルストレージに初めてお気に入りIDが登録される時
2.既にお気に入りリストが登録されていて、他のIDがさらに追加される時

この2パターンがあるため、まず最初にローカルストレージの中身を確認し、「お気に入りID」というキーが既に登録されているかどうかをif文で分岐させます。
まだ登録されていなければ、新しく「お気に入りID」というキーを作成し、値にクリックしたIDを入れます。
既に登録されているのであれば、取得したお気に入りリストの配列の最後にクリックしたIDを追加します。
どちらのパターンでもパートAの関数を使い、ソースを切り替えて完了です。

【解説Dパート】 お気に入りから削除機能

function removefav(oid){

    var key = 'お気に入りID';                       // キーの名前を指定

    // ローカルストレージから値を取得
    var getjson = localStorage.getItem(key);
    var oidlist = JSON.parse(getjson);

    if(oidlist != null){
        // 「お気に入りID」というキーが存在した時
        var checkitem = oidlist.indexOf(oid);     // 配列の何番目に該当のIDがあるかを見る
        if(checkitem != -1){
            // 「お気に入りID」の中に該当のIDが見つかった時
            oidlist.splice( checkitem, 1 );   // 配列から該当IDを削除
            var setjson = JSON.stringify(oidlist); // JSON形式に変換
            localStorage.setItem(key, setjson);    // ローカルストレージに保存

            togleitem(oid,'remove');               // クリックされた対象の<li>の表示を切り替え
        }
    }
}

ここまで読んでいる方であればほとんど説明は不要かもしれません。

削除をする場合は、配列の何番目にIDがあるかを調べないといけないので、indexOf()を使って、配列の何番目に該当IDがあるのかを調べます。
あとはspliceを使って該当IDを削除し、またJSON形式にしてからローカルストレージに保存。その後、ソースの表示を切り替えています。

おまけ php編 〜wordpressの投稿IDに応用〜

ここからは初級〜中級者向けのwordpressでお気に入り機能を自作した人向けの内容になります。

wordpressでローカルストレージを使ったお気に入り機能を実装

仕組み

1.onclickでリンク先を指定して起動
2.ローカルストレージからデータを取得
3.取得したデータをpostで渡す
4.postのデータを受け取って変数に格納
5.変数のIDを使ってwordpressのループを作成

xxxx.php
<!-- 飛び先のURLを引数に指定 -->
<div onclick="oidlisttrans('http://hogehoge.com/xxx/yyyy.php')">お気に入りリストを見る</div>

<script>
    // ローカルストレージにあるparamsのデータをpostで送信
    function post(path, params, method='post') {
        const form = document.createElement('form');
        form.method = method;
        form.action = path;
        for (const key in params) {
            if (params.hasOwnProperty(key)) {
                const hiddenField = document.createElement('input');
                hiddenField.type = 'hidden';
                hiddenField.name = key;
                hiddenField.value = params[key];
                form.appendChild(hiddenField);
            }
        }
        document.body.appendChild(form);
        form.submit();
    }

    // お気に入りリストにデータをpost()を使って送信
    function oidlisttrans(link,data='all'){
        var key = 'お気に入りID';
        var getjson = localStorage.getItem(key);
        var oidlist = JSON.parse(getjson);
        obj = {
            'IDリスト': oidlist
        };
        post(link, obj);
    }


飛び先のページ(上と同一ページでも可能)

yyyy.php
<?php
$idlist = explode( ',', $_POST['IDリスト'] );

$wp_query = new WP_Query();
$args = array(
    'post_status' => 'publish',
    'post__in' => $idlist,
    'orderby' => 'post__in',
);
$wp_query->query($args);
?>
<?php if (have_posts()) : ?>
    <?php while (have_posts()) : the_post(); ?>
        //ループ内の処理を自由に書く
    <?php endwhile; ?>
<?php else: ?>
    // 投稿がなかった時の処理
<?php endif; ?>

こんな感じの書き方でwordpressでも応用して使えるかな?

ちょっと長丁場になりましたがいろいろ使い道があると思います!

まとめ

長くなるためCSSはほとんど省きましたが、自由に表示を切り替えたり見た目を変更できるので、
好きな位置に好きなボタンを配置できるので、プラグインなどを使ったけどうまくいかない方の参考に少しでもなればいいかなと思います。

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】HTML・CSSのちょいテク詰め合わせ

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

ChromeOSでPHP開発

ChromeOSのLinux(Crostini)を使って開発マシンに仕上げる方法のメモ。

(メモ) PlayStoreが使える環境のChromeOSであればAndroidアプリのTermuxを使うほうが開発環境を作るには適していると思った。こちらを参照♪

以下、ChromeOS 86に基づいた内容。

[1] Linux(Crostini)を有効に

ChromeOSの設定から、Linux(beta)「Crostini」を有効に。

[2] Port転送を有効に

Port転送で例えば、8888を有効にしておく。

[3] aptでPHPをインストール

sudo apt update
sudo apt install php php-mbstring php-sqlite3

[4] phpのテストサーバーを起動

開発したいWebアプリのディレクトリにcdで移動。
その上で、PHPの開発用サーバーをポート8888で起動する。

php -S 0.0.0.0:8888

[5] 自身のIPアドレスを調べる

自身のIPアドレスを調べる。

sudo ifconfig | grep "inet "

[6] ChromeOSのブラウザでアクセス

ChromeOSのブラウザで上記IPアドレスにアクセス。

[7] vimなどで開発

vimなどで開発してブラウザを更新すれば、反映される。

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

【Laravel】whereHasでリレーションのネスト先を検索条件として指定する方法。

自分用のメモとして残します。

リレーションのネスト先。
言い方を変えるとリレーション先のリレーション?のフィールドをwhereHasで検索する方法です。

■やり方

リレーションの定義は割愛させていただきます。
withと同じようにピリオドでリレーションのチェーンをします。

$query->whereHas('user.phone', function($query) use($cond) {
    $query->where('phonename', $cond['phonename']);
});

 

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

PHPで日、月、年の加算と減算方法

特定の日付を元に明日の日付や1週間後の日付、一時間後の時間などの日時の加算や、昨日の日付や昨年の日付などの日時の減算の方法です。
PHPのstrtotime関数やDateTimeクラスを使えば簡単に取得できます。

※先月の日付を取得する際は、月末日の関係でおかしな日付になることもあるので注意!

strtotime関数

strtotime関数の詳細はこちらを参考にしてください。
YYYY-mm-ddは元となる日付です。

PHPで日付の加算をする

echo date("Y-m-d", strtotime("YYYY-mm-dd 1 day"));
echo date("Y-m-d", strtotime("YYYY-mm-dd 1 month"));
echo date("Y-m-d", strtotime("YYYY-mm-dd 1 year"));
echo date("Y-m-d", strtotime("YYYY-mm-dd 1 week"));

PHPで日付の減算をする

echo date("Y-m-d", strtotime("YYYY-mm-dd -1 day"));
echo date("Y-m-d", strtotime("YYYY-mm-dd -1 month"));
echo date("Y-m-d", strtotime("YYYY-mm-dd -1 year"));
echo date("Y-m-d", strtotime("YYYY-mm-dd -1 week"));

DateTimeクラス

DateTimeクラスの詳細はこちらを参考にしてください。
YYYY-mm-ddは元となる日付です。

PHPで日付の加算をする

$date = new DateTime('YYYY-mm-dd');
$date->modify('+1 day');
$date->modify('+1 week');
$date->modify('+1 months');
$date->modify('+1 years');

echo $date->format('Y-m-d');

PHPで日付の減算をする

$date = new DateTime('YYYY-mm-dd');
$date->modify('-1 day');
$date->modify('-1 week');
$date->modify('-1 months');
$date->modify('-1 years');

echo $date->format('Y-m-d');

フォーマット一覧

フォーマット 説明
now 現在の日時を取得
tomorrow 明日の日付を取得
yesterday 昨日の日付を取得
+1 day 1日後の日時を取得
+1 week 1週間後の日時を取得
+1 month 1ヶ月後の日時を取得
+1 year 1年後の日時を取得
+1 hour 1時間後の日時を取得
+1 minute 1分後の日時を取得
+1 second 1秒後の日時を取得
last day of previous month 先月の末日を取得
項目 フォーマット 説明 サンプル
Y 西暦(4桁) 2015
y 西暦(2桁) 15
L うるう年→1、普通の年→0 0
m 月(2桁) 07
n 月(先頭の0なし) 7
M 英語(略語) jul
F 英語 july
d 日(2桁) 09
j 日(先頭の0なし) 9
t その月の日数 31
z その年の経過日数 121
曜日・週 D 英語(略語) Tue
l 英語 Tuesday
w 曜日(日曜0→土6) 2
W その年の経過週(月曜開始) 28
時間 H 24時間単位 09
G 24時間単位(先頭の0なし) 9
h 12時間単位 09
g 12時間単位(先頭の0なし) 9
a 午前/午後(小文字) am
A 午前/午後(大文字) AM
i 分(2桁) 09
s 秒(2桁) 09

参考サイト

https://blog.codecamp.jp/php-datetime-calc
https://www.flatflag.nir87.com/modify-495

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

Laravel gmail Connection could not be established with host smtp.gmail.com 原因と対策

laravelでメールを送りたい

gmailを使おうとすると

Connection could not be established with host smtp.gmail.com :stream_socket_client(): SSL operation failed with code 1. OpenSSL Error

というエラーが発生するようになった。
ちょっと前までは同じ設定でうまく言っていたのに突然使えなくなった。

設定確認

laravel

\.env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=xxxx@gmail.com
MAIL_PASSWORD=xxxx
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=xxxx@gmail.com
MAIL_FROM_NAME=xxxx@gmail.com

gmail

安全性の低いアプリは許可
https://myaccount.google.com/lesssecureapps

原因は?

アバスト無料アンチウイルスのスキャンでした。:sweat_smile:
image.png
設定から送信メールのスキャン(smtp)を外せば解決しました。
image.png

フリーソフトに紛れ込むことが多いアバストアンチウイルスですが
こいつがメールをスキャンするから失敗するようになっていたのでした。
不要な人はアンインストールでも解決するでしょうね。
誰かの参考になれば幸いです。

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

TwitterOAuthを使用してフォロー,RT,いいねをする

前提

https://twitteroauth.com/
のドキュメントを参考にcomposerからインストール
https://developer.twitter.com/
から申請しAPIキーを取得

(最低限の)前提知識

スクリーンネームとユーザーID
・スクリーンネーム -> @hogehoge 的なID,いつでも変更できる
・ユーザーID -> 絶対に変えられないID  ※今回はユーザーIDを利用する
・API(アプリケーションインターフェース) -> 認可サーバーのID
・アクセストークン ->twitterが出した「ここまでの範囲なら利用していいよ!」ってやつ
詳しくはhttps://booth.pm/ja/items/1296585
の書籍を参考にしてください

test.php
<?php
require "vendor/autoload.php";
use Abraham\TwitterOAuth\TwitterOAuth as TwitterOAuth;

$consumerKey       = "#########";
//APIキーを記述
$consumerSecret    = "######################################";
//シークレットAPIキーを記述
$accessToken       = "############################################";
//自分もしくは他人のアクセストークン
$accessTokenSecret = "###########################################";
//自分もしくは他人のシークレットアクセストークン

$twitter = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);



$id ='################';
//いいね、RT対象のstatusIDを記述
$user_id ='#####################';
//フォローする対象のユーザーIDを記述

$retweet = $twitter->post('statuses/retweet',['id' => $id]);
//RTする
$favorites = $twitter->post('favorites/create', ['id' => $id]);
//いいねする
$follow = $twitter->post('friendships/create', ['user_id'=> $user_id]);
// フォローする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP クラス名::classはどういう処理??

目的

  • 実務のコードでクラス名::classという処理を見つけてパッと意味がわからなかったので備忘録としてまとめる。

すみません

  • 備忘録としてのメモ的要素が大きいため多少見ずらい記事になるかもしれません。すみません。

クラス名::classとは

  • 公式ドキュメントによると名前を解決しているらしい。
  • 簡単に言うとクラスの場所を出力してくれるキーワードである。
  • 例えば下記のようにPHPのコードが書かれていたとする。

    <?php
    
    use App\Models\Test;
    
    class Foo
    {
        echo Test::class;
    } 
    
  • 上記を実行した際に下記が出力される。

    App\Models\Test
    
  • 若干違うがターミナルでファイル名を指定してコマンド$ pwdを実行したときと似ていると感じた。(ファイルを指定してコマンド$ pwdを実行すると指定されたファイルのフルパスが出力される。)

  • クラス名::classはuse宣言で保持しているクラス名情報を返してくれるものである。

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

Twitter API with PHP + WordPress の Tips

Twitterまとめのまとめサイトを作りました (2ちゃんねるまとめのまとめを参考にしています)。このサイト作りから得たTipsをまとめます。

Twitter API 関連

Twitter APIの始め方 (WordPress/PHP環境)

別途、こちらの記事を参照ください (別サイトです)。

Twitter検索+時間指定

検索ワードにsinceとuntilを併せて指定することで、時間指定をかけたTwitter検索ができます (参考)。
下記の例では、現時刻から8時間前までのツイートを検索しています (9時間引いているのは、日本時間から協定世界時へ調整するためです)。

$dateImmu = new DateTimeImmutable();
$until = $dateImmu->modify('-9 hours')->format('Y-m-d_H:i:s') . '_UTC';
$since = $dateImmu->modify('-17 hours')->format('Y-m-d_H:i:s') . '_UTC'

$connection = getToken($consumerkey, $consumersecret, $accesstoken, $accesstokensecret);
$tweets = $connection->get("search/tweets",
                           ["q" => "since:" . $since . " until:" . $until,
                            "result_type" => "recent",
                            "lang" => "ja",
                            "count" => 100]);

PHP 関連

文章から最後のhttpsリンクを取り除く

下記のように、文章の最後にhttpsリンクがくっついているものを考えます。

【ニュース】『スマブラSP』に参戦した『マイクラ』スティーブの「股間」に注目集まる。ソレにしか見えない人々 https://t.co/XtIRzKfHpQ

このhttps表記だけを、文章から取り除きます。

【ニュース】『スマブラSP』に参戦した『マイクラ』スティーブの「股間」に注目集まる。ソレにしか見えない人々

$t = explode('https://t.co/', $text);
if (count($t) == 1) {
  $t = $t[0];
} else {
  $t = implode(array_slice($t, 0, count($t)-1));
}

WordPress / HTML 一般

フォルダの場所

WordPressの「外観→テーマの編集」のファイルが入っているフォルダは、/home/[サーバーID]/[独自ドメイン名]/public_html (Xserverの場合)。

functions.phpを直接編集したい、なんて思ったときに。

<?php
echo getcwd() . "\n";
?>

サーバー上ファイルの編集方法

SCPでサーバー上のファイルをローカルにコピーをして、編集後にそれをSCPコマンドでサーバーへ返すことができる。SCPを使うためには、SSHが利用可能でないといけない。準備方法はこちら (別サイトです)。

viが嫌いな人向け。

これでサーバー→ローカルへのコピー。

scp -i [xxx.key] -P 10022 [サーバーID]@[ホスト名]:/home/[サーバーID]/[独自ドメイン名]/public_html/wp/wp-content/themes/[テーマ名]/functions.php ./

これでローカル→サーバーへのコピー。

scp -i [xxx.key] -P 10022 ./functions.php [サーバーID]@[ホスト名]:/home/[サーバーID]/[独自ドメイン名]/public_html/wp/wp-content/themes/[テーマ名]/functions.php

画面遷移なしの検索フォーム

formで自ブロック内のidを指定して上げれば、擬似的に画面遷移なしで検索ができる。

<h2> <a id='search'> 検索ポイント </a> </h2>
<form action = "#search" method = "get">
<input type = "text" name = "query" class="textbox">
<input type = "submit" value ="検索" class="btn-submit">
</form>

<?php   
if(isset($_GET['query'])){
  echo "You searched " . $_GET['query'];

} else {
  echo "No Input";
}
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

phpの実行開始が遅い際、どのiniの読み込みが遅いのかを特定する

phpの実行開始が遅い場合の対処 という記事にあったのと同じ状況になりました。

(以下引用)

php -v
php -n -v // -nはiniファイルを読まないオプション

この2つの実行で体感で分かるぐらい速度差がある時は特定のPHP拡張の初期化が原因です。liipのPHP v7.1.7とMacOS Sierraで問題があり1秒ぐらいかかってました。

どのiniの読み込みに時間がかかっているのかを確認する

大雑把な方法ですが、この方法で特定ができました。

php.iniの情報から、読み込んでいるiniを確認する

$ php --ini
Configuration File (php.ini) Path: /etc/php/7.2/cli
Loaded Configuration File:         /etc/php/7.2/cli/php.ini
Scan for additional .ini files in: /etc/php/7.2/cli/conf.d
Additional .ini files parsed:      /etc/php/7.2/cli/conf.d/10-opcache.ini,
/etc/php/7.2/cli/conf.d/10-pdo.ini,
/etc/php/7.2/cli/conf.d/15-xml.ini,
/etc/php/7.2/cli/conf.d/20-bz2.ini,
/etc/php/7.2/cli/conf.d/20-calendar.ini,
...

該当ディレクトリ以下のファイルをすべて標準出力し、'.so'でgrepする

$ cat /etc/php/7.2/cli/conf.d/* | grep '\.so'
zend_extension=opcache.so
extension=pdo.so
extension=xml.so
extension=bz2.so
extension=calendar.so
extension=ctype.so
extension=dom.so

出力をfile等に流し込んで、以下のようなコマンドとして編集して実行する

$ cat /etc/php/7.2/cli/conf.d/* | grep '\.so' > file
$ vim file

こんなコマンド群を作成.以下の記事を参考に.
https://github.com/liip/build-entropy-php/issues/11#issuecomment-59716269

$ cat file 
time php -n -d zend_extension=opcache.so -v 
time php -n -d extension=pdo.so -v
time php -n -d extension=xml.so -v

コピーしてコンソールにペーストして流し込み

$ time php -n -d zend_extension=opcache.so -v 
PHP 7.2.24-0ubuntu0.18.04.7 (cli) (built: Oct  7 2020 15:24:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.24-0ubuntu0.18.04.7, Copyright (c) 1999-2018, by Zend Technologies

real    0m0.027s
user    0m0.022s
sys 0m0.006s
$ time php -n -d extension=pdo.so -v
PHP 7.2.24-0ubuntu0.18.04.7 (cli) (built: Oct  7 2020 15:24:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

real    0m0.012s
user    0m0.006s
sys 0m0.006s
$ time php -n -d extension=xml.so -v
PHP 7.2.24-0ubuntu0.18.04.7 (cli) (built: Oct  7 2020 15:24:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

real    0m0.008s
user    0m0.000s
sys 0m0.008s

iniファイル読み込みごとの実行時間が確認できます。時間のかかっている箇所があれば、php.ini からその箇所の設定をコメントアウト、再実行してみて、結果を確認してみてください。

注意点

  • .so のファイルによっては、依存関係他よくわからない理由で読み込みが失敗したりするのを確認しています。

これとか

time php -n -d extension=xsl.so -v
PHP Warning:  PHP Startup: Unable to load dynamic library 'xsl.so' (tried: /usr/lib/php/20170718/xsl.so (/usr/lib/php/20170718/xsl.so: undefined symbol: dom_node_class_entry), /usr/lib/php/20170718/xsl.so.so (/usr/lib/php/20170718/xsl.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
PHP 7.2.24-0ubuntu0.18.04.7 (cli) (built: Oct  7 2020 15:24:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

real    0m0.007s
user    0m0.007s
sys 0m0.000s

ファイルは存在するんですけどね。ライブラリ読み込みにおける依存関係によるものかなぁと推測していますが、しっかりとは確認していません。

$ ls -l /usr/lib/php/20170718/xsl.so 
-rw-r--r-- 1 root root 30824 10月  8 00:24 /usr/lib/php/20170718/xsl.so
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kylekatarnls/business_dayライブラリのdiffInBusinessDaysメソッドの挙動について

Carbonに組み込んで利用できるPHPライブラリ、business-dayについてです。

diffInBusinessDays という、日数の差を取得するメソッドの境界について調べました。

各サンプルコードは、READMEにあったものをちょちょいと書き換えたものです。

0日と数時間のずれが生じるパターン

ドキュメントにあった、00:00:00 と 23:59:59 を比較するパターンです。
1日の差として計算されます。

$days = Carbon::parse('2019-06-10')->diffInBusinessDays(Carbon::parse('2019-06-10')->endOfDay());
echo "If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you $days days of paid vacation.\n";

> If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you 1 days of paid vacation.

完全一致パターン

時刻が完全に一致する場合は、0日と計算されます。

$days = Carbon::parse('2019-06-10')->diffInBusinessDays(Carbon::parse('2019-06-10'));
echo "If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you $days days of paid vacation.\n";

> If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you 0 days of paid vacation.

0日と数時間のずれが生じるパターン(負の方向)

ズレが負の方向、-1日などの方向に振れる場合についても確認します。
方向が正でも負でも、絶対値で日数が取得されることがわかります。

$days = Carbon::parse('2019-06-11')->diffInBusinessDays(Carbon::parse('2019-06-10')->endOfDay());
echo "If you ask to leave from 2019-06-11 to 2019-06-10, it will cost to you $days days of paid vacation.\n";

> If you ask to leave from 2019-06-11 to 2019-06-10, it will cost to you 1 days of paid vacation.

境界値を探る

時刻が一致する場合は差分としての日数はインクリメントされず、時刻が少しでも大きくなった場合に、日数がインクリメントされるようです。

つまり、境界線は以下のようになります。

-2 <= x < -1    : 2
-1 <= x < 0     : 1
0               : 0
0 < x <= 1      : 1
1 < x <= 2      : 2

図にすると以下のようになります。

2   1   0   1   2
|---|---|---|---|
≦  ≦  =   ≦  ≦

Carbonの diffInDays メソッドとは境界が異なるようですので、注意しましょう。

$days = Carbon::parse('2019-06-10')->diffInDays(Carbon::parse('2019-06-10')->endOfDay());
echo "If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you $days days of paid vacation.\n";

> If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you 0 days of paid vacation.

時刻に関係なく、カレンダー上の日数を比較したい場合は

単純にカレンダー上の日数の差を取得したい場合は、各日時の時刻を合わせて比較すると混乱がなくてよいです。
この方法に気づいてから、日時の比較において考えなければいけないことがグッと減りました。

$from_day = Carbon::parse('2019-06-10');
$to_day = Carbon::parse('2019-06-11')->endOfDay();
$days = (clone $from_day)->setTime(0,0,0)->diffInDays($to_day->setTime(0,0,0));
echo "If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you $days days of paid vacation.\n";

> If you ask to leave from 2019-06-10 to 2019-06-10, it will cost to you 1 days of paid vacation.

元の時刻が変わってしまうと不都合があるケースが多いので、クローンしたオブジェクトを操作しています。

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

[CodeIgniter]バリデーションで独自バリデーションに引数を2つ以上渡す方法

はじめに

実務でCodeIgniterを使用する機会があり、バリデーションの処理で独自バリデーションに引数を2つ以上渡すのに苦戦し、その時に解決した方法をまとめました。

解決方法

手順1 独自バリデーションに渡したい値をJSON形式で変数に格納

// 今回は送信されたデータ4つを渡したい
$json_param = [
    'user_id"   => $this->input->post('user_id'),
    'user_name' => $this->input->post('user_name'),
    'password'  => $this->input->post('password'),
    'email'     => $this->input->post('email')
];

手順2 user_name_checkルールを作成し、独自バリデーションcallback_user_name_checkを作成する

// callback_user_name_checkの引数に1で作成した$json_paramを指定
$this->form_validation->set_rules('user_name', 'ユーザ名', 'callback_user_name_check['.json_decode($json_param).']');

手順3 独自バリデーション先でパラメータを受け取る

public function user_name_check($user_name, $json_data) {
    // JSON⇒オブジェクト型に変換
    $data = json_decode($json_data);

    // キーにアクセスし、変数に格納
    $user_id  = $data->user_id;
    $password = $data->user_name;
    $email    = $data->email;
}

user_name_checkメソッドの第1引数を$user_nameに指定しているのはCodeIgniterの仕様上で、第1引数は必ず検証ルールで作成したフィールドのデータが渡されるからです。

参考

CodeIgniter公式

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

PHPのDOMを使いやすくする

はじめに

PHPは標準でDOMが使えますが、とても使いにくく、ほとんど使われていません。
機能が不足していたり、冗長な書き回しになったり、とにかく不便です。

そこで、PHPのDOMを使いやすくするために、自作のプログラムを作りました。
Web標準であるDOMに慣れ親しんだ人なら、簡単に使えるものを目標としています。

技術的には、PHPの DOMDocumentDOMElement拡張したものになっています。

documentオブジェクト

最初に document オブジェクトを作成します。
ベースとなるHTML文字列を渡してください。

$document = new document('<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title></title></head><body></body></html>');
  • ベースとなるHTMLには<!DOCTYPE>を忘れずに記述してください
  • HTMLは、全体を1つのタグで囲っておく必要があります
  • 引数省略時は内蔵テンプレートが使用されます。上記サンプルと同じHTMLです

HTMLの出力

documentオブジェクトは文脈により文字列に変換されるので、出力するだけでHTMLを表示できます。

$document = new document();

print $document; // HTMLが表示される

$html = "$document"; // 文字列としてコピーされる

DOM要素も同様に、自動的に文字列に変換されます

print $document->body; // <body></body> が表示される

id付タグの短縮構文

idが付いているタグには$document->id名でアクセスできます。
操作したいタグにはあらかじめidを付けておくと、綺麗なコードが書けます。

$document->exapmle  // $document->getElementById('example') の短縮形

この値はDOM要素です。存在しなければnull

主要タグの短縮構文

<html> <head> <title> <body>の主要タグには短縮構文があり、$document->タグ名でアクセスできます。

$document->body  // $document->getElementsByTagName('body')[0] の短縮形

$document->title->textContent = 'タイトル名'; //titleはweb標準と異なるので注意

この値はDOM要素です。存在しなければnull

属性アクセスの短縮構文

属性へのアクセスは短縮構文があり、$el->属性名と書けます。

$body = $document->body;

$body->id            // $body->getAttribute('id') の短縮形
$body->id = 'value'  // $body->setAttribute('id', 'value') の短縮形
unset($body->id)     // $body->removeAttribute('id') の短縮形

タグの検索

セレクタで検索

documentオブジェクトには、JavaScriptでお馴染みのquerySelectorがあります。
CSSセレクタを使用して、ドキュメント全体からタグを検索できます。

//単数検索
$document->querySelector('CSSセレクタ')    //戻り値はDOM要素またはnull

//複数検索
$document->querySelectorAll('CSSセレクタ') //戻り値は常に配列

対応セレクタ

検索対象 セレクタ
タグ名 a
id #a
class .a
属性名 [a]
属性名と値 [a="b"]
and ab
子孫 a b
a > b
a + b
弟全て a ~ b
全て *

querySelector()の短縮構文

querySelector()には短縮構文が用意されていて、jQuery風に書けます。$document('セレクタ')

$document('.example')   // $document->querySelector('.example') の短縮形

デフォルト動作は単数検索です。複数検索するには、セレクタの頭に*を付けます。

$document('*.example')  // $document->querySelectorAll('.example') の短縮形

タグの作成

タグを1つ作成する短縮構文

タグの作成には短縮構文があり$document(<タグ名>, 本文, [属性名=>属性値])で1つタグが作成できます。
戻り値はDOM要素で、本文と属性は省略できます。

// <div class="example">sample</div> を作る
$document('<div>', 'sample', ['class'=>'example']); 

// 次のコードの短縮形
// $div = $document->createElement('div');
// $div->textContent = 'sample';
// $div->setAttribute('class', 'example');

子タグも一括作成

<ul> <ol> <select> <table> のタグは、本文を配列で渡すと、子タグも一括して作成できます。

ulタグ
$document('<ul>', [1,2], ['id'=>'example']);
//<ul id="example">
//  <li>1</li>
//  <li>2</li>
//</ul>
selectタグ
$document('<select>', [1,2]);
//<select>
//  <option value="1">1</option>
//  <option value="2">2</option>
//</select>
tableタグ
$document('<table>', [1,2]);
//<table>
//  <tr><td>1</td></tr>
//  <tr><td>2</td></tr>
//</table>

$document('<table>', [[1,2], [3,4]]);
//<table>
//  <tr><td>1</td><td>2</td></tr>
//  <tr><td>3</td><td>4</td></tr>
//</table>

HTML文字列をDOM化

HTML文字列を一括してDOM化するには$document('HTML文字列')で可能です。
戻り値はDocumentFragment。これはappendChild()などに渡すと挿入できます。

$fragment = $document('<footer><a href="http://example.com/">Home</a></footer>');

$document->body->appendChild($fragment); // <body>の最後に<footer>が挿入される

[付録] DOM操作の早見表

DOMに不慣れな人でも次の早見表を使えば大丈夫?

//タグを検索する
$document->querySelector('セレクタ');    // 戻り値はDOM要素またはnull
$document->querySelectorAll('セレクタ'); // 戻り値は配列

//タグを作成する
$el = $document->createElement('タグ名');// 戻り値はDOM要素


//中身を取得する
$el->textContent; //戻り値はタグを含まない文字列

//中身を取得する
$el->innerHTML;   //戻り値はタグを含む文字列


//中身を変更する
$el->textContent = '文字列はエスケープされる';

//中身を変更する
$el->innerHTML   = '文字列はエスケープされない';

//中身を全て削除する
$el->textContent = '';


//タグ名を取得する
$el->tagName; //戻り値は小文字

//属性を取得する
$el->getAttribute('属性名'); //戻り値は文字列

//属性を設定する
$el->setAttribute('属性名', '属性値');

//属性を削除する
$el->removeAttribute('属性名');


//タグを子の最後に追加する
$el->appendChild($new_el);

//タグを子の最初に追加する
$el->insertBefore($new_el, $el->firstChild);

//タグを前に追加する
$el->parentNode->insertBefore($new_el, $el);

//タグを後ろに追加する
$el->parentNode->insertBefore($new_el, $el->nextSibling);


//タグを置換する
$el->parentNode->replaceChild($new_el, $el);

//タグを削除する
$el->parentNode->removeChild($el);

//タグをコピーする
$el->cloneNode(true); //戻り値はDOM要素


//タグの子孫からタグを検索する
$el->querySelector('セレクタ');    //戻り値はDOM要素またはnull
$el->querySelectorAll('セレクタ'); //戻り値は配列

※PHP8から最新のDOMが使えるようになり、タグの追加・置換・削除が簡単になります。参考

ソースコード

クラスが2つです。ご自由にお使いください。

class document extends \DOMDocument{ // https://www.php.net/manual/ja/class.domdocument.php

    function __construct($str = '<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title></title></head><body></body></html>'){
        parent::__construct();
        $this->registerNodeClass('\DOMElement','HTMLElement');
        libxml_use_internal_errors(true);

        $html = substr($str, strpos($str, '<'));
        if($html[1] === '!'){
            $this->contents_type = 'html';
            $this->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_COMPACT);
        }
        else if($html[1] === '?'){
            $this->contents_type = 'xml';
            $this->loadXML($html, LIBXML_NONET | LIBXML_COMPACT); // https://www.php.net/manual/ja/libxml.constants.php
        }
        else{
            $html = '<?xml encoding="utf-8">' . $str;
            $this->contents_type = 'fragment';
            $this->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_COMPACT);
        }
    }


    function __get($name){
        if(in_array($name, ['html','head','body','title'], true)){
            return $this->getElementsByTagName($name)[0];
        }
        else{
            return $this->getElementById($name);
        }
    }


    function __invoke($selector, $text = null, $attr = []){
        if($selector instanceof self){
            return $this->importNode($selector->documentElement, true);
        }
        else if($selector instanceof \DOMNode){
            return $this->importNode($selector, true);
        }
        else if(preg_match('/</', $selector)){
            if(preg_match('/^<([\w\-]+)>$/', $selector, $m)){
                return $this->createHTMLElement($m[1], $text, $attr);
            }
            else{
                return self::createFragment($this, $selector);
            }
        }
        else if($selector[0] === '*'){
            if(strlen($selector) > 1){
                $selector = substr($selector, 1);
            }
            return $this->querySelectorAll($selector, $text);
        }
        else{
            return $this->querySelectorAll($selector, $text)[0];
        }
    }


    function __toString(){
        $this->formatOutput = true;

        if($this->contents_type === 'html'){
            return $this->saveXML($this->doctype) . "\n" . $this->saveHTML($this->documentElement);
        }
        else if($this->contents_type === 'xml'){
            return $this->saveXML($this->doctype) . "\n" . $this->saveXML($this->documentElement);
        }
        else{
            return $this->saveHTML($this->documentElement);
        }
    }


    function querySelector($selector, $context = null){
        return $this->querySelectorAll($selector, $context)[0];
    }


    function querySelectorAll($selector, $context = null){
        $xpath    = new \DOMXPath($this);
        $selector = self::selector2xpath($selector, $context);
        return iterator_to_array($xpath->query($selector, $context));
    }


    private function createHTMLElement($tagName, $text = '', $attr = []){
        $el = $this->createElement($tagName);
        foreach($attr as $k => $v){
            $el->setAttribute($k, $v);
        }

        if(is_array($text)){
            if($tagName === 'table'){
                $el = $this->createTableElement($el, $text);
            }
            else if($tagName === 'select'){
                $el = $this->createSelectElement($el, $text);
            }
            else if($tagName === 'ol' or $tagName === 'ul'){
                $el = $this->createListElement($el, $text);
            }
        }
        else{
            $el->textContent = $text;
        }

        return $el;
    }


    private function createListElement($el, array $contents){
        foreach($contents as $v){
            $child = $this->createElement('li', $v);
            $el->appendChild($child);
        }
        return $el;
    }


    private function createSelectElement($el, array $contents){
        foreach($contents as $v){
            $child = $this->createElement('option', $v);
            $child->setAttribute('value', $v);
            $el->appendChild($child);
        }
        return $el;
    }


    private function createTableElement($el, array $contents){
        foreach($contents as $row){
            $tr = $this->createElement('tr');
            $el->appendChild($tr);
            foreach((array)$row as $cell){
                $td = $this->createElement('td', $cell);
                $tr->appendChild($td);
            }
        }
        return $el;
    }


    static function createFragment($document, $str){
        $fragment = $document->createDocumentFragment();
        $dummy    = new self("<dummy>$str</dummy>");
        foreach($dummy->documentElement->childNodes as $child){
            $fragment->appendChild($document->importNode($child, true));
        }
        return $fragment;
    }


    static function selector2xpath($input_selector, $context = null){
        $selector = trim($input_selector);
        $last     = '';
        $element  = true;
        $parts[]  = $context ? '' : '//';
        $regex    = [
            'element'    => '/^(\*|[a-z_][a-z0-9_-]*|(?=[#.\[]))/i',
            'id_class'   => '/^([#.])([a-z0-9*_-]*)/i',
            'attribute'  => '/^\[\s*([^~|=\s]+)\s*([~|]?=)\s*"([^"]+)"\s*\]/',
            'attr_box'   => '/^\[([^\]]*)\]/',
            'combinator' => '/^(\s*[>+~\s,])/i',
        ];

        $pregMatchDelete = function ($pattern, &$subject, &$matches){ // 正規表現でマッチをしつつ、マッチ部分を削除
            if (preg_match($pattern, $subject, $matches)) {
                $subject = substr($subject, strlen($matches[0]));
                return true;
            }
        };

        while (strlen(trim($selector)) && ($last !== $selector)){
            $selector = $last = trim($selector);

            // Elementを取得
            if($element){
                if ($pregMatchDelete($regex['element'], $selector, $e)){
                    $parts[] = ($e[1] === '') ? '*' : $e[1];
                }
                $element = false;
            }

            // IDとClassの指定を取得
            if($pregMatchDelete($regex['id_class'], $selector, $e)) {
                switch ($e[1]){
                    case '.':
                        $parts[] = '[contains(concat( " ", @class, " "), " ' . $e[2] . ' ")]';
                        break;
                    case '#':
                        $parts[] = '[@id="' . $e[2] . '"]';
                        break;
                }
            }

            // atribauteを取得
            if($pregMatchDelete($regex['attribute'], $selector, $e)) {
                switch ($e[2]){ // 二項(比較)
                    case '!=':
                        $parts[] = '[@' . $e[1] . '!=' . $e[3] . ']';
                        break;
                    case '~=':
                        $parts[] = '[contains(concat( " ", @' . $e[1] . ', " "), " ' . $e[3] . ' ")]';
                        break;
                    case '|=':
                        $parts[] = '[@' . $e[1] . '="' . $e[3] . '" or starts-with(@' . $e[1] . ', concat( "' . $e[3] . '", "-"))]';
                        break;
                    default:
                        $parts[] = '[@' . $e[1] . '="' . $e[3] . '"]';
                        break;
                }
            }
            else if ($pregMatchDelete($regex['attr_box'], $selector, $e)) {
                $parts[] = '[@' . $e[1] . ']';  // 単項(存在性)
            }

             // combinatorとカンマがあったら、区切りを追加。また、次は型選択子又は汎用選択子でなければならない
            if ($pregMatchDelete($regex['combinator'], $selector, $e)) {
                switch (trim($e[1])) {
                    case ',':
                        $parts[] = ' | //*';
                        break;
                    case '>':
                        $parts[] = '/';
                        break;
                    case '+':
                        $parts[] = '/following-sibling::*[1]/self::';
                        break;
                    case '~': // CSS3
                        $parts[] = '/following-sibling::';
                        break;
                    default:
                        $parts[] = '//';
                        break;
                }
                $element = true;
            }
        }
        return implode('', $parts);
    }
}



class HTMLElement extends \DOMElement{ // https://www.php.net/manual/ja/class.domelement.php

    function __construct() {
        parent::__construct();
    }


    function __get($name){
        if($name === 'innerHTML'){
            $result = '';
            foreach($this->childNodes as $child){
                $result .= $this->ownerDocument->saveHTML($child);
            }
            return $result;
        }
        else if($name === 'outerHTML'){
            return $this->ownerDocument->saveHTML($this);
        }
        else if($name === 'children'){
            $children = [];
            foreach($this->childNodes as $v){
                if($v->nodeType === XML_ELEMENT_NODE){
                    $children[] = $v;
                }
            }
            return $children;
        }
        else{
            return $this->getAttribute($name);
        }
    }


    function __set($name, $value){
        if($name === 'innerHTML'){
            $fragment = document::createFragment($this->ownerDocument, $value);
            $this->textContent = '';
            $this->appendChild($fragment);
        }
        else if($name === 'outerHTML'){
            $fragment = document::createFragment($this->ownerDocument, $value);
            $this->parentNode->replaceChild($fragment, $this);
        }
        else{
            $this->setAttribute($name, $value);
        }
    }


    function __unset($name){
        $this->removeAttribute($name);
    }


    function __isset($name){
        return $this->hasAttribute($name);
    }


    function __toString(){
        return $this->ownerDocument->saveHTML($this);
    }


    function querySelector($selector){
        return $this->querySelectorAll($selector)[0];
    }


    function querySelectorAll($selector){
        $xpath    = new \DOMXPath($this->ownerDocument);
        $selector = document::selector2xpath($selector, $this);
        return iterator_to_array($xpath->query($selector, $this));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ありがとうPrestissimo、そしてさようなら

Composer2.0は並列ダウンロードができる

Prestissimoといえば、Composerにほぼ必須といってよいくらいの高速化ツールです。
しかしComposer2.0でついに並列ダウンロードが実装され、お別れの時がやってきました。
Prestissimo制作者のhirakさんが作られたプルリクエストは4年越しに実質的にマージされてクローズされることになりました。おめでとうございます。

Composer2.0がリリースされる前にPrestissimoは消しておこう

PrestissimoはComposer2.0では動きません。そのため、Composer2.0へバージョンアップする前に忘れずに消しておく必要があります。現在Composer2.0はRCリリースですが、2020年10月末までに正式リリース予定となっています。
2.0でインストーラーが変わるとかはなく、いつものようにcomposer self-updateすると2.0に更新されますので、composer global require hirak/prestissimoしているところがあれば削除しましょう。特にCIやデプロイスクリプトで自動的にcomposer self-updateしている環境では突然失敗するようになるためご用心ください。

知らない間にComposer2.0に更新されないようにしたい

こんな感じで--1引数をつけると、1.0系列で最新のパッケージに更新されます。

$ composer self-update --1

composer-setup.phpを使った新規インストールの時は--version=引数で1.0系列のバージョンを指定してください。

php composer-setup.php --version=1.10.15
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む