20200915のPHPに関する記事は12件です。

EC2 (Amazon Linux) にPHP7.2 をインストールし、php.ini を設定するまでのまとめ

タイトルのとおりの備忘録です。
LAMP環境を作り、PHPの初期設定をします。

後々同じことをやる方のググる手間を省ければと思い、まとめました。
「とりあえずEC2でPHPを動かしたいぞ!!!」というあなたのお役に立てれば幸いです:pray:

前提

■ 先にVPCやサブネットの用意を終わらせておくとスムーズです。
  (ECインスタンス作成時に紐付けられるため)

  もしよろしければ、以下の記事を参考になさってみてください!
  ・VPC作成 〜 パブリック・プライベートサブネットを作るまでの手順①
  ・VPC作成 〜 パブリック・プライベートサブネットを作るまでの手順②

■ CloudFormation を使った構築手順については今回触れません。

ざっくりの流れ

  1. EC2 インスタンスを作成
  2. LAMP環境をインストールする
  3. mbモジュールをインストールする
  4. php.ini を設定する

1. EC2 インスタンスを作成

▼ 参考:
 AWSコンソールからEC2インスタンスを作成する手順

LAMP環境をインストールする際、EC2インスタンスにssh接続する必要があります。
EC2インスタンス作成時には既存のキーペアの選択 or 新しいキーペアの作成を選ぶようにしてください。

▼ EC2インスタンスにssh接続するコマンド:

ssh -i [キーペアのパス] ec2-user@[パブリック IPv4 アドレス]

2. LAMP環境をインストールする

▼ 参考:
 チュートリアル: Amazon Linux AMI を使用して LAMP ウェブサーバーをインストールする - Amazon Elastic Compute Cloud

3. mbモジュールをインストールする

▼ 参考:
 Amazon Linuxでphpでmbstringを使う - Qiita

PHPにはマルチバイト文字列を扱うための関数がいろいろあります。(例:mb_substr)

mbモジュールをインストールしておかないと、PHPを実行した際にFatal error が出てしまいます。

4. php.ini を設定する

▼ 参考:
 【PHP】PHPをインストールしたらやっておきたい設定 - Qiita

初期状態の/etc/php.ini をコピーしてバックアップを取っておいてから設定を変更するようにすれば、何かあったときに戻せるので安心です。

上記の記事で「セキュリティに関する設定」として記載されているsession.hash_function などはPHP 7.1.0 から削除されているので設定不要です。

参考

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

【Windows10】Laragon で Laravel8 (Jetstream + Inertia + Vue) を動かす

Windows環境でLaravelを動かすには色々と方法がある。

  • Homesteadを使って仮想環境を立てる(公式推奨?)
  • Dockerを使う
  • XAMPPでPHPを動かす
  • etc ...

今のトレンドだとDockerで環境ごと保存するのが一番?
でも個人的に準備などが面倒臭くて手が出しにくい…
(立ち上げるのが面倒だったり、リソースを食い散らかしたり...)

Nodeみたいにローカルで開発できないかなぁ...
XAMPP使うかぁ?という時に Laragon というものを見つけた。

  • 様々な言語に対応(PHP, Node.js, Python, Java, Go, Ruby)
  • 内部でDBを立ち上げて管理してくれる(mysql, PostgreSQL, mongoDBなど)
  • SSLに対応(証明書は無い?)
  • 環境変更はボタン一つ
    • 新しい構成を追加することも可能
  • ポータブルな設計なので移動が簡単
    • 要らなくなったらフォルダ削除でOK
    • 追加も配置するだけ
    • データもプログラムも
  • 滅茶苦茶早くて軽い
    • 本当に秒で起動する
    • フルインスコで130MBのディスク使用量
    • 実行時のメモリ使用量も4MB未満らしい
  • 設定でhostsを自動的に書き換えてくれる
    • ***.testのリンクを作成(デフォルト)
  • ngrokを用いたローカルデータの一時的な公開が可能
    • 40 connections / minute

と使わない理由が無い内容だったので、折角なのでLaravel8の開発環境で採用してみた。
(個人的にhosts書き換えがGOD)

以下のソフトウェアの使用が強制されるのが注意点?
アップデートで使用するソフトウェアを切り替えれるようになると神になると思う。

公式サイト
Laravel - ウェブ職人のためのPHPフレームワーク
Laragon - portable, isolated, fast & powerful universal development environment for PHP, Node.js, Python, Java, Go, Ruby.

Introduction | Laravel Jetstream
Inertia.js - The Modern Monolith

Laragon のインストール

