- 投稿日:2020-09-15T23:57:46+09:00
EC2 (Amazon Linux) にPHP7.2 をインストールし、php.ini を設定するまでのまとめ
タイトルのとおりの備忘録です。
LAMP環境を作り、PHPの初期設定をします。後々同じことをやる方のググる手間を省ければと思い、まとめました。
「とりあえずEC2でPHPを動かしたいぞ!!!」というあなたのお役に立てれば幸いです前提
■ 先にVPCやサブネットの用意を終わらせておくとスムーズです。
(ECインスタンス作成時に紐付けられるため)もしよろしければ、以下の記事を参考になさってみてください!
・VPC作成 〜 パブリック・プライベートサブネットを作るまでの手順①
・VPC作成 〜 パブリック・プライベートサブネットを作るまでの手順②■ CloudFormation を使った構築手順については今回触れません。
ざっくりの流れ
- EC2 インスタンスを作成
- LAMP環境をインストールする
mb
モジュールをインストールする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 Cloud3.
mb
モジュールをインストールする▼ 参考:
Amazon Linuxでphpでmbstringを使う - QiitaPHPにはマルチバイト文字列を扱うための関数がいろいろあります。(例:
mb_substr
)
mb
モジュールをインストールしておかないと、PHPを実行した際にFatal error
が出てしまいます。4.
php.ini
を設定する▼ 参考:
【PHP】PHPをインストールしたらやっておきたい設定 - Qiita初期状態の
/etc/php.ini
をコピーしてバックアップを取っておいてから設定を変更するようにすれば、何かあったときに戻せるので安心です。上記の記事で「セキュリティに関する設定」として記載されている
session.hash_function
などはPHP 7.1.0
から削除されているので設定不要です。参考
- 投稿日:2020-09-15T21:28:01+09:00
【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)以下のソフトウェアの使用が強制されるのが注意点?
アップデートで使用するソフトウェアを切り替えれるようになると神になると思う。
- editor として Notepad++
- terminal として Cmder
- DB管理ソフトとして HeidiSQL
- phpMyAdmin も使える
公式サイト
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 MonolithLaragon のインストール
上記リンクから
Laragon
をインストールする。
LiteはNode系が無いバージョンだが、後で簡単に追加できるのでお好みで。
(今回はLaravelでVueを使用したいのでProを採用(Liteだとコンパイルできない...))Laravel8 のインストール
- 「右クリック」>「クイックアプリ作成」>「構成」をクリック
- 勝手にNotepad++が起動する(いつの間に入ったんだろ...)
Laravel8=composer create-project "laravel/laravel=8.*" %s --prefer-dist
を追記- ver 8に固定させるため一応...
- 「右クリック」>「クイックアプリ作成」>「Laravel8」をクリック
- 「右クリック」>「www」> 「<作成したプロジェクト名>」を選択
- Laravelのページが開けたら成功
php artisan serve は必要ない。(そのためのLaragon)
以下のエラーは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 JetstreamJetstream では2つのフロントエンドスタックがある。
- Livewire + Blade Templates (従来のBladeを拡張する)
- Inertia.js + Vue.js (SPA, VueのPropsにControllerからデータを渡せる)
今回は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 が表示されていれば完成
以下のエラーはデータベースが存在しないので作成しよう。
Illuminate\Database\QueryException SQLSTATE[HY000] [1049] Unknown database 'laravel'
- Laragonの初期設定ではプロジェクト名のデータベースが作成される
- root / no password
所見
適当に垢作ってログインするとテンプレートのダッシュボードが表示される。
標準で二段階認証やセッション管理、API tokenなどが用意されているのは流石。
新たにTeamというroleみたいな仕組みがあるらしいが調査中。このように手軽に遊んだりテストしたりにはLaragonは最適かもしれない。
仮想環境と別の手段として覚えておいても良いかも。
Laravelいいよ!Laragonいいよ!
(Laragonの由来ってもしかしてLaravel…?)
- 投稿日:2020-09-15T21:09:57+09:00
【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.php
にpage_id
とpost_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を追加します。これで下記の画面のようにいいねボタンが実装され、テーブルにもカラムが追加されていると思います。
↓いいねボタンをクリック
上記のような画面になりデータベースが更新されれば成功です。
こちらはユーザーIDが7、投稿IDが13で実行しています。参考URL
- 投稿日:2020-09-15T21:01:48+09:00
2020/09/15 PHPerによるPHPerのための「『PHP8』のニュースや記事を語り合う」TechCafe
2020/09/15、ラクスによるPHPerによるPHPerのための「『PHP8』のニュースや記事を語り合う」TechCafe というイベントがありました。
実はひめやかに参加していたので、以下はそのざっくりメモです。
TechCafe
TechCafeとは
エンジニアと技術が交差する憩いの場。
なんかしらの話題でわいわい。なんでPHP?
ラクスのサービスのうち6個がPHPなので。対象:PHPを入門後の初級エンジニア~シニアエンジニア
本日のテーマ
出演者
ラクス ふじさわ氏
ラクス 加納氏
ラクス 久山氏
ラクス おおつか氏
白柳隆司氏 https://www.youtube.com/channel/UCv1AIkCCrRB_Tcz_1ZgSoFg/videos
Futoshi Endo氏 https://twitter.com/Fendo181PHP8の新機能
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は何から何になろうとしているのか、そういった思想的なところの話も面白かったですね。総合的には、次回もまた参加してみようかなと考えるくらいにはよかったと思います。
- 投稿日:2020-09-15T20:53:16+09:00
最近、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
はリクエストが完了した後に、接続を切断してくれるよう宣言している。修正後、挙動を確認してみた所、無事解決した。
参考資料
- 投稿日:2020-09-15T20:53:16+09:00
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
はリクエストが完了した後に、接続を切断してくれるよう宣言している。修正後、挙動を確認してみた所、無事解決した。
参考資料
- 投稿日:2020-09-15T17:44:56+09:00
外部プログラムを直接実行した場合にシェルスクリプトにフォールバックする言語・しない言語
※本記事の内容は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もどき等を投げる際はご注意ください。
- 投稿日:2020-09-15T15:30:49+09:00
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」をダブルクリックしインストール
- 投稿日:2020-09-15T11:55:37+09:00
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.phprequire 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
- 投稿日:2020-09-15T00:26:31+09:00
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のフォーマットが、Y → y になってしまう
- 変数展開による渡し方では、発行される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); }対策
- date_formatで指定したいカラムを、別でフォーマット化する
- 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); });
- 投稿日:2020-09-15T00:21:14+09:00
laravel-admin でRowAction の表示方式を変更する
環境
- Laravel Framework 8.2.0
- laravel-admin 1.8.4
経緯
laravel-admin 1.8.4 では RowAction はハンバーガーメニューみたいな形式になってるが、ドキュメントの TOP のスクリーンショットにあるようなリンク形式にしたくて若干ハマった。
こんなの変更方法
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,
- 投稿日:2020-09-15T00:08:39+09:00
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()
orEntityTrait::getErrors()