- 投稿日:2019-08-04T23:23:52+09:00
Leanpub で Laravel 入門チュートリアルの電子書籍版を作りました。
技術ブログに公開している『入門 Laravel チュートリアル(全11回)』を、電子書籍版にまとめました
![]()
書籍ページ
https://leanpub.com/laravel-primerLeanpub という電子書籍作成〜販売プラットフォームを利用しましたが、公開までのフローがかなりモダンで低ハードルでしたので、本の宣伝も兼ねて紹介したいと思います。
Leanpub
Leanpub は、電子書籍の作成から販売までを行えるプラットフォームです。
カナダのスタートアップらしく、テック系の書籍の取り扱いが多いです。そのためか、制作フローもデベロッパーフレンドリーになっています。
日本語の書籍はまだほとんど出版されていませんが、『Everyday Rails - RSpecによるRailsテスト入門』は結構知られているのではないでしょうか。
便利なところ
- Git でコンテンツを管理できる。
- Markdown で書いた内容が PDF / EPUB / MOBI 版として自動出力される。
- ボタン一つで公開、更新できる。
- 読者は通知された本の更新を無料でダウンロードできる。
- 収益は Paypal で受け取れる。
- 無料で始められる。
当初 Amazon から出そうと思ったのですが、ダイレクトパブリッシングの登録方法が少し煩雑だったのと、出版するコンテンツの生成方法(MOBI ファイルってどうやって作るの?)は結局自分で解決する必要がありそうだったので、二の足を踏んでいました。
Leanpub では以前本を買ったことがあって、どうやら出版・販売もできるらしいということで調べてみると、かなりハードルが低そうだったので、今回チャレンジしてみました。
なにより、書き慣れた Markdown が使えて、そこから PDF / EPUB / MOBI 版が自動出力されるというのに驚きました。
制作フロー
簡単に制作フローを紹介します。
(1)
まず Git リポジトリを作成します。以下のディレクトリ構成が必要です。└─ /manuscript ├─ /resources │ └─ 画像など ├─ Book.txt ├─ chapter1.md ├─ chapter2.md └─ chapter3.md
manuscriptというディレクトリにマークダウンファイルを書いていきます。
章ごとにファイルを作成します。ファイル名は任意です。さらに
Book.txtに章立てを定義します。単に章ごとにファイル名を並べるだけです。Book.txtchapter1.md chapter2.md chapter3.mdこれだけで、マークダウン中の
h1やh2を読み取り、目次を自動生成してくれます。(2)
GitHub にリモートリポジトリを用意します。リポジトリは private で構いません。Collaborators に Leanpub ユーザーを追加する必要があります。これにより、Leanpub 側で更新内容を取得できるようになります。
(3)
本の内容は Markua という独自記法のマークダウンで記述します。独自記法と言っても、ほとんど普通のマークダウンです。大きく違うところは生の HTML は使えないくらい。書けたらリモートリポジトリにプッシュします。
(4)
プレビュー版は、Leanpub の管理画面からボタン一つで出力できます。
PDF / EPUB / MOBI 版がそれぞれ出力されます。(5)
公開も、プレビューと同様にボタン一つです。
表示画像をアップしたり値段を決めたり紹介文を書いたりしてから、公開します。いかがでしょうか。本当に公開まではこれだけでした。
静的サイトジェネレーターみたいなノリで電子書籍が出せてしまいましたもちろん、プラットフォームとしては Amazon の方が圧倒的にオーディエンスが多く広いでしょう。ただ、手軽さでは Leanpub に軍配が上がると思います。
(技術書は必ずしも広く一般にリーチする必要もないでしょうし…。)
エンジニアが情報発信をして対価を得るチャネルとしてもっと知られて、日本語のコンテンツも増えるといいなーと思うので、この記事で少し興味をもってもらえると嬉しいです。
そして、Web 開発者の皆さんは、こちらの本もぜひチェックしてください!
![]()
https://leanpub.com/laravel-primer
以上です。
- 投稿日:2019-08-04T22:55:06+09:00
array_columnの多次元対応
array_columnの多次元対応
仕様
引数
- $arr : 値を取り出したい多次元配列
- $column_key : 値を返したいカラム
戻り値
- \$arr配列の中から、\$clolumn_keyと同一のキーを持つ要素を配列にして返す。一致するキーがない場合、空配列を返す。
ソース
※array_depthはネットからの拾い物。配列の次元数を取得する関数。
function array_colummn_multi( $arr, $column_key ){ switch (array_depth($arr)) { case 0: return []; case 1: return array_key_exists($column_key, $arr) ? [ $arr[$column_key] ] : []; case array_depth($arr) >= 2: $ret = []; foreach($arr as $key => $val){ if( $key === $column_key ) $ret[] = $arr[$key]; $ret = array_merge( $ret, array_colummn_multi($val, $column_key) ); } return $ret; } } function array_depth($arr, $blank=false, $depth=0){ if( !is_array($arr)){ return $depth; } else { $depth++; $tmp = ($blank) ? array($depth) : array(0); foreach($arr as $value){ $tmp[] = array_depth($value, $blank, $depth); } return max($tmp); } }
- 投稿日:2019-08-04T22:10:32+09:00
laravelで外部ファイルを使用してcssを適用する手順
laravelでcssを適用する時に躓いたので、メモをします。
/public/css/にcssファイルを作成します。例としてstyles.cssという名前で作成したとします。
viewファイルに以下の一文を記述します。
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
httpsで表示するページなら、assetの部分がsecure_assetになります。
- 投稿日:2019-08-04T21:07:50+09:00
codeigniter 3.1.10 と Smarty 3.1を連携する開発環境を構築する
はじめに
PHP5系でcodeigniter 2.0.xで開発されたシステムをPHP7系に移植する作業を依頼されました。
腕のいい開発者さんが作ったのでしょう。シンプルな中にも質実剛健のような作りで、見ていて気持ちがいいです。ですが、この世に産み落とされてから早x年。
時代は、codeigniterからLaravelに移り、開発者さん自身もメンテナンスを放棄されてしまったようで、私のところへの依頼となりました。さて、codeigniterの2.0.xから2.2までは問題なくアップデートができたのですが、3系へのアップグレード時に、使われていたSmartyが使えなくなりました。
そこで、Codeigniter3系でSmartyのインストール方法を探しましたが、ロストテクノロジーなので、メチャメチャ苦労しました。
そのため、ここで導入方法を記録しておきます。驚いたことに、Smartyの導入さえできれば、開発されたコードは、そのまま使えましたので、まだまだ現役稼働ができる見込みが立ちました。
システムのリフォーム? リストア? 蘇ると嬉しいですね。今回のサンプルコードをGithubにあげています。もし、参考になったのでしたら、スターをつけていただくとモチベーションがあがります
https://github.com/idani/CodeIgniter3.1-Smarty3.1-Smaple環境
- php 7.3(5.4)
- 移行絡みでしたので、5系、7系の両方を用意して対応してます。
- Codeigniter 3.1.10
- Smarty 3.1.33
codeigniterの特徴
さて、私、Codeigniterって今回、所見でした。なので、自分用のメモとして触った感じをメモ。
applicationとsystemディレクトリに分かれています。
systemの機能を拡張したい場合でも、applicaitonディレクトリの方で、継承、上書きなどを行って独自にカスタマイズできることが可能です。また、この構成のおかげで、Systemのバージョンアップをする場合にも、Systemディレクトリを最新バージョンで上書きするだけで動くという、すっごい楽なメンテナンス性がよいですね。
開発環境構築
まずは、codeigniterを動作させるためのPHPの実行・開発環境を用意します。
https://github.com/idani/CodeIgniter3.1-Smarty3.1-Smapleディレクトリ構成は、このようになっています。
基本的にSRCの部分を差し替えて、いろいろのPHPの開発で使いまわしています。このあたり、Dockerの部分がわからない場合は、以下のサンプルコードと記事を見てもらえれば理解できます。
https://github.com/idani/nginx-http2-php-mysql
今回は、SRCに、「codeignter-3.1.10」を展開からスタートしました。
smartyのインストール
docker-compose.ymlの最後の部分でComposerイメージを使ってインストールします。
まずは、以下のようにdocker-compose.ymlを書き換えます。
docker-compose.yamlcomposer: image: composer volumes: - ./src/application:/app command: composer require smarty/smartyその上で、docker-composeを実行して、インストールを行います。
Smartyのダウンロードを行いますので、終了するまでネットワーク環境によりますが、2,3分かかります。$ docker-compose up composer Creating network "codeigniter31-smarty31-smaple_default" with the default driver Creating codeigniter31-smarty31-smaple_composer_1 ... done Attaching to codeigniter31-smarty31-smaple_composer_1 Gracefully stopping... (press Ctrl+C again to force) Stopping codeigniter31-smarty31-smaple_composer_1 ... donecodeigniterでのcomposerの読み込み設定
「application/config/config.php」で「composer_autoload」をTrusにする。
config.phpに書いてありますが、
「application/vendor/autoload.php」を読み込む場合は、先のように「$config['composer_autoload'] = TRUE;」にするだけでOKです。composerのインストールしたディレクトリを変更する場合は、「$config['composer_autoload'] = '/path/to/vendor/autoload.php';」と設定します。
ディレクトリの作成
Smarty用のテンプレートディレクトリとテンプレートキャッシュ用のディレクトリを作成します。
application/views/templates
application/views/templates_c
の2つのディレクトリをchmod 777で作成しておきます。Smarty用のライブラリ作成
参考文献記載のライブラリを作成して、CodeigniterとSmartyの橋渡しをします。
ここで、Codeigniter側の制限ですが、ファイル名とクラス名は合わせる必要があり、また、すでにSmartyクラスはあるので、「Smartie」としています。
ここは気持ち悪いのですが、後ほど、ライブラリの読み込み時に「Smarty」に補正をかけます。
application/library/Smartie.php<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); /** * Smartie Class * * @package CodeIgniter * @subpackage Libraries * @category Smarty * @author Kepler Gelotte * @link http://www.coolphptools.com/codeigniter-smarty */ class Smartie extends Smarty { var $debug = false; function __construct() { parent::__construct(); $this->setTemplateDir(APPPATH . "views/templates"); $this->setCompileDir(APPPATH . "views/templates_c"); if ( ! is_writable( $this->compile_dir ) ) { // make sure the compile directory can be written to @chmod( $this->compile_dir, 0777 ); } // Uncomment these 2 lines to change Smarty's delimiters // $this->left_delimiter = '{{'; // $this->right_delimiter = '}}'; $this->assign( 'FCPATH', FCPATH ); // path to website $this->assign( 'APPPATH', APPPATH ); // path to application directory $this->assign( 'BASEPATH', BASEPATH ); // path to system directory log_message('debug', "Smarty Class Initialized"); } function setDebug( $debug=true ) { $this->debug = $debug; } /** * Parse a template using the Smarty engine * * This is a convenience method that combines assign() and * display() into one step. * * Values to assign are passed in an associative array of * name => value pairs. * * If the output is to be returned as a string to the caller * instead of being output, pass true as the third parameter. * * @access public * @param string * @param array * @param bool * @return string */ function view($template, $data = array(), $return = FALSE) { if ( ! $this->debug ) { $this->error_reporting = false; } $this->error_unassigned = false; foreach ($data as $key => $val) { $this->assign($key, $val); } if ($return == FALSE) { $CI =& get_instance(); if (method_exists( $CI->output, 'set_output' )) { $CI->output->set_output( $this->fetch($template) ); } else { $CI->output->final_output = $this->fetch($template); } return; } else { return $this->fetch($template); } } } // END Smartie Classライブラリを読み込む
「application/config/autoload.php」の「libraries」の項目を以下のように修正します。
先程の「smartie」という部分を「smarty」に読み替えてロードしています。
application/config/autoload.php/* | ------------------------------------------------------------------- | Auto-load Libraries | ------------------------------------------------------------------- | These are the classes located in system/libraries/ or your | application/libraries/ directory, with the addition of the | 'database' library, which is somewhat of a special case. | | Prototype: | | $autoload['libraries'] = array('database', 'email', 'session'); | | You can also supply an alternative library name to be assigned | in the controller: | | $autoload['libraries'] = array('user_agent' => 'ua'); */ $autoload['libraries'] = array('smartie'=>'smarty');動作確認
デバッグメッセージを出力させるために、Configをいじります。
今回は「4:All Messages」を設定します。application/config/config.php/* |-------------------------------------------------------------------------- | Error Logging Threshold |-------------------------------------------------------------------------- | | You can enable error logging by setting a threshold over zero. The | threshold determines what gets logged. Threshold options are: | | 0 = Disables logging, Error logging TURNED OFF | 1 = Error Messages (including PHP errors) | 2 = Debug Messages | 3 = Informational Messages | 4 = All Messages | | You can also pass an array with threshold levels to show individual error types | | array(2) = Debug Messages, without Error Messages | | For a live site you'll usually only enable Errors (1) to be logged otherwise | your log files will fill up very fast. | */ $config['log_threshold'] = 4;ブラウザで「 http://127.0.0.1:8000/ 」を表示させます。
正常に動作したなら、以下のデフォルトのページが表示されます。そして、ログにもSmartyライブラリの初期化完了のログ「Smarty Class Initialized」が出力されます。
application/logs/log-.php・・・ INFO - 2019-08-04 20:07:34 --> Loader Class Initialized DEBUG - 2019-08-04 20:07:34 --> Smarty Class Initialized INFO - 2019-08-04 20:07:34 --> Controller Class Initialized INFO - 2019-08-04 20:07:34 --> File loaded: /src/application/views/welcome_message.php INFO - 2019-08-04 20:07:34 --> Final output sent to browser DEBUG - 2019-08-04 20:07:34 --> Total execution time: 0.0578サンプルを作成する
codeigniterが初見なので、サンプルを書いてみます。
application/controllers/HelloWorld.php<?php defined('BASEPATH') OR exit('No direct script access allowed'); class HelloWorld extends CI_Controller { public function index() { $data = [ 'title' => 'Hello World!', ]; $this->smarty->view('HelloWorld.tpl',$data); } }application/views/template/HelloWorld.tpl{$title}ブラウザで、「 http://127.0.0.1:8000/HelloWorld 」にアクセスをすると、以下のように表示されます。
「HelloWorld.php」の$data['title']を変更して、動的に表示が変更されることを確認してみてください。
以上の構築した環境を、以下のGithubで公開をしています。スターを付けていただけると、誰かの役に立ったんだとわかって、モチベーションがあがります。よろしくお願い致します。
https://github.com/idani/CodeIgniter3.1-Smarty3.1-Smaple
参考文献
- 投稿日:2019-08-04T21:06:31+09:00
AamazonLinuxにてphp7.3のxdebugを有効にする
背景
php7.3にアップグレード後、phpunitにてカバレッジレポートを出力しようとしてxdebugをinstallしようと思いましたが、
2019年08月01日時点でamzn-mainリポジトリにはphp73-pecl-xdebug.x86_64パッケージは存在しませんでした。対応方針
以下の理由により、
remi-php73リポジトリからphp7.3をインストールしました。
それぞれのリポジトリからのインストールにおいても回避方法はあると思いますが、WEB上の事例が多い方法としてremi-php73を選択しています。
amzn-mainにはphp7.3のxdebugのパッケージが無いremi-safeではphpコマンドではなくphp73コマンドになりphpunitがそのまま動作しない
- (aliasを貼れば動くかもしれない)
phpenvは利用経験がなかったためskip導入
remiリポジトリを設定
$ rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpminstall
amzn-mainが有効な状態でphpモジュールを導入しようとすると、php5.4が導入されます。
remi-php73を有効化、amzn-mainを無効化してから、yum installl phpするとうまく指定できました。$ sudo yum install --enablerepo=remi,remi-php73 --disablerepo=amzn-main php php-fpm php-xdebug導入途中で発生した事象
httpd24との依存関係でエラー
エラー: httpd24-tools conflicts with httpd-tools-2.2.34-1.16.amzn1.x86_64 エラー: httpd24 conflicts with httpd-2.2.34-1.16.amzn1.x86_64apache2.4を利用していないのであれば、httpd24モジュールを削除。
$ sudo yum remove httpd24 httpd24-toolslibwebpとの依存関係でエラー
エラー: パッケージ: gd-last-2.2.5-5.el6.remi.x86_64 (remi-safe) 要求: libwebp.so.5()(64bit)
amzn-mainで導入されているlibwebpですと依存関係でエラーが発生するようです。
一旦libwebpを削除し、epelから入れ直します。$ yum remove libwebp $ yum --disablerepo=amzn-main --enablerepo=epel install libwebp
- 投稿日:2019-08-04T17:23:01+09:00
cURLを使って外部サービスのAPIを叩く
PHPで組んだシステムからAPIをぶっ叩いて外部サイトとあれやこれややり取り出来る機能を追加してほしいというお題をもらったので、cURLを利用して外部APIを叩く勉強をした。
そのときの備忘録目的
社内チャットにchatworkを使用しているので、社内システムから業務に必要な申請があった時、管理者にchatworkへ「申請があったよ」ってタスクを登録してあげたりする機能を追加したい。
cURLの使い方
基本
$url = "叩きたいAPIのURL"; $ch = curl_init(); //初期化オプション設定
curl_setopt($ch, CURLOPT_URL, $url); //URLの指定 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //返り値が欲しい実行
$response = curl_exec($ch); curl_close($ch); //終了基本的にはこんなところ。
あとはやりたい動作毎にメソッドを指定してあげる。GETメソッド
APIを叩いてデータを取得したい場合。ユーザー情報とかroomIDとか。
特に設定は必要なく、上記の基本部分だけの記述でOK。POSTメソッド
何かしらのデータ登録的な処理を行ってほしい時。メッセージの投稿やタスクの設定など。
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));まずPOST処理をしたいことを設定。
その後、設定するためのパラメータを配列形式で指定する。設定する内容は行いたい処理によって様々なので、使いたいAPIのドキュメントを参照して設定する。今回の場合だと、タスク登録時に発行されるタスクIDが後々の処理で必要になるので、POSTの場合でも返り値を受け取る設定をしておいた。
PUTメソッド
変更などの処理を行ってほしい場合。今回だと、タスクステータスの自動変更など。
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));POSTメソッドとは、メソッドの指定の仕方が違う。
オプションにCURLOPT_PUTがあったので、これを指定すれば動きそうと思ったら動かなくて、上記の方法でPUTメソッドと指定してあげないとダメ。
ついにで今回はPOSTもPUTも記述を統一したので気にしてなかったが、PUTする場合はパラメータもhttp_build_queryで変換してあげないとダメらしい。
POST時に返り値で貰えるタスクIDがあれば、そのタスクIDをURLの中で指定し、タスクのパラメータをAPIで自動的に切り替える機能も盛り込むことが出来た。あとはDELETEメソッドもあるが、今回は使用しなかったのでここでは割愛。
とりあえず、これらの方法で無事外部サービスとの連携機能を実装することが出来た。
- 投稿日:2019-08-04T16:57:44+09:00
php-master-changes 2019-08-03
今日は strip_tags() のバグ修正があった!
2019-08-03
cmb69: Fix #78346: strip_tags no longer handling nested php tags
- https://github.com/php/php-src/commit/a87ef5e3ddfdb3083c2b62bb25835b74d9a3d083
- [7.3~]
- strip_tags() で PHP タグをネストした場合に除去がうまくいかなくなっていた問題の修正
- 投稿日:2019-08-04T16:17:26+09:00
【アドバイスください】PHPフレームワーク「OFFWORK 5.0」
経緯
PHPでアプリケーションフレームワーク的なものができたので、公開します。
現在100行程度のindex.phpですが、磨けば、ミニマリスト、シンプリスト、学習者、など、一部の方のお役に立つかもしれないと、妄想しています。
「OFFWORK」と名付けました(語源:One File Freme Work)(one punche manに似てる?)。英語でOff Workは「非番」の意だそうです。日曜大工で犬小屋を作るような、気楽な開発のお役に立てば幸いです。お願い
PHPやWEBプログラムについて、私はまだまだ未熟です。このソースから、「セキュリティ上の問題・課題や解決策」「少しの改良で効果・効率・使い勝手が上がるアイデア」など、教えていただけましたら、大変ありがたいです。ご奇特な方がいらっしゃいましたら、アドバイスをよろしく願いします。
動作概要
「~/index.php/{コントロール名}/{アクション名}/{パラメータ}」の形式のURLでアクセスします。./controllers/{コントロール名}.phpの中にあるclass {コントローラ名}のfunction {アクション名}が呼ばれ処理した後、./views/{コントロール名}_{アクション名}.phpが呼ばれ画面を作成します。functionの戻り値配列に"controller"か"view"があれば、画面作成のPHPを変更します。functionの戻り値配列に"redirect"があれば、リダイレクトを行います。「_」から始まるコントローラ名とアクション名は、URLから呼び出されません。
モデルのクラスは、class _{テーブル名} extends model を推奨します。_{テーブル名}.phpのファイルを作り、コントローラからrequire_onceしても構いませんが、コントローラのファイルの中にモデルのコードを書くと見通しが良いかも知れません。複数形/単数形、キャメルケース/スネークケース、などの命名規則は定めていません。
実装例
フルームワークの本体はindex,phpのみです。他の4つのファイルuser*.phpはサンプルです。保存する際はutf8を指定してください。「********」の箇所はDB接続の設定に書き換えてください。
/index.php<?php header('X-FRAME-OPTIONS: SAMEORIGIN'); session_start(); ini_set('error_reporting', E_ALL); ini_set('display_errors', '1'); $DSN="mysql:host=********;dbname=********;charset=utf8mb4"; $DB_USERNAME="********"; $DB_PASSWORD="********"; include("./local.php");//load local setting //---------- class Model{ protected $db; public function __construct(){ global $DSN,$DB_USERNAME,$DB_PASSWORD; try{ $options = array( PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES=>false, PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC, PDO::MYSQL_ATTR_INIT_COMMAND=>"SET CHARACTER SET 'utf8'"); $this->db=new PDO($DSN,$DB_USERNAME,$DB_PASSWORD,$options); }catch(PDDOException $e){$this->exit500($e->getMessage());} } function executeQuery($sql,$vars,$fetchable=false){ try{ $stmt=$this->db->prepare($sql); $result=$stmt->execute($vars); if($fetchable==true){ $data=$stmt->fetchall(); return array("data"=>$data,"return"=>$result,"count"=>$stmt->rowCount()); }else{ return array("return"=>$result,"count"=>$stmt->rowCount()); } }catch(PDDOException $e){$this->exit500($e->getMessage());} } function exit500($msg){ header('Content-Type: text/plain; charset=UTF-8', true, 500); exit(h($msg)); } } function h($s){ return htmlspecialchars($s,ENT_QUOTES,'UTF-8'); } function redirect($s){ $tmp=$_SERVER["HTTP_ORIGIN"].$_SERVER["SCRIPT_NAME"].$s; header('Location: '.$tmp); exit; } //session---------- function _login($name,$value){ session_regenerate_id(true); $_SESSION['_auth']=true; $_SESSION[$name]=$value; } function _logout($name){ unset($_SESSION['_auth']); unset($_SESSION[$name]); //session_destroy(); } function _verify_login($exitto){ if(isset($_SESSION['_auth'])){if($_SESSION['_auth']===true){return;}} redirect($exitto); } //paramaters---------- $controller="";$action="";$para=array();$msg=array();$fname=""; if (!empty($_SERVER['PATH_INFO'])) { $para=explode('/',$_SERVER['PATH_INFO']); $controller=array_shift($para);if($controller==""){$controller=array_shift($para);} $action=array_shift($para); } //default page---------- if($controller==""||$action==""||substr($controller,0,1)=="_"||substr($action,0,1)=="_"){ $fname='./views/default.php'; }else{ //controller&action---------- $fname="./controllers/".basename($controller).".php"; if(file_exists($fname)){ include($fname); if(class_exists($controller)){ $class = new $controller(); if(method_exists($class,$action)){ $d=$class->$action($para); if(!is_array($d)){$d=array($d);} }else{$msg[]="method [".h($action)."()] not found in the class [".h($controller)."] .";} }else{$msg[]="class [".h($controller)."] not found.";} }else{$msg[]="file [".h($fname)."] not found.";} //redirect---------- if(isset($d['redirect'])){redirect($d['redirect']);} //view---------- if(isset($d["cntroller"])){$controller=$d["cntroller"];} $view=$action;if(isset($d["view"])){$view=$d["view"];} $fname="./views/".basename($controller)."_".basename($view).".php"; } if(!file_exists($fname)){$msg[]="view file [".h($fname)."] not found.";} //drawing---------- header('Content-Type: text/html; charset=utf-8'); header('X-FRAME-OPTIONS: SAMEORIGIN'); foreach($msg as $x){echo $x."<br>";} if(file_exists($fname)){include($fname);} // PHP frame work "offwork 5.0" made by arigato2050/controllers/user.php<?php class user{ function login(){ $msg=""; if(isset($_REQUEST["username"])&&isset($_REQUEST["password"])){ $_user=new _user(); $result=$_user->check($_REQUEST["username"],$_REQUEST["password"]); if(count($result)==1){ _login('app',$result[0]); return array("redirect"=>"/user/index"); }else{$msg="usernameまたはpasswordが異なります";} } $username="";if(isset($_REQUEST["username"])){$username=$_REQUEST["username"];} return array("data"=>array("username"=>$username,"password"=>""),"msg"=>$msg); } function logout(){ _logout('app'); return array("redirect"=>"/user/login"); } function index(){ _verify_login("/user/login"); $_user=new _user(); $result=$_user->getAll(); return array("data"=>$result); } function add(){ _verify_login("/user/login"); return array("view"=>"edit","data"=>array("id"=>0,"username"=>"","password"=>"")); } function edit($para){ _verify_login("/user/login"); $_user=new _user(); $result=$_user->get1($para[0]); return array("data"=>$result); } function save($para){ _verify_login("/user/login"); $_user=new _user(); $result=$_user->set1($_REQUEST); return array("redirect"=>"/user/index","data"=>$result); } function del($para){ _verify_login("/user/login"); $_user=new _user(); $result=$_user->del1($para[0]); return array("redirect"=>"/user/index","data"=>$result); } } class _user extends Model{ function getAll(){ $result=$this->executeQuery("SELECT * FROM user;",array(),true); return $result['data']; } function check($username,$password){ $result=$this->executeQuery("SELECT * FROM user WHERE username=:username and password=:password;",array(":username"=>$username,":password"=>$password),true); return $result['data']; } function get1($id){ $result=$this->executeQuery("SELECT * FROM user WHERE id=:id;",array(":id"=>$id),true); return $result['data'][0]; } function del1($id){ $result=$this->executeQuery("DELETE FROM user WHERE id=:id;",array(":id"=>$id)); return $result['data']; } function set1($para){ if($para['id']==0){ $result=$this->executeQuery("INSERT INTO user (username,password,created) VALUES (:username,:password,:created);",array(":username"=>$para['username'],":password"=>$para['password'],":created"=>date("Y/m/d H:i:s"))); }else{ $result=$this->executeQuery("UPDATE user SET username=:username, password=:password, modified=:modified WHERE id=:id;",array(":id"=>$para['id'],":username"=>$para['username'],":password"=>$para['password'],":modified"=>date("Y/m/d H:i:s"))); } return $result['data']; } } /* サンプルのDBは以下です。`id`はAUTO_INCREMENTです。ログイン用のusername/passwordを登録してからお使いください。 CREATE TABLE `user` ( `id` int(11) NOT NULL, `username` text, `password` text, `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; *//views/user_login.php<!DOCTYPE html> <html> <body> <h1>login</h1> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/login/'; ?>" method="post"> username:<input type="text" name="username" value="<?php echo h($d['data']['username']); ?>"><br> password:<input type="password" name="password" value="<?php echo h($d['data']['password']); ?>"><br> <input type="submit" value="送信"> </form> <?php echo $d["msg"]; ?><br> <?php if($d["msg"]>""){echo date("Y/m/d H:i:s")."<br>";} ?> </body> </html>/views/user_index.php<!DOCTYPE html> <html> <body> <h1>index</h1> <table border=1> <tr> <th>id</th> <th>username</th> <th>password</th> <th>created</th> <th>modified</th> </tr> <?php foreach($d['data'] as $row ){ echo "<tr>"; ?> <td><a href="<?php echo $_SERVER['SCRIPT_NAME'].'/user/edit/'.$row['id']; ?>"><?php echo h($row['id']); ?></a></td> <td><?php echo h($row['username']); ?></td> <td><?php echo h($row['password']); ?></td> <td><?php echo h($row['created']); ?></td> <td><?php echo h($row['modified']); ?></td> <?php echo "<tr>"; } ?> </table> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/add/'; ?>" method="post"> <input type="submit" value="追加"> </form> <br> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/logout/'; ?>" method="post"> <input type="submit" value="ログアウト"> </form> <!-- <br>cookie:<br> <?php var_dump($_COOKIE); ?> <br>session:<br> <?php var_dump($_SESSION); ?> --> </body> </html>/views/user_edit.php<!DOCTYPE html> <html> <body> <h1>edit</h1> id:<?php if($d['data']['id']==0){ echo "(new)"; }else{ echo h($d['data']['id']); } ?> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/save/'; ?>" method="post"> <input type="hidden" name="id" value="<?php echo h($d['data']['id']); ?>"> username:<input type="text" name="username" value="<?php echo h($d['data']['username']); ?>"><br> password:<input type="text" name="password" value="<?php echo h($d['data']['password']); ?>"><br> <input type="submit" value="送信"> </form> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/index/'; ?>" method="post"> <input type="submit" value="キャンセル"> </form> <?php if($d['data']['id']!=0){ ?> <br> <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/del/'.h($d['data']['id']); ?>" method="post"> <input type="submit" value="削除"> </form> <?php } ?> </body> </html>実行例
[URL]http://~/index.php/user/login
[URL]http://~/index.php/user/index
[URL]http://~/index.php/user/edit/3
参考
参考にさせていただきました。
https://qiita.com/kahirokunn/items/175b82295ab683ffb624
http://choilog.com/katty0324/blog/6
https://qiita.com/mitsuru793/items/45b2452284e321c7a5a9
https://qiita.com/mpyw/items/b00b72c5c95aac573b71
https://qiita.com/7968/items/6f089fec8dde676abb5b
https://qiita.com/t_kawai/items/ed5e86c32966931c8e9b
- 投稿日:2019-08-04T13:56:44+09:00
Laradock 起動時にphp-redisのFailed to fetchが出て失敗した時の対処法
最近
Dockerの基本を勉強したので、早速Laravelで何か作ってみようとLaradockをローカルで起動しようとした際に表題のエラーが出たので対処した方法を記載します。もっと簡単な、あるいはまっとうなやりかたあれば教えていただきたく、です。
(確認の仕方も含めて丁寧目に書いています)※Qiitaへの投稿は初めてなので、色々不安ですが、誤りなどありましたらご指摘ください。
状況
ローカルへの環境構築でdocker起動までの手順は下記の記事に従いました。
なお私の環境はmacです。
DockerでLaravelの環境を構築し、Herokuへのデプロイを目指すためのチュートリアルdockerの起動
laradockディレクトリ内でdocker-compose upします。$ docker-compose up -d nginx mysql phpmyadminすると今回は以下のようなエラーが出て、起動に失敗しました。
エラー内容
E: Failed to fetch http://ppa.launchpad.net/ondrej/php/ubuntu/pool/main/p/php-redis/php-redis_4.2.0-1+ubuntu16.04.1+deb.sury.org+1_amd64.deb 404 Not Found E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing? ERROR: Service 'workspace' failed to build: The command '/bin/sh -c if [ ${INSTALL_PHPREDIS} = true ]; then apt-get install -yqq php-redis ;fi' returned a non-zero code: 100
php-redisのfetchに失敗しています。※ ちなみにurl(http://ppa.launchpad.net/ondrej/php/ubuntu/pool/main/p/php-redis/php-redis_4.2.0-1+ubuntu16.04.1+deb.sury.org+1_amd64.deb) を確認するとたしかに該当のページが存在しません。
続いて「
run apt-get updateするか--fix-missingオプションつけてやってみて」みたいなことが書いてあります。
とりあえずapt-get updateを試してみることにします。手順
最後のエラーメッセージに
workspaceのビルドの際に下記のコマンドで失敗してるとあります。'/bin/sh -c if [ ${INSTALL_PHPREDIS} = true ]; then apt-get install -yqq php-redis ;fi' returned a non-zero code: 100なので今回はこの上記の箇所に
apt-get updateを足してみることにします。上記の箇所ってどこ?
Dockerではコンテナ起動時に
Dockerfileの内容に従ってビルドを行います。
なのでworkspaceのDockerfileを探します。# カレントディレクトリを確認 $ pwd # laradockディレクトリ内であることを確認 # 私の場合 → /Users/ユーザー名/Docker/project_name/laradock # ディレクトリ内のリストを表示 $ ls DOCUMENTATION ide-codiad nginx LICENSE ide-icecoder percona adminer ide-theia php-fpm aerospike ide-webide php-worker apache2 ipython phpmyadmin aws-eb-cli jenkins portainer beanstalkd jupyterhub postgres beanstalkd-console kibana postgres-postgis caddy laravel-echo-server rabbitmq cassandra laravel-horizon redis certbot logs redis-cluster couchdb logstash redis-webui docker-compose.sync.yml maildev rethinkdb docker-compose.yml mailhog selenium docker-registry manticore solr docker-sync.yml mariadb sonarqube docker-web-ui memcached sync.sh elasticsearch minio thumbor env-example mongo traefik gitlab mongo-webui travis-build.sh grafana mosquitto varnish graylog mssql 'workspace' haproxy mysql zookeeper hhvm neo4j # workspaceがあるのを確認 ※わざとハイライトにしています # workspaceへ移動 $ cd workspace # workspace内のリストを表示 $ ls 'Dockerfile' composer.json insecure_id_rsa.pub aerospike.ini crontab mc aliases.sh insecure_id_rsa xdebug.ini auth.json insecure_id_rsa.ppk # Dockerfileがあるのを確認 ※わざとハイライトにしています # Dockerfileを編集 $ vim Dockerfileコマンドモードで
/[ ${INSTALL_PHPREDIS} = true ]などとして該当箇所を検索します。DockerfileRUN if [ ${INSTALL_PHPREDIS} = true ]; then \ apt-get install -yqq php-redis \ ;fi # 上記を下記のように書き換える RUN if [ ${INSTALL_PHPREDIS} = true ]; then \ apt-get update && \ apt-get install -yqq php-redis \ ;fi
:wqで保存してファイルを閉じます。dockerを再度起動
laradockディレクトリに戻って再度起動してみます。$ cd ../ # workspaceディレクトリから一つ上のディレクトリに戻る $ docker-compose up -d nginx mysql phpmyadmin ... # 初回起動時はそこそこ時間がかかる Creating project_name_docker-in-docker_1 ... done Creating project_name_mysql_1 ... done Creating project_name_workspace_1 ... done Creating project_name_phpmyadmin_1 ... done Creating project_name_php-fpm_1 ... done Creating project_name_nginx_1 ... done今度はうまく起動できたようです。
一応dockerコンテナの起動を確認します。
$ docker ps --format "table {{.Names}}" NAMES project_name_nginx_1 project_name_php-fpm_1 project_name_phpmyadmin_1 project_name_workspace_1 project_name_mysql_1 project_name_docker-in-docker_1上記のように表示されればコンテナが起動できています。
お疲れ様でした。
- 投稿日:2019-08-04T13:56:36+09:00
【PHP】文字列を一文字づつ分解して配列に格納したい(str_split)
【PHP】文字列を一文字づつ配列に分解
str_split()
PHPで○番目の文字や最後の文字を切り出したい、などの場面がよくあります。
こうした場合、文字列を一文字づつの配列にしてあげると色々処理ができて便利です。結構探すのに時間かかってしまったので備忘録もかねてメモ。例えば、文字列の"hello world"を配列形式で('h','e','l','l','o','w','o','r','l','d');のようにしたい。
使い方
上記の場面で便利な関数はstr_split()です。こちらの関数を上記に適用すると下記のようになります。
$str = "hello world"; $array = str_split($str); print_r($array);出力結果;
Array ( [0] => h [1] => e [2] => l [3] => l [4] => o [5] => [6] => w [7] => o [8] => r [9] => l [10] => d )数値に対しても、同じようにstr_spritで配列に格納をすることができます。(こっちのケースの方が使用することが多いかも?)
*ただし文字列として格納されます。厳密にint型で扱いたい場合はint_val等の型変換が必要になリます。$num = 123456789; $array1 = str_split($num); print_r($array1);出力結果;
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 [6] => 7 [7] => 8 [8] => 9 )まとめ
str_splitを使うと、文字列を配列に変換できる
追記
こうした関数の処理を手軽に確認したい時、僕はよくpaiza.ioを使ってます。
https://paiza.io/ja
- 投稿日:2019-08-04T11:44:33+09:00
Laravelで定数をつかうよ
まえがき
Laravelで定数を使う方法をメモ。
configを使う方法はよく見るのだけど、
定数クラスを使うパターンが見つからなかったので。前提
- PHP7.3.6
- Laravel 5.5.45
- サンプル
- Usersテーブルにgenderカラムがある
- genderカラムは以下の値を持つ
- 0: 回答なし
- 1: 男性
- 2: 女性
configを使う
基本はこちらのブログで書いてある通り。
(上記ブログでの転載元ShellingSoft開発者ブログが表示されなかった。。)const.phpで一元管理するパターン
config/const.php<?php return [ // usersで使う定数 'Users' => [ 'GENDER_NONE' => 0, 'GENDER_MAN' => 1, 'GENDER_WOMAN' => 2, 'GENDER_LIST' => [ 'gender_none' => 0, 'gender_man' => 1, 'gender_woman' => 2, ], ], ];テーブル名を第1キー(第2キーか?)にして、カラム+意味で定数名をつくる。
GENDER_LISTは入力欄を楽に作りたいから用意した。
Validationでも使えるしね。resources\views\users\user.blade.php<table> <tr> <th>性別</th> <td> <ul> @foreach (config('const.Users.GENDER_LIST') as $key => $value) <li>{!! Form::radio('gender', $value) !!}{{ __("values.gender_{$key}") }}</li> @endforeach </ul> </td> </tr> </table>テーブルごとにファイル分けるパターン
const.phpだけでつくると1ファイルが大きくなって見づらくなるので、ちょっと定数が多いなと思ったら、テーブル単位でファイルを分けてみた。
その際、今度はconfigディレクトリがいっぱいになってゲンナリするので、定数用のサブディレクトリを用意した方がよさげかな?config └ consts └ users.phpresources\views\users\user.blade.php<table> <tr> <th>性別</th> <td> <ul> @foreach (config('consts.users.GENDER_LIST') as $key => $value) <li>{!! Form::radio('gender', $value) !!}{{ __("values.gender_{$key}") }}</li> @endforeach </ul> </td> </tr> </table>定数クラスを使う
やっぱ定数は
\User::GENDER_LISTみたいに呼び出したい・・・・
とはいえ、モデル内のクラス定数だと\App\User::GENDER_LISTと頭に\Appがつくのが煩わしい。。
そこで、定数だけを切り離した定数クラスを用意してみた。まずは適当なところに定数ファイルをつくる。
app └ Consts └ UserConst.phpapp/Consts/UserConst.php<?php namespace App\Consts; // usersで使う定数 class UserConst { const GENDER_NONE = 0; const GENDER_MAN = 1; const GENDER_WOMAN = 2; const GENDER_LIST = [ 'none' => self::GENDER_NONE, 'man' => self::GENDER_MAN, 'woman' => self::GENDER_WOMAN, ]; }次に、aliasを設定する。
config/app.php<?php return [ // // 省略 // 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, // // 省略 // 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, // 定数系 'UserConst' => App\Consts\UserConst::class, ];ちなみに、クラス名が
(テーブル名)Constとしたのは、
テーブル名のままaliasを設定するとなんかにぶつかるかもとヒヨったからです。。
aliasで設定する名前だけUsersConstとしておけば、クラス自体の名前はUserでも大丈夫。
ただ、名前がずれてるとちょっと見つけづらいので。あとは使うだけ。
resources\views\users\user.blade.php<table> <tr> <th>性別</th> <td> <ul> @foreach (\UserConst::GENDER_LIST as $key => $value) <li>{!! Form::radio('gender', $value) !!}{{ __("values.gender_{$key}") }}</li> @endforeach </ul> </td> </tr> </table>最初はFacadeを使って読み込もうとしていたんだけどどうもうまくいかず、
試しに直接 alias で設定したらうまくいきました。あとがき
そもそもテンプレート上で定数を使いたかったから色々探したんだけど、
軒並みconfig/const.phpを使う方法しか見つからず、
「方法がこれだけってんなわけねぇだろ・・・・(´・ω・`)」と思って調べました。おかげで config 内にサブディレクトリを置けることが分かったのはちょっとした収穫。
- 投稿日:2019-08-04T09:42:08+09:00
PHPの無名関数式とは何か、そしてPHP7.4のアロー関数にそなえよう
PHP 7.4は現在beta1までリリースされたので、今頃はみなさまも12月のPHP 7.4.0正式リリースに向けて遊び倒してるところだと存じます。7.4でPHPに新たに導入される文法はいくつかありますが、その目玉のひとつがArrow Functions 2.0です。
ちなみにこの記事は「アロー関数はすべての無名関数式の代替になるわけではない」として書きはじめたので、今回本当に書きたかったのはそのあたりです。
前提知識: 式と文
PHPをはじめとするC言語に影響を受けた多くのプログラミング言語では「式(expression)」と「文(statement)」を厳密に区別する特徴があります。
雑に言うと、文とは以下のようなものです。
- 制御構造
if,else,elseif(endif)foreach(endforeach)for(endfor)while,do(endwhile)switch,case(endswitcn)try,catch,finally- ラベル
gotoreturnthrow- 定義文
function(式もある)class(式もある)traitinterface- 宣言文
usedeclare(enddeclare)namespaceconstglobalvarpublic,protected,privatestatic__halt_compiler()- その他の構文
echo,これらの文の分類は私が大雑把に分けたもので、公式の定義ではありません。
それ以外の言語要素は基本的に式です。また、式同士は構文や演算子によって結合して式になります。
- 関数呼び出し
var_dump($arg)$obj->m($arg)Klass::m($arg)vendor\ns\f($arg)- 単項演算子
!cond()+123,-123~$bit@foo()$n++,$n--,++$n,--$n- 演算子式
- 比較式
$a < $b,$a > $b,$a <= $b,$a <= $b$a == $b,$a != $b,$a === $b,$a !== $b,$a <> $b$a <=> $b宇宙船演算子$obj instanceof Klass型演算子- 算術演算
$a + $b,$a - $b,$a * $b,$a / $b,$a % $b,$a ** $b- ビット演算
$a & $b,$a | $b$bit << $n,$bit >> $n- 文字列結合
$str . "ing"- 条件演算子
$var ? 'foo' : bar()$var ?: func()エルビス演算子$var ?? 'default'null合体演算子- 代入
$var = 1list($a, $b) = foo(),[$a, $b] = $foo(),list('a' => $a, 'b' => $b) = ['a' => 1, 'b' => 2]$a += $b,$a -= $b,$a *= $b,$a /= $b,$a %= $b$var && func()- リテラル・変数
$var,${var},$$varname,${'var'}"{$var}","$(var)"[1, 2, 3],['key' => $val],array(1, 2, 3),array('key' => $val)- その他の言語構造
(int)$varキャスト$arr['key']配列アクセス$obj->propプロパティアクセスKlass::CONST_VALUEクラス定数isset($var)empty($var)assert($cond)exit(0),die(1)yield $varinclude,require,include_once,require_onceprint("message")
new Klass,new Klass($arg),new $classnew class{ ... }などです。寝惚けた頭で適当に列挙しただけなので、漏れがありそうですね。
0個以上の式の組み合せから構成される文を「式文(expression statement)」と呼びます。式文は
;または?>で終端します。以下の
;で終端する行はすべて式文です。require_once __DIR__ . '/Foo.php'; $config = include(__DIR__ . '/config.php'); $var = 1; f(); $a = [1, 2, ...y()]; $b = array_merge($a, z()); $c = $options + ['foo' => true]; $obj->method($a, $b, 1); throw new LogicException('Aieee') $foo === 1 or die(1); assert($hoge instaceof Fuga); exit; print(1);原則として、式と直接組み合せられるのは式だけです。
また、文の中にも式しか書けない箇所がいくつもあります。
if ($cond) { // ... } foreach (foo() as $f) { // ... } switch ($value) { // ... }これらの文の
$condやfoo(),$valueの場所には式しか書くことができません。翻せば、式であれば何でも書いてよいということです。関数式とは何か
関数定義文の制約
通常の関数定義文は以下のような構造をとります。
function x() { var_dump('Called x()'); }この文はグローバル名前空間に
x()という関数として定義されます。次に、
x()の中でローカルに使いたい関数を用意したくなったとします。function x() { function _inner() { var_dump('Called inner()'); } _inner(); _inner(); _inner(); } x(); // 'Called inner()' が三回出力される実はこの文は有効です。しかし、いくつかの問題があります。
ひとつは、
_inner()はx()の外からも呼べることです。PHPのfunction文は実はさまざまな箇所に記述することが文法上許されるのですが、メソッド定義の文脈を除いて、「現在の名前空間に関数を定義する」機能を持つだけです。メソッド定義の文脈とは、クラス定義(class,interface,traitを含む)の直下のことです。つまり、
_inner()は「x()の中だけで使えるローカル関数」ではなく、PHPではただのグローバル関数定義です1。次に、PHPでは原則として関数の再定義はできない制約があることです。先ほどのコードは
x()を一回しか起動してないので一見して動作するように見えましたが、x()を2回呼び出すとFatal error: Cannot redeclare _inner()というエラーが発生するはずです。PHPの構文では以下のような記述も許されますが、実際にはまったくの無価値です。
for ($i = 0; $i == 0; $i++) { function x() { var_dump("called x()"); } }関数定義文の問題は、関数を作成するときに必ず名前がついてしまうので複数回定義できないことです。また、変数によって関数名や定義内容を動的に変化させることも基本的にはできません2。
function関数式関数式は名前をつけずにローカルな関数を作成できる言語機能です。ここで作った関数は
Closureクラスのオブジェクトなので、変数に代入することも、関数に渡すこともできます。この関数にはfunction文で定義された関数のような名前がないので「無名関数」とも呼ばれます。function x() { $inner = function () { var_dump('Called inner()'); } $inner(); $inner(); $inner(); } x(); // 'Called inner()' が三回出力される変数のキャプチャ (クロージャ)
この関数式は単にローカルで使用するための関数として使うこともできるのですが、もっとおもしろい特徴があります。それは変数を「キャプチャ」(捕捉)できることです。
わかりやすい例として、「常に1を返す関数」「常に2を返す関数」「常に3を返す関数」…「常に
nを返す関数」 を数多く作ることができます。$f = []; foreach (range(0, 10) as $n) { $f[$n] = function () use ($n) { return $n; }; } var_dump($f[0]()); // int(0) が表示される var_dump($f[1]()); // int(1) が表示される var_dump($f[2]()); // int(2) が表示される var_dump($f[3]()); // int(3) が表示される // ...ここでは
useが肝です。この場合は$nの変数参照ではなく、値がコピーされて捕捉されます。逆に言うと、「名前付きで定義されない」「変数をキャプチャできる」以外は通常の関数と同じことができます。この「変数を捕捉できる」という特徴を活かすと、とてもおもしろい使いかたができます。
// キャラクターのせりふを画面に出力する $character = function ($name) { return function ($script) use ($name) { echo "{$name} 「{$script}」\n"; }; }; $taro = $character("太郎"); $jiro = $character("二郎"); $taro("こんにちは、初めまして"); $jiro("どうも、私は二郎です");
$characterは、最初に受け取った変数を閉じ込めた新しい関数を作って返します。このような特徴から「PHPの無名関数はクロージャである」と説明でき、それはClosureというクラス名にも反映されています。クロージャと状態の更新
無名関数は変数を捕捉できますが、デフォルトでは「値をコピーして捕捉」するため、変更することはできません。しかし、リファレンスを組み合せることで変更も可能です。
この仕様を利用すれば、クロージャの例題としてしばしば例に出されるカウンターも実装できます。
function make_counter(int $n = 0) { return function ($add = 1) use (&$n) { return $n += $add; }; } // この関数は $make_counter = function (){...}; で定義してもよい $c = make_counter(10); var_dump($c(2)); var_dump($c(2)); var_dump($c(2)); var_dump($c(5));無名関数と再帰
通常の関数定義文であれば問題なく実行できる関数の再帰呼び出しですが、無名関数では制約があります。
function fib($n) { return ($n < 2) ? 1 : fib($n - 1) + fib($n -2); } // 問題なく再帰できるここまで説明したように変数リファレンスのキャプチャを利用することで自己再帰できます。
$fib = function ($n) use (&$fib) { return ($n < 2) ? 1 : $fib($n - 1) + $fib($n -2); };
use (&$fib)や&を省略すると動きません。関数を作成する時点で$fib変数は定義されておらず、キャプチャするためにまだ定義されない関数をコピーすることもできないからです。リファレンスキャプチャではこの制約を超えられます。mapと関数式
私は中学校の算数で挫折した人間なのでよく知りませんが、数学では関数ということばを写像(map)という用語で置き換えることができるようです。プログラミング言語の機能では、集合に対して関数を適用する操作に
mapという名前が付けられることがあります。PHPではarray_mapという、関数に対して関数を適用する機能があります。中学校の算数の授業で習った関数や方程式は $f(x) = x \times 2$ や $y = x \times 2$ のような形式で書くことができ、以下のような表にすることがありました。
x 0 1 2 3 4 5 6 7 8 9 y 0 2 4 6 8 10 12 14 16 18 この関数をPHPの関数式で表すと、以下のようにできます。
$f = function ($n) { return $n * 2; };さらに、PHPでこのような表を作るために、さきほど紹介した
array_map()を利用できます。$x2 = function ($n) { return $n * 2; }; print_r(array_map($x2, range(0, 9))); // Array // ( // [0] => 0 // [1] => 2 // [2] => 4 // [3] => 6 // [4] => 8 // [5] => 10 // [6] => 12 // [7] => 14 // [8] => 16 // [9] => 18 // )PHP 7.4で追加されたアロー関数(short function)
function式のデメリットは、単に一つの式を返したいだけでもfunction (...) { return ...; }の形で書かねばならず、たいへん野暮ったいことです。PHPにとって関数式の先輩であるJavaScriptにはECMAScript2015でアロー関数が導入されました。それに範をとってPHPにも2015年頃から幾度と短い関数式記法が提案されてきましたが3、2019年にようやくPHP: rfc:arrow_functions_v2という提案が受理され、PHP 7.4で正式に利用できるようになりました。
Arrow Functions 2.0仕様のアロー関数は以下のような記法です。
fn(parameter_list) => exprJavaScriptのアロー関数とは異なり、引数リスト部は必ず
fn()で括らねばなりません。また=>の右辺に欠けるのは単一の式です。$before = array_map(function ($n) { return $n * 2; }, range(0, 9)); $after = array_map(fn($n) => $n * 2, range(0, 9));引数や型宣言の仕様は既存の
function関数式と同様です。fn(array $x) => $x; fn(): int => $x; fn($x = 42) => $x; fn(&$x) => $x; fn&($x) => $x; fn($x, ...$rest) => $rest;このアロー関数のおいしいところとしては
use ()で変数の明示的なキャプチャが不要になったことです。$f = []; // Before foreach (range(0, 10) as $n) { $f[$n] = function () use ($n) { return $n; }; } // After foreach (range(0, 10) as $n) { $f[$n] = fn() => $n; }キャプチャが不要になったことは、現状の文法では
fnと組み合せてuseを書くことはできないことの裏返しでもあり、現状の制限事項です。$thisも暗黙的にキャプチャされますが、static fn()のようにstaticを前置すると束縛しないようになります。PHP 7.4のアロー関数の制限
PHP RFC: Arrow Functions 2.0の提案にはFuture Scopeとして将来の機能拡張可能性(必ずしもそのようにしたいという希望ではない)がまとめられてます。逆に言うと、これらの機能はPHP 7.4時点では使えない制限事項です。
複数の文
現状では
fn($n) => $n * 2のような式を書くことはできますが、fn() => { $m = 2; return $n * $m; }のように複数の文を書くことはできません。つまり、いままでfunction () { ... }式で書かれてきたものを直接置き換えることはできません。RFCでは次のような拡張可能性があることを示唆してます。
fn(params) => { stmt1; stmt2; return expr; } // 可能なら単にこうする fn(params) { stmt1; stmt2; return expr; }使用が自明ではない変数はキャプチャされない
RFCでは以下のようなコードで
$xはキャプチャしないと言及されています。$x = 42; $y = 'x'; $fn = fn() => $$y;また、以下のようなコードでも同様に
functionからfnに置き換えたときに動作しなくなります。$n = 10; $f1 = function () use ($n) { return $n; }; $f2 = function () use ($n) { return eval('return $n;'); }; $f3 = fn() => $n; $f4 = fn() => eval('return $n;'); var_dump($f1()); var_dump($f2()); var_dump($f3()); var_dump($f4()); // Notice: Undefined variable: n変数キャプチャ方法の切り替えはできない
function式ではfunction () use (&$var)と書くことで外部の変数のリファレンスを捕捉できました。アロー関数では現状リファレンスのキャプチャをサポートしないということは、今回の記事で取り上げた方法での再帰やカウンター関数は実装できないということです。RFCでは以下のようなパターンでの構文拡張可能性があると記述されてます。
$a = 1; $fn = fn() use(&) { $a++; }; $fn(); var_dump($a); // int(2)↑のパターンは比較的簡潔ですが、二つ以上の変数をキャプチャしたとき両方リファレンスでキャプチャしてしまうことになりそうです。
$a = 1; $b = 2; $fn = fn() use(&$a) { $a += $b; }; $fn(); var_dump($a); // int(3)↑ のパターンはリファレンスで捕捉したい
use(&$a)のみを記述しますが、$bは省略されているので、$bはキャプチャするのかしないのか混乱させかねない問題があります。明示的に書くか、多少の混乱の恐れがあっても省略を維持するかのトレードオフがあります。関数/メソッド定義文のアロー記法
今回導入されたアロー関数は従来の無名関数式の代替となる式であり、関数やメソッドの定義文は対象外です。
しかし、関数やメソッドの実装が単一の式(式文)のみからなる場合はアロー記法で定義できるようにしても良かろうというアイディアもRFCに記載されてます。
class Test { private $foo; private $bar; fn getFoo() => $this->foo; fn getBar() => $this->bar; }将来のPHPバージョン(おそらく8系)でもし複数文記法と同時に受理されてしまうと、ソースコード中の
functionが全てfnに置換されたりするのか? うーむ…再帰が(素直に)できない
これまでに説明した通り、常識的な手法でPHPの無名関数を再帰呼び出しするには変数リファレンスのキャプチャが必須だからです。その制約を乗り越える方法はいちおうあって、不動点コンビネータによる無名再帰です。Wikipediaを読んでぴんとこなければ、がんばって理解する必要は特にないです。
<?php $fix = fn($f)=>(fn($x)=>$f(fn(...$as)=>($x($x)(...$as))))(fn($x)=>$f(fn(...$as)=>($x($x)(...$as)))); $fib = $fix(fn($fib) => fn($x) => ($x < 2) ? 1 : $fib($x - 1) + $fib($x -2)); var_dump($fib(6)); // 13これは変なコーディング用のテクニックなので、実用的に真似する必要はまったくないです。だいたい再帰呼び出し以外のアルゴリズムで書いた方がパフォーマンスも出るし…
最後に
この記事の内容は @perpouh さんのスターリンソートwithPHPに
アロー関数でやろうと思ってたと書いてあって、「まじか、それってめっちゃ難易度が高いのでは……?」と感じたのがきっかけです。実際にやってみると、難易度超激難ってほどではないものの、やっぱり「ちょっとついでにアロー関数で書いてみっか」って気分でやれるほど気楽でもなかったです。結局やったんですけど。PHP縛りゲー大好きなので、次から何か適当な処理を書いてみたくなったら次もアロー関数縛りでやってみたいです。ありがとうございます。おまけ: 式の中に文を書く方法
これまでさんざん「式と文は繋げられない」とか「文の中には式しか書けない箇所がある」とか言ってきたのですが、実際には従来の
function式であれば複数の文が記述できるので、その制約を乗り越えることができます。つまり、引数の中に
foreachを書いたりifの条件式を書くところの中でクラスを定義したり、文字列リテラルの中にめっちゃ複雑な式を書いたり、なんでもやりたい放題です。楽しいですよ。脚注
- 投稿日:2019-08-04T04:43:59+09:00
PHP For Beginnersチュートリアル その7 基本的なコンタクトフォームの実装
このシリーズの目的
体系的なwebコーディングの訓練ができるようになるために、PHPの初学のきっかけかつ、PHPでログインフォームやフォームを実装することができるようになるために
上記のチュートリアルを進めているのでその備忘録。
内容
今回のチュートリアル
PHP Tutorial: Create Contact Form & Send an Email With Attachment Using PHPMailer
このチュートリアルでやること
・簡単なコンタクトフォームの作り方を覚える
・フォームに書かれた内容と添付ファイルをPHPを使ってEメールで送信する成果物
index.php<?php $msg = ""; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; if (isset($_POST['submit'])) { mb_language("japanese"); mb_internal_encoding("UTF-8"); require 'vendor/autoload.php'; require 'Mairtrap-config.php'; // メール送信を行うための関数 function sendemail($to,$from,$fromName,$body,$attachment) { $mail = new PHPMailer(); // Server $mail->SMTPDebug = 0; //本番では0とかにする。 $mail->isSMTP(); $mail->SMTPAuth = true; $mail->Host = MAIL_HOST; $mail->Username = MAIL_USERNAME; $mail->Password = MAIL_PASSWORD; $mail->SMTPSecure = MAIL_ENCRPT; $mail->Port = SMTP_PORT; // Recipients $mail->setFrom($from,$fromName); $mail->addAddress($to); $mail->addAttachment($attachment); // Content $mail->FromName = mb_encode_mimeheader("$fromName", "ISO-2022-JP", "UTF-8"); $mail->Subject = mb_encode_mimeheader("Contact Form test", "ISO-2022-JP", "UTF-8"); $mail->Body = mb_convert_encoding("$body","JIS","UTF-8"); $mail->CharSet = 'ISO-2022-JP'; $mail->Encoding = "7bit"; // Select HTML or NOT $mail->isHTML(false); return $mail->send(); } //ここまで $name= $_POST['username']; $email= $_POST['email']; $body = $_POST['body']; $file = "attachment/". basename($_FILES['attachment']['name']); //デバック用のコード、本番時はオフ // echo "<pre>"; // print_r($_FILES); if (move_uploaded_file($_FILES['attachment']['tmp_name'],$file)) { if(sendemail("test@to.example.com",$email,$name,$body,$file)) $msg = "Email sent"; else $msg = "Error"; } else $msg = "Please check your attachment!"; } ?> <!DOCTYPE html> <html> <head> <title>PHP contactform tutorial</title> <link rel="stylesheet" href="css/sample1.css"> </head> <body> <div class="form"> <img src="img/icon.png" alt="アイコン"> <form action="index.php" method="post" enctype="multipart/form-data"> <input type="text" name="username" placeholder="Name" required><br> <input type="text" name="email" placeholder="Email" required><br> <textarea name="body" placeholder="what`s up?" required></textarea><br> <input type="file" name="attachment" required><br> <input type="submit" name="submit" value="Send Email"><br> </form> </div> <br><br> <?php echo $msg; ?> </body> </html>sample1.cssinput,textarea { width:250px; height:27px; margin-bottom:10px; } textarea { height: 100px; resize:vertical; } body { text-align: center; margin-top: 150px; } img{ height: 100px; width:200px; }添付ファイルを送れるようなフォームを作る手順
1.formタグにおいてmethod属性をPOSTにする
2.enctype属性を追加し内容をmutipart/form-dataにする。
3.type属性がfileのinputタグを書く
2,3が肝。
今回のコードの注釈
$_FILESについて$file = "attachment/". basename($_FILES['attachment']['name']); /*$_FILESはHTTPのPOSTメソッドでアップロードされたファイルの値を取得するファイルアップロード変数。平たく言うと、クライアントからサーバーへファイルをアップロードするために必要な変数であり、送信されてきたデータのうち、type属性がfileであるinputタグの部分のデータが連想配列でこの変数に格納されることになる。 格納されるデータは以下の通りであり、必ず最初にinputタグで指定したname属性の値を引数に設定していることがわかる。 $_FILES['inputで指定したname']['name'] ファイル名 $_FILES['inputで指定したname']['type'] ファイルのMIMEタイプ $_FILES['inputで指定したname']['tmp_name'] サーバー上で一時的に保存されるテンポラリファイル名 $_FILES['inputで指定したname']['error'] アップロード時のエラーコード $_FILES['inputで指定したname']['size'] ファイルサイズ(バイト単位) よって今回の場合、<input type="file" name="attachment" required>と指定しているので、例えばbg.jpgというファイルをアップロードすると各データとキーと値は以下のようになる。 Array ( [attachment] => Array ( [name] => bg.png [type] => image/png [tmp_name] => C:\xampp\tmp\phpEE16.tmp [error] => 0 [size] => 20918 ) ) また、basenameはファイルあるいはディレクトリへのパスを含む文字列を受け取って、最後にある名前の部分を返す関数で要はファイル名やディレクト名を取得するための関数である。 つまり、$file = "attachment/". basename($_FILES['attachment']['name']);というのはattachmentフォルダにある$_FILESでアップロードされてきたファイルの名前ということになり、次で触れるmove_upload_file関数を使うために必要なパスを書いていると思えばいい。*/ ?>move_upload_fileについてif (move_uploaded_file($_FILES['attachment']['tmp_name'],$file)) { if(sendemail("test@to.example.com",$email,$name,$body,$file)) $msg = "Email sent"; else $msg = "Error"; } else $msg = "Please check your attachment!"; } /*move_upload_fileはクライアントからのリクエストでアップロードされたファイルの保存場所を変更する際に使用する関数である。 そもそもファイルアップロードの仕組みから説明すると、アップロードされたファイルは任意の一時フォルダに保存される。先に出したキーと値でいうと [tmp_name] => C:\xampp\tmp\phpEE16.tmp この部分を見ればいい、tmpというフォルダに仮に保存されているのがわかる。 仮に保存されているということは、一定時間で消えるということなのでそうならないために。これを別の専用のフォルダに移すための関数がmove_upload_file関数ということである。 内容は簡単、第1引数に移動前のパス、第2引数に移動後のパスを指定してやればいい。 今回の場合は$_FILESにおいてサーバーに一時的に保存されるファイル名の引数の設定の仕方は上に書いてあるように、$_FILES['inputで指定したname']['tmp_name']であるからそれを第1引数に設定し、第2引数には先程設定した$fileを指定する。 つまり、先程の$fileはアップロードされたファイルの保存場所を指定したということが逆説的にわかる。 あとは簡単な話で、functionでメール送信の為の一連の処理はまとめてあるので、関数作成の際に設定した引数を入れてif文で条件分岐してエラーメッセージ等を適宜出すようにすればいい。 ちなみに今回指定した引数は左から順に 送り先のアドレス、inputタグのname属性がemail箇所から送信されてきた値(フォームに入力されたメールアドレス、通常の場合は補足の項目にあるように送り主のアドレス)、inputタグのname属性がusernameの箇所から送信されてきた値(フォームに入力された名前、通常の場合は送り主の名前)、メール本文、添付ファイル(ない場合は書かなくていい)となっている。 */補足
1.複数のメールアドレスにメールを送信するためには?
成果物の下記の部分においてsendemailで別のアドレスと引数を指定してやればいい。
if (move_uploaded_file($_FILES['attachment']['tmp_name'],$file)) { if(sendemail("test@to.example.com",$email,$name,$body,$file)) $msg = "Email sent"; //中略 //これを例えば以下のようにする if (move_uploaded_file($_FILES['attachment']['tmp_name'],$file)) { if(sendemail("test@to.example.com",$email,$name,$body,$file)) $msg = "Email sent"; sendemail("test@example.com","post@mail.com","website","hey!"); //中略こうすると、"test@to.example.com"と"test@example.com"へ送られるメールの内容を変えつつ同時に送信することができる。
今回のようなメールフォームには必要ないが覚えておこう。2.日本語の部分が文字化けする(件名、送信者名、本文等)
成果物のように以下のコードを必ず記載する。
チュートリアルは英語の教材でかつ、英語圏の人たちを対象に想定して作られているので特に言及されていないが、チュートリアルのまま日本語を本文等に使うと必ず文字化けするので忘れないこと。設定するコードmb_language("japanese"); mb_internal_encoding("UTF-8"); $mail->FromName = mb_encode_mimeheader("$fromName", "ISO-2022-JP", "UTF-8"); $mail->Subject = mb_encode_mimeheader("Contact Form test", "ISO-2022-JP", "UTF-8"); $mail->Body = mb_convert_encoding("$body","JIS","UTF-8"); $mail->CharSet = 'ISO-2022-JP'; $mail->Encoding = "7bit";参考
PHPでmove_uploaded_file関数を使う方法【初心者向け】
PHPでファイル名や拡張子,ディレクトリ名を取得する方法
PHP $_FILES(ファイル変数)のすべて!【初心者向け基本】
PHPで日本語メールを送信する
- 投稿日:2019-08-04T00:39:38+09:00
php mysql でsql injectionに対応したhashログイン処理を実装した例
php mysql でsql injectionに対応したログイン処理を実装した例
phpとmysqlでセッションログイン処理を実装するときにとかそういうのを作るのが面倒だったので、exampleを残します。。
依存パッケージのinstall
composer require josegonzalez/dotenvログイン処理をするとき
// sign up $userInfo = $login->signup("username","xxx@example.com", "12345678"); //login $userInfo = $login->login("xxx@example.com", "12345678"); //check $userInfo = $login->check(); if(!$userInfo) //ログインできていな //logout $userInfo = $login->logout();ログイン処理のクラス
ログイン処理のクラスファイルを作成
login.php<?php /* * phpとmysqlでセッションログイン処理を実装するときにとかそういうのを作るのが面倒だったので、exampleを残します。。 * made : ryosuke ando <ryo@ando.link> * */ require_once __DIR__ . "/vendor/autoload.php"; use josegonzalez\Dotenv\Loader as Dotenv; class login { private $dbh; public function __construct() { $appDir = __DIR__; Dotenv::load([ 'filepath' => $appDir . '/.env', 'toEnv' => true ]); $mysqlUser = $_ENV['MYSQL_USER']; $mysqlPass = $_ENV['MYSQL_PASS']; $mysqlHost = $_ENV['MYSQL_HOST']; $mysqlDbName = $_ENV['MYSQL_DBNAME']; $dsn = sprintf("mysql:host=%s;dbname=%s;charset=UTF8", $mysqlHost, $mysqlDbName); try { $this->dbh = new PDO($dsn, $mysqlUser, $mysqlPass); $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e) { echo "error: " . $e->getMessage() . "\n"; exit(1); } } public function login($email, $password) { /* * ログイン失敗なら、エラーを返します * 成功なら、 */ $sql = "SELECT * FROM users WHERE mail = ?"; $sth = $this->dbh->prepare($sql); $sth->bindParam(1, $email, PDO::PARAM_STR); $sth->execute(); $userInfo = $sth->fetch(PDO::FETCH_ASSOC); if (!$userInfo) //ユーザー名が間違えている return null; if (!password_verify($password, $userInfo['password'])) //パスワードが間違えている return null; session_start(); $_SESSION["user_info"] = $userInfo; return $userInfo; } public function check() { /* * ログインされていれば、ログイン情報を連想配列で返します。 * ログインに失敗しているならば、nullを返します。 */ session_start(); //ログインされているかチェックし、ログイン if (!isset($_SESSION["user_info"])) return null; return $_SESSION["user_info"]; } public function logout() { session_start(); $_SESSION["user_info"] = null; } public function signup($name, $email, $password) { $sql = "INSERT INTO users (`username`,`mail`,`password`) VALUES (?,?,?)"; $sth = $this->dbh->prepare($sql); // echo password_hash($password, PASSWORD_BCRYPT); $sth->bindParam(1, $name, PDO::PARAM_STR); $sth->bindParam(2, $email, PDO::PARAM_STR); $sth->bindParam(3, password_hash($password, PASSWORD_BCRYPT), PDO::PARAM_STR); $sth->execute(); $userInfo = $sth->fetch(PDO::FETCH_ASSOC); session_start(); $_SESSION["user_info"] = $userInfo; return $userInfo; } }