Download | Laragon

上記リンクからLaragonをインストールする。
LiteはNode系が無いバージョンだが、後で簡単に追加できるのでお好みで。
(今回はLaravelでVueを使用したいのでProを採用(Liteだとコンパイルできない...))

image.png

  • 起動
  • 「▶全て開始」をクリック
  • 「?ウェブ」をクリックしてLaragonのページが開けたら成功 image.png

Laravel8 のインストール

  • 「右クリック」>「クイックアプリ作成」>「構成」をクリック
  • 勝手にNotepad++が起動する(いつの間に入ったんだろ...)
    • Laravel8=composer create-project "laravel/laravel=8.*" %s --prefer-dist を追記
    • ver 8に固定させるため一応...
  • 「右クリック」>「クイックアプリ作成」>「Laravel8」をクリック
  • 「右クリック」>「www」> 「<作成したプロジェクト名>」を選択
  • Laravelのページが開けたら成功

php artisan serve は必要ない。(そのためのLaragon)
image.png

以下のエラーはPHPのversionが足りてないので最新のを取ってくる。

[InvalidArgumentException]
Could not find package laravel/laravel with version 8.* in a version installable using your PHP version 7.2.19.
  • Laravel8の必要環境はPHP >= 7.3
  • Laragonのupdateはzipを持ってくるだけでOK
  • 以下のリンクからPHPのzipを落とす
    • https://windows.php.net/download
    • 初期に入っていたのはx64のThread Safety版だった
    • zipを解凍して「%laragon%/bin/php」に置く
    • 「右クリック」>「PHP」>「バージョン[xxx]」>「<導入したversion>」を選択
    • リアルタイムに環境が更新されるはず

Laravel Jetstream(Inertia) の導入

DBやjavascriptとの連携を確認したいので認証のテンプレまで入れてみる。
従来の認証周りは8ではJetstreamが担うらしい。
Introduction | Laravel Jetstream

Jetstream では2つのフロントエンドスタックがある。

今回はVueを使いたいのでInertiaを選択。

  • 「◼ターミナル」をクリック
  • 勝手にCmderが開く
cd <プロジェクトdir>

// インストール
composer require laravel/jetstream
php artisan jetstream:install inertia

// DB構築
php artisan migrate

// nodeセットアップ
npm install
npm run dev
  • Laravelのページの右上に login / register が表示されていれば完成

image.png

以下のエラーはデータベースが存在しないので作成しよう。

Illuminate\Database\QueryException
SQLSTATE[HY000] [1049] Unknown database 'laravel'
  • Laragonの初期設定ではプロジェクト名のデータベースが作成される
  • root / no password

所見

適当に垢作ってログインするとテンプレートのダッシュボードが表示される。
標準で二段階認証やセッション管理、API tokenなどが用意されているのは流石。
新たにTeamというroleみたいな仕組みがあるらしいが調査中。

image.png

このように手軽に遊んだりテストしたりにはLaragonは最適かもしれない。
仮想環境と別の手段として覚えておいても良いかも。
Laravelいいよ!Laragonいいよ!
(Laragonの由来ってもしかしてLaravel…?)

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

【PHP】いいね機能実装

PHPについて学習内容を備忘録としてまとめます。

いいね機能

PHPでajax処理を用いたいいね機能を実装しましたので、ここに実装方法をまとめます。
いいね機能とはFacebookやツイッターにあるお気に入り機能を指します。

いいねボタンの実装

まずはいいねボタンを実装します。
ボタンを配置する場所は適切なところに設置してください。

#user_disp.php

//ユーザーIDと投稿IDを元にいいね値の重複チェックを行っています
function check_favolite_duplicate($user_id,$post_id){
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM favorite
            WHERE user_id = :user_id AND post_id = :post_id";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $user_id ,
                         ':post_id' => $post_id));
    $favorite = $stmt->fetch();
    return $favorite;
}

<form class="favorite_count" action="#" method="post">
        <input type="hidden" name="post_id">
        <button type="button" name="favorite" class="favorite_btn">
        <?php if (!check_favolite_duplicate($_SESSION['user_id'],$post_id)): ?>
          いいね
        <?php else: ?>
          いいね解除
        <?php endif; ?>
        </button>
</form>

上記ではいいねボタンをクリックした際に、check_favolite_duplicate関数ですでに投稿をお気に入りしているかを判断し、
ボタンをいいねいいね解除に切り替えています。

当然このままではajax処理は行われないため、JavaScriptを利用していきます。
jsファイルを作成し、下記を追加します。

jsファイルを作成

//URLから引数に入っている値を渡す処理
function get_param(name, url) {
    if (!url) url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return false;
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}


$(document).on('click','.favorite_btn',function(e){
    e.stopPropagation();
    var $this = $(this),
        page_id = get_param('page_id'),
        post_id = get_param('procode');
    $.ajax({
        type: 'POST',
        url: 'ajax_post_favorite_process.php',
        dataType: 'json',
        data: { page_id: page_id,
                post_id: post_id}
    }).done(function(data){
        location.reload();
    }).fail(function() {
      location.reload();
    });
  });

上記処理ではクラス名がfavorite_btnであるボタンをクリックした際に、
ajax_post_favorite_process.phppage_idpost_idを渡して処理を進めています。

get_paramとはURLから引数に入っている値を取り出すことができます。
この関数からpage_id(ユーザーID)とpost_id(投稿ID)を受け取っています。

ajax処理の追加

ではajax_post_favorite_process.phpで進めている処理を見ていきます。

<script src=" https://code.jquery.com/jquery-3.4.1.min.js "></script>
<script src="../js/user_page.js"></script>
<?php
session_start();
session_regenerate_id(true);
require_once('config.php');

function check_favolite_duplicate($user_id,$post_id){
    $dsn='mysql:dbname=db;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $sql = "SELECT *
            FROM favorite
            WHERE user_id = :user_id AND post_id = :post_id";
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $user_id ,
                         ':post_id' => $post_id));
    $favorite = $stmt->fetch();
    return $favorite;
}

if(isset($_POST)){

  $current_user = get_user($_SESSION['user_id']);
  $page_id = $_POST['page_id'];
  $post_id = $_POST['post_id'];

  $profile_user_id = $_POST['page_id'] ?: $current_user['user_id'];

  //既に登録されているか確認
  if(check_favolite_duplicate($current_user['id'],$post_id)){
    $action = '解除';
    $sql = "DELETE
            FROM favorite
            WHERE :user_id = user_id AND :post_id = post_id";
  }else{
    $action = '登録';
    $sql = "INSERT INTO favorite(user_id,post_id)
            VALUES(:user_id,:post_id)";
  }

  try{
    $dsn='mysql:dbname=shop;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    $stmt = $dbh->prepare($sql);
    $stmt->execute(array(':user_id' => $current_user['code'] , ':post_id' => $post_id));

  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
    echo json_encode("error");
  }
}

check_favolite_duplicate関数で現在のユーザーIDと投稿IDを取得しデータベースに組み合わせが重複していないか確認を取り、
重複していた場合にいいねを解除しています。
重複していなかった場合はfavoriteテーブルにuser_idとpost_idを追加します。

これで下記の画面のようにいいねボタンが実装され、テーブルにもカラムが追加されていると思います。

image.png
image.png

↓いいねボタンをクリック
image.png
image.png
上記のような画面になりデータベースが更新されれば成功です。
こちらはユーザーIDが7、投稿IDが13で実行しています。

参考URL

https://qiita.com/nyann123/items/7320d98d17768986add0

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

2020/09/15 PHPerによるPHPerのための「『PHP8』のニュースや記事を語り合う」TechCafe

2020/09/15、ラクスによるPHPerによるPHPerのための「『PHP8』のニュースや記事を語り合う」TechCafe というイベントがありました。

実はひめやかに参加していたので、以下はそのざっくりメモです。

TechCafe

TechCafeとは

エンジニアと技術が交差する憩いの場。
なんかしらの話題でわいわい。

なんでPHP?
ラクスのサービスのうち6個がPHPなので。

対象:PHPを入門後の初級エンジニア~シニアエンジニア

本日のテーマ

vlcsnap-2020-09-15-20h58m37s190.png

出演者

ラクス ふじさわ氏
ラクス 加納氏
ラクス 久山氏
ラクス おおつか氏
白柳隆司氏 https://www.youtube.com/channel/UCv1AIkCCrRB_Tcz_1ZgSoFg/videos
Futoshi Endo氏 https://twitter.com/Fendo181

PHP8の新機能

Union Types

TypeScriptとかでもふつうにある機能。
わりと賛否わかれる。
これまでIDEやら静的解析はPHPDocを使っていたが、PHP構文となることで実行時チェックできるようになる。
コメントの更新忘れで古くなるとかが防げる。
nullとかのエッジケースを明示化できる。

PHPは元々自由型だったので複数の型が入ってくることはよくある。
新規コードよりは、レガシーコードのリファクタに利点があるのではないか。

PHPStanを使ってチェックしているが、これが来たらスマートにできそう。
新しく来た人が見たときに助かるのでよい。

Javaの人はきっと許さない。
強い型言語から来たので、型がないと肩が凝る。

JIT

PHPは、通常はリクエストが来る度にソースコードをOPCodeに変換し、さらにネイティブコードに変換して実行する。

高速化の仕組みとしてOPCacheがある。
OPCodeをキャッシュして、次のリクエストでOPCodeを使い回す。

JITはこの発展系。
ネイティブコードをキャッシュして、次のリクエストでネイティブコードを使い回す。

Webアプリのオーバーヘッドが大半がI/Oなので、Webアプリでは大幅な高速化はなさそう。
高速な計算処理など、これまでC一択、最近はRustだったりした
これをPHPでやるという選択肢の可能性ができる。
Cとちがってメモリ管理とかが簡単なので安全に実装できる。

PHPは遅いってイメージが未だに残ってるので我々がどうにかしていかないと。

php.iniオプションopcache.jitの説明が微妙に間違ってた。

JIT使うと使う容量が増えてみたいな話があったけど、実はJITを有効化するオプションopcache.jit_buffer_sizeが直接容量を指定する設定なのだ。

PHP8のjitをweb以外でつかうとなると現時点でなにか用途がありますか?
画像処理・ゲーム・機械学習とかどうよ?
セキュリティ関連。

The nullsafe operator

APIの返り値などツリー形式データにアクセスする際、if($session !== null)のネストをなくすことができる。

10行くらいが1行にリファクタできるなんて気持ちいい。
全ての言語に広まってほしい。
JavaScriptでも同じRFCが上がってる。

Named arguments

メソッドの引数に名前を付けて渡すことができる。
呼び出し側を見るだけで、目的が一目瞭然。
通常引数は順番が大事だったけど、名前付き引数は順番を入れ替えることもできる。
最後の引数だけ指定したい場合は途中の引数のデフォルト値を調べて渡さないといけなかったけど、それをスキップできる。

これまではデフォルト引数の問題を解決するため配列$optionsを渡すみたいなことをやっていたこともあったが、何を設定できるのかわからなかった。

Union Typesもだが、プロジェクトに入って間もない人にとって助かる機能。

IDEなんかでがんばって実現してるものを取り入れるみたいな傾向が見える。

PHPに求められるものが変わった。
できる人が手の中でちょっとしたものを作るみたいなツールから、大人数で大規模な使用に耐えるシステムに。

Attributes

PHPDocなどに記述していたメタデータを言語仕様として書けるようになった。

たとえばPHPUnitでは@afterのようなアノテーションを書けるが、これは単なるコメントで制約もなく、ライブラリ作者が勝手に決めていた。
取り出しも正規表現とかでがんばって抜き出していた。
言語仕様になるので、リフレクションでアクセスできるようになる。

仕様が二転三転したので、記事によっては古い書き方が残ってたりすることもあるので注意。

PHPUnitは2022年に対応するかも。

アトリビュート自体はライブラリ作者のためのものなので、一般ユーザが気にすることはあんまりない。

Match expression

switch文によく似たmatch式の導入。

break不要、式なので代入できる、複数条件記述できる、厳密な比較。

defaultがないとエラーになると言っていたけど、実際はdefaultがないだけではエラーにならず、どの分岐にも進めなかったときにエラーになる。

単一行でシンプルな代入処理はmatch式、それ以外はswitchがよさそう。

==はもはや死語になっていくんだな

ちょっと動けばいいや的なのなら==でいいんじゃね。
その後大きくなるようなら最初から===がいい。

New mixed type

RFCでは次の用途で用いるとしている。

・型のことを気にしてはいるが、まだ正確に記述していない
・正確に指定することができない
・明確な意図で型を縛っていない

弊社では、リファクタリング途中であるマーキングで使えるんじゃないか?
何も考えてないのか、考えた結果そうしているの違い。

PHPStan通すためにとりあえずmixed書いちゃう的な。

時間が来てしまったので残りはスルー

Breaking Changesがいくつもあるので気をつけよう。
正式リリース後にマイグレーションガイドが出るはずなので、それを参考にしよう。

MSがPHP8をサポートしなくなるのでWSL2とか使おう。

PHPの最新話題についていくにはPHPWeeklyのメルマガがいいぞ。

エンディング

今後の勉強会の紹介。
9/25 PHP LT JAM 【LT未経験者歓迎】 #phpltjam
10/7 PHPerによるPHPerのための「PHPのニュースや記事を語り合う」TechCafe

テックブログのご案内。

次回やってほしいことがあったらTwitterで『#PHPTechCafe』で呟いて。

感想

私はこの手のイベントに滅多に参加しないのですが、今回はたまたま別件について調べてたところ目に入って、日程も近かったので試しに参加してみました。
で感想ですが、

たいへんもうしわけないのだが、9割方知ってた。

まあ話題からしてPHP8のことで、大概は自分で調べてたから仕方ないですけどね。

しかし私くらいPHP8を追ってる人なんてそれほど多くないと思うので、普通の人が聴くぶんには十分に面白いイベントだったと思います。
便利になる新機能がたくさん紹介されたので、PHPの今後への期待も膨らんだことでしょう。
また単なる新機能の話だけではなく、どうしてその機能が入ることになったのか、PHPは何から何になろうとしているのか、そういった思想的なところの話も面白かったですね。

総合的には、次回もまた参加してみようかなと考えるくらいにはよかったと思います。

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

最近、TrelloのAPI経由でデータ取得ができなくなってません? まぁ修正しましたけど

問題

TrelloのAPI経由でタスクなどを取得してDiscordなどに表示していたが、2020年8月後半からタスクなどが表示されなくなった。

原因

Discordにエラーなどは表示されず。(そういう設定もしていないけど)

「APIが原因か?」と考え、APIを含めたURLで叩いてみたら問題なく取得できている。

それなら、実行ファイル(PHP)の問題かな?
ターミナルで以下のコマンドを叩いてみる。

$ php (実行ファイル名).php

すると、以下のエラーが確認できた。

PHP Warning:  file_get_contents(trelloのAPIが含まれたURL): failed to open stream: HTTP request failed! HTTP/1.1 426 Upgrade Required in (実行ファイル).php on line 15

HTTPリクエストのエラーなのはわかったけど、426って何? 見たことないんですけど。

調べてみた所、今のプロトコルじゃリクエストを拒否するとの事らしい。
426 Upgrade Required - HTTP | MDN

HTTP/1.1 426 Upgrade Required in (実行ファイル).php on line 15って言われてるし、file_get_contents()HTTP/1.1で実行できるようにすれば良いのかな?

解決策

stream_context_create()で作成したHTTPヘッダ情報をfile_get_contents()に渡す。

自分の予想で合っていたみたい。

stream_context_create()でどんな形式でHTTPリクエストを実行するかを指定できるので、この関数を利用してHTTP/1.1で実行できるよう宣言する。
file_get_contentsの第三引数でstream_context_create()で作成したのを入れられるので、宣言したのを記述する。

以上をまとめると、実行ファイルは以下のような形になる。

(実行ファイル).php
//HTTPヘッダ情報
$options = [  
    'http' => [  
        'method'           => 'GET',  
        'protocol_version' => 1.1,  
        'header'           => "Connection: close\r\n"  
    ]  
];  
$context = stream_context_create($options);  

$trello_url = "trelloのAPIが含まれたURL";
$trello_data = file_get_contents($trello_url,FALSE,$context);

Connection: closeはリクエストが完了した後に、接続を切断してくれるよう宣言している。

修正後、挙動を確認してみた所、無事解決した。

参考資料

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

TrelloのAPI経由でデータ取得ができなくなった時の対処法(HTTP/1.1 426 Upgrade Required編)

問題

TrelloのAPI経由でタスクなどを取得してDiscordなどに表示していたが、2020年8月後半からタスクなどが表示されなくなった。

原因

Discordにエラーなどは表示されず。(そういう設定もしていないけど)

「APIが原因か?」と考え、APIを含めたURLで叩いてみたら問題なく取得できている。

それなら、実行ファイル(PHP)の問題かな?
ターミナルで以下のコマンドを叩いてみる。

$ php (実行ファイル名).php

すると、以下のエラーが確認できた。

PHP Warning:  file_get_contents(trelloのAPIが含まれたURL): failed to open stream: HTTP request failed! HTTP/1.1 426 Upgrade Required in (実行ファイル).php on line 15

HTTPリクエストのエラーなのはわかったけど、426って何? 見たことないんですけど。

調べてみた所、今のプロトコルじゃリクエストを拒否するとの事らしい。
426 Upgrade Required - HTTP | MDN

HTTP/1.1 426 Upgrade Required in (実行ファイル).php on line 15って言われてるし、file_get_contents()HTTP/1.1で実行できるようにすれば良いのかな?

解決策

stream_context_create()で作成したHTTPヘッダ情報をfile_get_contents()に渡す。

自分の予想で合っていたみたい。

stream_context_create()でどんな形式でHTTPリクエストを実行するかを指定できるので、この関数を利用してHTTP/1.1で実行できるよう宣言する。
file_get_contentsの第三引数でstream_context_create()で作成したのを入れられるので、宣言したのを記述する。

以上をまとめると、実行ファイルは以下のような形になる。

(実行ファイル).php
//HTTPヘッダ情報
$options = [  
    'http' => [  
        'method'           => 'GET',  
        'protocol_version' => 1.1,  
        'header'           => "Connection: close\r\n"  
    ]  
];  
$context = stream_context_create($options);  

$trello_url = "trelloのAPIが含まれたURL";
$trello_data = file_get_contents($trello_url,FALSE,$context);

Connection: closeはリクエストが完了した後に、接続を切断してくれるよう宣言している。

修正後、挙動を確認してみた所、無事解決した。

参考資料

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

外部プログラムを直接実行した場合にシェルスクリプトにフォールバックする言語・しない言語

※本記事の内容はWindowsには一切当てはまりません。

※Linux (Debian stretch) / macOS (10.14) で確認。

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/exec.3.html 等によると、execvpでのみ、ENOEXECが発生したらシェルスクリプトと解釈する機能があるそうです。
スクリプト言語でシェルを使わず外部プログラムを実行した場合、この機能は働くのでしょうか?

準備

$ cat lsscript
ls

実行

Ruby

  • 直接実行(例): popenの引数が配列、systemの引数が2要素以上
  • ruby -e 'p IO.popen(["./lsscript"]).read'

process.c proc_exec_cmd
execveを試し、ENOEXECなら/bin/shを付加して再試行

Python

  • 直接実行(例): check_call(等)の引数が配列
  • python -c 'import subprocess;subprocess.check_call(["./lsscript"])'

Modules/_posixsubprocess.c child_exec
execveを試す。 ENOEXECの場合失敗する

Python3も同様。
なおshell=Trueとすると失敗しなくなりますがpipes.quoteやらshlex.quoteやらが必要となります。
(この記事の読者であればこちらの必要性はわかりますよね)

Perl

  • 直接実行(例): system()の引数が2要素以上
  • perl -e 'print system("./lsscript","dummy")'

doio.c Perl_do_aexec5
execvpを試す。

PHP

  • 直接実行: pcntl_exec
  • php -r 'pcntl_exec("./lsscript");'

ext/pcntl/pcntl.c pcntl_exec
execveを試す。 ENOEXECの場合失敗する

(上はexecですからサブプロセスとするにはfork呼び出し等が必要。その辺が必要ない標準実行系はext/standard/exec.cにありますが、popen固定のようです(つまり必ずシェルが入る))

結論

Python/PHPでシェルを介さずに実行しようとすると、自動でshを付けてくれません。C言語のshebangもどき等を投げる際はご注意ください。

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

xampp(PHP)からaccessにアクセスできない時

xampp(PHP)からaccessに接続しようとしてハマったのでメモ。

環境

  • windows10 Pro 64bit
  • xampp 64bit
  • accessの形式 accdb

PHPのソース

define('ACCESS_DB_DRIVER',   '{Microsoft Access Driver (*.mdb, *.accdb)}');

$dbq = 'C:/xampp/htdocs/***.accdb';


$objPdo = new PDO("odbc:Driver=" . ACCESS_DB_DRIVER . ";DBQ=" . $dbq . ";");

エラー内容

PHP Fatal error:  Uncaught PDOException: SQLSTATE[IM002] SQLDriverConnect: 0 [Microsoft][ODBC Driver Manager] ...

解決方法

php.iniを編集

;extension=pdo_odbc

コメントアウトを外す

ODBCを確認

コントロール パネル>システムとセキュリティ>管理ツール>ODBCデータソース(64bビット)

「ドライバー」タブにMicrosoft Access Driverが無かったので、ドライバーをインストールすれば解決すると推測

ドライバーダウンロード

こちらの記事を参考にさせていただきました。
Microsoft Access(.mdb、.accdb)のODBC、OLEDBドライバーに関するまとめ

https://www.microsoft.com/ja-jp/download/details.aspx?id=13255
「AccessDatabaseEngine_X64.exe」をダブルクリックしインストール

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

League/CSVでSJIS-winでCSV出力する

CSVファイルをSJISで読み込むほうはよく出るのですが、
出力するほうは意外と誰も書いてなかったので私が書きます。

こうする

document_root/includes/csv.class.php
<?php

//use League\Csv\Reader;
use League\Csv\Writer;
use League\Csv\CharsetConverter;

require dirname(__FILE__) . '/../third_party/league/csv/autoload.php'; // 適宜読み込み先を変更すること。composerとかで読み込んでたら記述いらんかも

class csv
{
  public function __construct()
  {
    //
  }

  /**
   * Export csv as SJIS-win by League/CSV
   *
   * @params string $fileName e.g. "example.csv"
   * @params array $records e.g. [[1, "山田太郎", 80]]
   * @params array $rowHeader [optional] e.g. ["連番", "氏名", "年齢"]
   * @return void
   * @link https://csv.thephpleague.com/9.0/
   */
  public function export($fileName, $records, $rowHeader = [])
  {
    // SJIS-winで出力
    $encoder = (new \League\Csv\CharsetConverter())->inputEncoding('utf-8')->outputEncoding('SJIS-win');

    $csv = \League\Csv\Writer::createFromFileObject(new \SplTempFileObject());

    $csv->addFormatter($encoder);

    // 見出し行
    if(is_array($rowHeader) && count($rowHeader) > 0){
      $csv->insertOne($rowHeader);
    }

    // データ行
    foreach($records as $record){
      $csv->insertOne($record);
    }

    $csv->output($fileName);
    exit;
  }
}
document_root/index.php
require dirname(__FILE__) . '/includes/csv.class.php';

$csv = new csv();

$fileName = "ファイル名.csv";
$rowHeader = ["み","だ","し","行"];
$records = [
  ["な","か","み","1"],
  ["な","か","み","2"],
  ["な","か","み","3"]
];

$csv->export($fileName, $records, $rowHeader);

参考URL

https://csv.thephpleague.com/9.0/converter/charset/
https://qiita.com/kiyc/items/f70280e13e5194e5dd94

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

CakePHP3 ORMのWHERE句で、date_formatで指定した "Y" が "y" になってしまう

TL;DR

  • DATE_FORMATで %Y を使用する場合は、以下を参考にする
    • where句でコールバック関数とクロージャーを使用し、eqメソッドを使用する
$createdFormat = $query->func()->date_format([
    'created'    => 'identifier',
    "'%Y-%m-%d'" => 'literal'
]);
$query->where(function ($exp) use ($createdFormat, $created) {
    return $exp->eq($createdFormat, $created);
});

前提

  • PHP
    • 7.3.11
  • CakePHP
    • v3.6.14

事象

1.
ORM: $query->where(["DATE_FORMAT(created, '%Y-%c') = " => $date])
SQL: WHERE DATE_FORMAT(created, '%y-%c') = :c0
################
2.
ORM: $query->where(["DATE_FORMAT(created, '%Y-%c') = ${date}"])
SQL: WHERE DATE_FORMAT(created, '%Y-%c') = 2020-8)
  • 上記の通り、連想配列によるデータの渡し方では、発行されるSQLのフォーマットが、Yy になってしまう
  • 変数展開による渡し方では、発行されるSQLのフォーマットは、Y のまま

原因

  • 巡りめぐってQueryExpression::_parseCondition()を参照し、そこでstrtolowerを行ってるため
  • 以下対象コードの一部抜粋
    protected function _parseCondition($field, $value)
    {
        $operator = '=';
        $expression = $field;
        $parts = explode(' ', trim($field), 2);

        if (count($parts) > 1) {
            list($expression, $operator) = $parts;
        }

        $type = $this->getTypeMap()->type($expression);
        $operator = strtolower(trim($operator));

        ...

        return new Comparison($expression, $value, $type, $operator);
    }

対策

  1. date_formatで指定したいカラムを、別でフォーマット化する
  2. WHERE句でコールバック関数とeqメソッドを使って条件に追加する
$createdFormat = $query->func()->date_format([
    'created'    => 'identifier',
    "'%Y-%m-%d'" => 'literal'
]);
$query->where(function ($exp) use ($createdFormat, $created) {
    return $exp->eq($createdFormat, $created);
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel-admin でRowAction の表示方式を変更する

環境

  • Laravel Framework 8.2.0
  • laravel-admin 1.8.4

経緯

laravel-admin 1.8.4 では RowAction はハンバーガーメニューみたいな形式になってるが、ドキュメントの TOP のスクリーンショットにあるようなリンク形式にしたくて若干ハマった。
2020-09-14 23_40_02-Window.png
こんなの

変更方法

config/admin.php
    /*
    |--------------------------------------------------------------------------
    | The global Grid action display class.
    |--------------------------------------------------------------------------
    */
    'grid_action_class' => \Encore\Admin\Grid\Displayers\DropdownActions::class,

config/admin.php 内の grid_action_class\Encore\Admin\Grid\Displayers\Actions::class に変更するだけで良かった。

改めてドキュメントを見るとこの辺で grid_action_class について書いてるけど古い方式だから(?)触れられていないのかな?
https://laravel-admin.org/docs/en/model-grid-custom-actions#Old%20version%20compatible

もう少しカスタマイズする

Actions::render(View|Edit|Delete) をオーバーライドする。

renderDelete はクラスやデータ属性を下手に消すと削除用のスクリプトが動かなくなるので注意。

CustomActions.php
<?php

namespace App\Admin\Grid\Displayers;

use Encore\Admin\Grid\Displayers\Actions;

class CustomActions extends Actions
{
    protected function renderView()
    {
        return <<<EOT
<a href="{$this->getResource()}/{$this->getRouteKey()}" class="{$this->grid->getGridRowName()}-view btn btn-sm btn-default">
    <i class="fa fa-eye"></i> View
</a>
EOT;
    }

    protected function renderEdit()
    {
        return <<<EOT
<a href="{$this->getResource()}/{$this->getRouteKey()}/edit" class="{$this->grid->getGridRowName()}-edit btn btn-sm btn-default">
    <i class="fa fa-edit"></i> Edit
</a>
EOT;
    }

    protected function renderDelete()
    {
        $this->setupDeleteScript();

        return <<<EOT
<a href="javascript:void(0);" data-id="{$this->getKey()}" class="{$this->grid->getGridRowName()}-delete btn btn-sm btn-default">
    <i class="fa fa-trash"></i> Delete
</a>
EOT;
    }
}

config/admin.php を修正。

     /*
     |--------------------------------------------------------------------------
     | The global Grid action display class.
     |--------------------------------------------------------------------------
     */
-    'grid_action_class' => \Encore\Admin\Grid\Displayers\Actions::class,
+    'grid_action_class' => \App\Admin\Grid\Displayers\CustomActions::class,

127.0.0.1_8000_admin_auth_permissions(iPad).png

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

CakePHP3 リレーション関係にあるテーブルに対してバリデーションを掛けたい

前提

  • PHP
    • 7.3.11
  • CakePHP
    • v3.6.14
  • MySQL
    • 5.7.26

0. テーブル構造

mysql> desc users;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | int(11)      | NO   | PRI | NULL    | auto_increment |
| name          | varchar(255) | NO   |     | NULL    |                |
| tel           | varchar(255) | NO   |     | NULL    |                |
| email         | varchar(255) | NO   | MUL | NULL    |                |
| created       | datetime     | NO   |     | NULL    |                |
| modified      | datetime     | NO   |     | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
19 rows in set (0.00 sec)

mysql> desc addresses;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | int(11)      | NO   | PRI | NULL    | auto_increment |
| user_id       | int(11)      | NO   |     | NULL    |                |
| city          | varchar(255) | NO   |     | NULL    |                |
| building      | varchar(255) | NO   | MUL | NULL    |                |
| created       | datetime     | NO   |     | NULL    |                |
| modified      | datetime     | NO   |     | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
19 rows in set (0.00 sec)

1. 保存したいデータ

  • リレーション関係のような階層構造を持った連想配列にする
$data = [
    'user' => [
        'email' => 'hoge@example.com',
        'tel' => '09012345678',
        'name' => '山田太郎',
        'address' => [
            'city' => '2200011',
            'building' => '14',
            'address' => '横浜市西区高島',
            'building' => '123'
        ],
    ],
];

2. バリデーション

  • リレーション関係のような階層構造を持った連想配列にする
$validate = [
    'associated' => [
        'Addresses' => [
            'validate' => 'default',
        ],
    ],
];

バリデーションクラス

/**
 * Default validation rules.
 *
 * @param \Cake\Validation\Validator $validator Validator instance.
 * @return \Cake\Validation\Validator
 */
public function validationDefault(Validator $validator)
{
    $validator->notEmpty('city');
    $validator->notEmpty('building');

    return $validator;
}

3. オブジェクト生成

  • 第一引数に保存したいデータを、第二引数にバリデーションを渡す
$user = $this->Users->newEntity($data, $validate);

4. エラー検知

  • バリデーションエラーに引っかかってる場合、以下のメソッドでエラー内容を取得することが可能
    • EntityTrait::getError() or EntityTrait::getErrors()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む