20200220のPHPに関する記事は14件です。

PHPのモジュールシステムを整理してみた ~require_onceからcomposerオートローダーまで~

何かとPHPを使うことが増えたので、PHPのモジュールシステムについて整理してみました。ここで言うモジュールシステムとは、他ファイルからクラス、関数、定数などを取り込める機能のことです。

TL;DR

ぶっちゃけcomposerオートローダーがを使えばいいと思います。
極論ですがcomposerがないPHPはレガシーPHPです。

PHPモジュールシステム解説

この記事で使うコードは、全てgithubに公開されており、実際に動かすことができます。サンプルコードを動かすためには、ローカル環境にPHP(7系統)、composerがインストールされている必要があります。

サンプルコード
https://github.com/kaidouji85/php-module

1 require_onceをシンプルに使う

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/01-require

1 require_once.png

main.phpの中で、greeting.phpに定義されたmessage関数を呼び出しています。これだけシンプルなら問題ありませんが、ファイルが増えてくると名前が衝突する危険があります。それがどういう意味なのかは、次のセクションで説明します。

2 名前衝突でエラーになる

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/02-name-conflict

2 名前衝突でエラー (1).png

main.phpでgreeting.php、secret.phpを読み込んでいますが、両方のファイルで同じ名前の関数が定義されています。この場合、message関数が2個定義されたことになりますが、PHPでは関数、クラス、定数などを二重定義するとエラーになります。したがって、このコードは動きません。仮にPHPで二重定義がOKだったとしても、secret-word.php、greeting.phpのどちらのmessageを呼び出したのか区別することが出来ません。

開発規模が大きくなると、違うファイルで同じ名前のものが沢山出てくると思います。全モジュールで被らない名前を考えるのは、規模が大きくなるほど現実的でないです。この問題を解決するために、名前空間という機能が用意されています。

3 名前空間で二重定義を回避する

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/03-name-space

3 名前空間で二重定義を回避 .png

main.php
<?php
require_once("greeting.php");
require_once("secret-word.php");

print(\SecretWord\message());
secret-word.php
<?php
namespace SecretWord;

function message() {
    return "secret word!!";
}

名前空間とは、greeting.php、secret-word.phpを仮想的なフォルダのようにものに入れて名前が被るのを防ぐ機能です。main.phpを見ると\SecretWord\message()としていますが、これは名前空間SecretWordに含まれるmessage関数を呼び出すという意味です。名前空間SecretWordにはmessageという関数は1つしか含まれていないので、二重定義エラーにはなりません。しかし、名前空間内に同じ名前が存在した場合には、二重定義エラーになります。

名前空間はParent\child\grandchildというように、階層構造を持つことができます。ただ、名前空間の階層が深くなると、その分コードがが冗長になります。これを解決するために、PHPにはuseという文法があります。

4 useで名前空間配下のものをシンプルに呼び出す

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/04-use

4 useで名前空間配下のものをシンプルに呼び出す (3).png

main.php
<?php
require_once("greeting.php");
require_once("secret-word.php");

use function \SecretWord\message;

print(message());

main.phpを見るとmessage()と名前空間なしで関数を呼び出しています。ただ、名前空間指定なしで、どのようにしてgreeting.php、secret.phpの区別をしているのでしょうか。答えはmain.php 5行目のuse function \SecretWord\message;です。useであらかじめ利用するものを名前空間込みで書いておけば、以降は名前空間なしで呼ぶことができます。

これで全ての問題が解決したように思えますが、そんなことはありません。開発規模が大きくなると名前空間の階層が深くなることは先に述べた通りですが、名前空間の管理方法を決めておかないと意図しない二重定義が発生する恐れがあります。また、useを使えばコードはスッキリしますが、毎回require_once、useを書かなければいけません。この問題を解決するために、PHPではcomposerオートロードという仕組みを導入しました。

5 composerオートロード

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/05-composer

5 composerオートロード.png

composer.json
{
    "name": "phpModule",
    "authors": [
        {
            "name": "y.takeuchi",
            "email": "kaidouji85@gmail.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "App\\" : "app/"
        }
    },
    "require": {}
}
main.php
<?php
require_once("vendor/autoload.php");
use App\SecretWord;

print(SecretWord::message());
app/SecretWord.php
<?php
namespace App;

class SecretWord
{
    public static function message() {
        return "secret word!!";
    }
}

autoload.php

構成が一気に変わりました。最初に目につくのはvendor/autoload.php(以下autoload.php)でしょうか。autload.phpはプログラマーが書くのではなく、以下コマンドで自動生成するものです。

composer dump-autoload
# vendor/autoload.phpが自動生成される
# ほかにも必要なファイルが自動生成される

autload.phpはプロジェクトで使うphpファイルを全てrequire_onceするためのものです。プログラムを起動したら一番初めに実行されるファイルいわゆるエントリポイント内で、require_once("vendor/autoload.php");と書きます。するとautoload.phpが必要なphpファイルを全て読み込んでくれるので、エントリポイント以外でrequire_onceを書く必要がなくなります。エントリポイント以外ではuseを使っていきます。autoload.phpはcomposerで自動生成するべきものなので、git管理に含めてはいけません。

PSR-4

autoload.phpが必要なPHPファイルの読み込みをやってくれるのは分かりましたが、新しいPHPファイルを追加した場合はどうなるのでしょうか。再度composer dump-autoloadを実行すればよい気もしますが、それだとファイル構成に変化があるたびにコマンド再実行をしなければなりません。はっきり言って、面倒くさいです。この問題を解決するために、composerはファイルを配置する場所、名前空間、クラス名が一定のルールに沿っていれば、ファイル構成の変更を自動的に反映してくれる仕組みを備えています。このルールはPSR-4と呼ばれています。

PSR-4
https://www.php-fig.org/psr/psr-4/

PSR-4 概要図 (2).png

細かいところは省きますが、PSR-4では名前空間とファイルパスの一部が完全一致していることが分かります。また、名前空間とソースコードのルートディレクトリの関連付けが。composer.jsonに定義されています。さらにファイル名から.phpを除いたもの、クラス名も完全一致しています。これらの規約を満たしていれば、autoload.phpに自動的に取り込まれます。余談ですが、PSR-4に準拠していればVS CodeなどのIDEでのコード補完が抜群に良くなりました。

6 オートロードで多層階層をスマートに

対応するサンプルコード
https://github.com/kaidouji85/php-module/tree/master/06-multi-layer

6 オートロードで多層階層をスマートに.png

6のクラス図 (2).png

最後に呼び出し階層が複数に渡る例を紹介します。ファイル構成が複雑なので、クラス図を用意しました。Greetingをインタフェース化しているのにSecretWordでそれを呼び出していないのが微妙ですが、そこはご容赦ください。全体構成は複雑ですが、単体ソースコードのレベルではモジュール読込がとても分かりやすくなっています。エントリポイントはrequire_once("vendor/autoload.php");が書いてありますが、それ以外のモジュール読込はすべてuse XXXXXXXXXで統一されています。一般的にエントリポイントでは数行で済ませることが多いので、実質的にモジュール読込はuse xxxxxxxxxだけで出来るようになりました。

結論

よほどの理由がない限り、composer オートローダを使うのがいいと思います。以下がその理由です。

  • モジュール読込がuse XXXXX形式に統一できる
  • 基本はPSR-4に準拠しているので名前空間の事故が起こりにくい
  • 副次効果としてIDE補完が効きやすくなる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 文字列を時刻に変換

使う機会があったのでメモしておきます。

指定の時間からある日時を足し引きしたい場合

qiita.php
$str = "10";
$int = intval($str);
$datetime = "2020/02/20 13:00:00";

//10秒前 2020-02-20 12:59:50
echo date("Y-m-d H:i:s",strtotime($datetime . "-" . $str . "second"));
echo "<br>";
//10分後 2020-02-20 13:10:00
echo date("Y-m-d H:i:s",strtotime($datetime . "+" . $int . "minute"));
echo "<br>";
//10時間前 2020-02-20 03:00:00
echo date("Y-m-d H:i:s",strtotime($datetime . "-" . $str . "hour"));
echo "<br>";
//10日後 2020-03-01 13:00:00
echo date("Y-m-d H:i:s",strtotime($datetime . "+" . $int . "day"));
echo "<br>";
//10週間前 2019-12-12 13:00:00
echo date("Y-m-d H:i:s",strtotime($datetime . "-" . $str . "week"));
echo "<br>";
//10ヶ月後 2020-12-20 13:00:00
echo date("Y-m-d H:i:s",strtotime($datetime . "+" . $int . "month"));
echo "<br>";
//10年前 2010-02-20 13:00:00
echo date("Y-m-d H:i:s",strtotime($datetime . "-" . $str . "year"));
echo "<br>";

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

laravelのサービスコンテナの挙動に関するメモ

初めに

この記事は、laravelのサービスコンテナの挙動について説明します。
コードはLaravel 6.2(PHP 7.2.24)で動作を確認しています。

サービスコンテナに値の生成方法を登録する

サービスコンテナの主要な役割の一つが、ある値の生成方法を登録することです。
bind()関数を用いてキーとなる文字列とその生成方法をサービスコンテナに登録することができます。

>>> app()->bind('rand', function() {return rand();})
=> null

make()関数を用いることで、登録した生成方法を用いて値を生成できます。

>>> app()->make('rand')
=> 2114203198
>>> app()->make('rand')
=> 576409439

makeには第二引数としてパラメタを渡すことができます。
パラメタはarrayである必要があります。
渡したパラメタはbindの第二引数の第二引数として渡されます。
(ちなみに第一引数$appにはサービスコンテナ自身が渡されます。)

>>> app()->bind('p', function($app, $param) {dump($param);return 1;})
[]
=> null
>>> app()->make('p', ['hoge' => 'fuga'])
array:1 [
  "hoge" => "fuga"
]
=> 1

makeWithという関数もありますが、これはmakeと同じ動作をします。 1

>>> app()->makeWith('rand')
=> 449067795
>>> app()->makeWith('p', ['hoge' => 'fuga'])
array:1 [
  "hoge" => "fuga"
]
=> 1

bindの第二引数に文字列を指定することもできます 2

>>> app()->bind('another_rand', 'rand')

これは以下の書き方とほぼ同じ挙動をします

app()->bind('another_rand', function($app, $param){$app->make('rand', $param);})

値を何度も生成したくない場合はsingleton関数を使うことができます。
これを使うと値は一度だけ生成されてキャッシュされ、二回目以降はキャッシュされた値が返されます。

>>> app()->singleton('singleton_rand', function() {return rand();})
=> null
>>> app()->make('singleton_rand')
=> 259982949
>>> app()->make('singleton_rand')
=> 259982949
>>> app()->make('singleton_rand')
=> 259982949

サービスコンテナを使ったインスタンス作成

サービスコンテナの主要な役割のもう一つが、サービスコンテナを用いてインスタンスを作成する際にクラスの依存関係を自動的に解決することです。
そのあたりの挙動についてみていきます。

makeに登録されていない文字列が与えられた場合、サービスコンテナはそれをクラス名とみなしてそれをnewしようと試みます。

>>> class Hoge{function __construct(){echo 'Hoge constructed';}}
>>> app()->make(Hoge::class)
Hoge constructed
=> Hoge {#2999}

この際newしようとしたクラスのコンストラクタに引数が必要だった場合、サービスコンテナは以下のルールにのっとりその引数の解決を試みます。(上の条件が優先)3
1. makeのパラメタで引数名が指定されていればその値を使う
2. 引数にクラスが定義されていればそのクラスをサービスコンテナを用いて生成する。
3. 定義されていなければその引数名をキーとしてサービスコンテナを用いてその値を生成する(この場合コンテキストによる結合のみが参照される)

順番に詳しく見ていきます。

もっとも直接的な指定としてmakeのパラメタを通じてコンストラクタの引数に与える値を指定できます

>>> class Hoge2{function __construct($i){echo "i=$i";}}
>>> app()->make(Hoge2::class, ['i' => 1]);
i=1
=> Hoge2 {#3012}

makeのパラメタで値が指定されていない場合、コンストラクタの引数のクラスを見てそのクラスをサービスコンテナを用いて生成します。

>>> class Hoge3{}
>>> app()->bind(Hoge3::class, function() {echo 'Hoge3 is constructed by service container!'; return new Hoge3();})
>>> class Hoge4{function __construct(Hoge3 $hoge3){}}
>>> app()->make(Hoge4::class)
Hoge3 is constructed by service container!
=> Hoge4 {#3011}

もし、クラスによって注入するクラスを分けたい場合、コンテクストによる結合を行うことができます。
これはwhen, needs, give関数を使って以下のように書けます

>>> class Hoge5{}
>>> class ExtendedHoge5 extends Hoge5{}
>>> class Fuga{function __construct(Hoge5 $hoge){echo get_class($hoge) . ' given';}}
>>> class Fuga2{function __construct(Hoge5 $hoge){echo get_class($hoge) . ' given';}}
>>> app()->when(Fuga2::class)->needs(Hoge5::class)->give(function($app) {return $app->make(ExtendedHoge5::class);})
>>> app()->make(Fuga::class)
Hoge5 given
=> Fuga {#3048}
>>> app()->make(Fuga2::class)
ExtendedHoge5 given
=> Fuga2 {#3015}

このようにコンテキストによる結合を指定したFuga2だけHoge5ではなくExtendedHoge5が注入されています。

クラス名が指定されていない場合変数名をキーにして値のサービスコンテナによる生成が試みられます。
この場合コンテキストによる結合のみが参照されます。

>>> class Hoge6{function __construct($i){echo $i;}}
>>> app()->make(Hoge6::class)
Illuminate/Contracts/Container/BindingResolutionException with message 'Unresolvable dependency resolving [Parameter #0 [ <required> $i ]] in class Hoge6'
>>> app()->bind('$i', function(){return 1;})
=> null
>>> app()->make(Hoge6::class)
Illuminate/Contracts/Container/BindingResolutionException with message 'Unresolvable dependency resolving [Parameter #0 [ <required> $i ]] in class Hoge6'
>>> app()->when(Hoge6::class)->needs('$i')->give(1);
=> null
>>> app()->make(Hoge6::class)
1
=> Hoge6 {#3026}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでSQS

前置き

SQS使った事なかったのでPHPから連携させて使ってみた。

環境

Amazon Linux 2
PHP 7.2.27 (たぶん7系であればOK)
AWS SDK 3.133 (たぶん3系であればOK)

Amazon Linuxでは脳死でyumでphpを入れると5系になってしまう。
この状態でcomposerでAWS SDKを入れようとすると2.8になる。
するとAWS SDKを使ったPHPのプログラム実行時に、credentialがなんちゃかってエラーが出たりして全然出来ないので、PHPを7にあげて…とかしてハマった。

SQSって

AWSのキューのサービス。
FIFO(ファースト・インファーストアウト)って基本情報の勉強とかしてると出てくるやつ。
つまり入れた順番で情報を取り出せる。

Aのシステムでキューへメッセージ(データ)を格納する。
Bのシステムでキューを覗きに行って、メッセージがあったら取ってくる。
という利用イメージ。

料金は
Amazon SQS の料金はどのように計算されますか?
によると、

毎月最初の 100 万件のリクエストまでは無料です。

無料利用枠を超えた後の 100 万件のリクエストあたりの料金 (月単位)
標準キュー 0.40USD (0.0000004USD/リクエスト)
FIFO キュー 0.50USD (0.0000005USD/リクエスト)
との事。

1秒に1回のリクエストだと
1*60*60*24 = 86,400リクエスト
1ヶ月を30日とすると
86400*30 = 2,592,000リクエスト

なので3秒に1回くらいのペースなら無料枠を超えないのかなというイメージ。
たぶん実際にはキューに入れる側のリクエスト、取得する側のリクエスト、さらに取得済みのメッセージを消すリクエスト、とかも発生するはずなので、工夫しないと簡単にリクエスト数が跳ね上がってしまう。

とはいえ標準キューで無料枠を100万リクエストオーバーしたとしても0.4USDって事は1ドル100円として40円なので大した事はない。

ところで料金表のところで標準キューとFIFOキューって表記がある。
落ち着け、そもそもキューってFIFOじゃないのって話なのだが、SQSの場合は標準キューだと必ずしもインした順番どおりにアウトされない事があるらしい。(キューとはなんぞやって頭を悩ませる案件)

そういうSQS特有の概念も含めて、だいたいこのへんを眺めればざっくり概要はつかめる気がする。
Amazon SQSを使う前に知っておきたい基本的なこと

ちなみにメッセージがキューに入っているかなぁって確認する側に関して。
たぶんSQSにロングポーリングを設定するのが良いと思われる。
Amazon SQS メッセージキューのロングポーリングの有効化

要するに、メッセージ確認する側は何もしなくても、SQSにメッセージ確認のリクエストを投げると、
・メッセージがあれば即レスポンス
・メッセージが無い場合は最大20秒までメッセージが来るのを待ってレスポンス
(もちろん待ってる途中でメッセージが入ればその時点でレスポンス)
という動きをSQS側がしてくれるようになる。
(リクエスト側で気にするのはリクエスト自体のタイムアウト設定くらい…?)

リアルタイム性を求める場合は、ずっとメッセージが来なくても20秒に1回リクエストが発生するだけになるのでお財布にも優しい。
(そもそもそこまでリアルタイムにこだわらなければ取得側のリクエスト周期自体を伸ばせばいい話なのだけど。)

環境構築

日が経ってるのでうろ覚えの記憶を頼りにhistoryを見つつなので間違ってたらすまんとだけ

とりま

sudo su -

で管理ユーザになる

PHP 7.2をインストール
ハマったのでとりあえず片っ端からそれらしいPHPモジュールをいれてみた(爆
もしかすると不必要なやつもあるかもしれないが、いったんhistoryから拾ってきたやつを書き連ねる

yum install -y php72 php72-php php72-php-fpm php72-php-mbstring php72-php-devel php72-php-gd

AWS SDKをインストールするため、ここらへんを参考にcomposerをインストール
めんどくさかったので作業ディレクトリ作ってそこでやった

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

AWS SDKをインストール
Installing the AWS SDK for PHP Version 3

composer require aws/aws-sdk-php

ハマってたときにcomposerのupdateでこけたりしてムキーってなってこのへんの対応をしたけど、普通にupdateいらなかったかもしれないしそもそもmicro以外のインスタンスタイプなら起きないかもなので参考程度に貼っておく
EC2のmicro instanceでcomposer updateがコケる場合のメモ

PHPからメッセージの送信

AWS SDK使ったりAWS CLIでコマンド叩く人にはお馴染みのあれだが
PHPからのリクエスト時に当然認証は入るので、事前に「プログラムによるアクセス」を許可したユーザをIAMで用意しておかないといけない。
例のアクセスキーとシークレットアクセスキーを用意するあれ

PHPのソースはここらへんを参考に

AWS SDK for PHP から AWS SQSを使ってみた

AWS SDK for PHPでAmazon SQSを使う

標準キューとFIFOキューでは若干違うらしい。
今回はFIFOキューでやった。

メッセージの送信処理

sendMessage.php
<?php
require_once 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

$queueUrl = "https://sqs.ap-northeast-1.amazonaws.com/xxxxxxxx/yyyyyyyy.fifo";

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);

    $fifo_params = [
        'QueueUrl' => $queueUrl,
        'MessageBody' => "xxxxxx", // ここにメッセージを設定
        'MessageGroupId' => 'test', // よく分かってない
        'MessageDeduplicationId' => time(), // よく分かってない
    ];

    $client->sendMessage($fifo_params);
} catch(AwsException $e){
    var_dump($e->getMessage());
}

これを実行後にAWSコンソールからSQSを確認するとメッセージが入っている。

メッセージの受信処理

getMessage.php
<?php
require_once 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

$queueUrl = "https://sqs.ap-northeast-1.amazonaws.com/xxxxxxxx/yyyyyyyy.fifo";

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);
// ここまではsendMessageと一緒

// 継続的にメッセージを受け取りに行く場合にはここでループの記述
// while(true){

    // メッセージ受け取り
    $result = $client->receiveMessage(array(
        'QueueUrl'        => $queueUrl,
        'WaitTimeSeconds' => 5, // ロングポーリングする秒数(最大20秒)
        'MaxNumberOfMessages' => 10, // 同時に取ってくるメッセージ数
    ));

    // メッセージの受け取り処理をループにしていた場合はメッセージの中身が空ならここでcontinueさせる
    // if(!$result->search('Messages[]'))continue;

    // メッセージを1件づつ取得し、取得済みメッセージは都度削除リクエストを投げる
    foreach ($result->search('Messages[]') as $message) {
        $queueHandle = $message['ReceiptHandle'];
        echo $message['Body'];

        // 対象メッセージの削除
        $client->deleteMessage([
            'QueueUrl' => $queueUrl,
            'ReceiptHandle' => $queueHandle,
        ]);
    }
// メッセージの受け取りループをしていた場合はここまで
// }
} catch(AwsException $e){
    var_dump($e->getMessage());
}

結論

受信側でSQSをロングポーリングしつつ、メッセージ送信処理を実行したところ、受信側では体感1秒もかからないうちに受信出来た。
使える場面は十分ありそうな手応え。

おまけ

可視性タイムアウトについて

SQSに「test」という名前のキューを作成
「test」キューに対しメッセージ「AAA」を送信

$client->receiveMessageのタイミングで、「AAA」に対し、「ReceiptHandle」と呼ばれるセッションIDみたいなものが設定払い出される。(ReceiptHandleはメッセージに対し1:1。)
そしてReceiptHandleの有効期限中は、「AAA」は不可視、要は他からは見えない、いわば専有状態になる。
(たぶん、ReceiptHandleの有効期限=「可視性タイムアウト」)

メッセージを受信したプロセスは、そのReceiptHandleを使用すれば不可視になっているメッセージに対する操作を行える。(削除とか可視性タイムアウト時間の変更とかとか)

若干ハマったのが
可視性タイムアウトを0秒に設定するとメッセージが消せない()
受信側の処理が途中で落ちた場合に、再取得のため再度リクエストを投げてまだ不可視だったらめんどくさいなと思い、思い切って0秒にしてみたところ上記の状態になった。
(ReceiptHandleの有効期限が切れてるんだから当然デスヨネ)

なお再リクエスト時にまだ不可視の問題は、ReceiptHandleを使って対象メッセージの有効期限を0秒にすると即可視状態になるので、それで解決した。

可視性タイムアウト0秒は、おそらく
「メッセージ自体の有効期限がくるまでメッセージはずっと可視状態で残り続ける」
という要件じゃないと使えない設定だと思われる。
そんなものある…?最新のニュース一覧とかには使えるか…?

FIFOキューをPHPから作成

createQueue.php
<?php
require 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;

try{
    $client = new SqsClient([
        'profile' => 'default',
        'region' => 'ap-northeast-1',
        'version' => 'latest',
    ]);

    $client->createQueue([
        'QueueName' => 'omake.fifo',
        'Attributes' => ['FifoQueue' => 'true'],
    ]);


} catch(AwsException $e){
    var_dump($e->getMessage());
}

FIFOキューをAWS CLI ver2から作成

aws2 sqs create-queue --queue-name omake.fifo --attributes FifoQueue=true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WordPressのカスタム投稿タイプを作成する方法(ソースいじる編)

WordPress初心者の僕がカスタム投稿タイプについていろいろ調べました。
カスタム投稿タイプを作る方法は2つ。

  1. 自分で書く
  2. プラグインを使う

僕はコアのソースコードを書き換えるというのがそもそも好きではありません。
コアのバージョンが上がった時の対応とかがしんどいですし。
書き換えるのであれば継承して上から書くとかが理想です。
ただWordPressはそういった感じではないので、出来るのであればプラグインを使う方向で検討し、どうしても無理そうなら自分で書く。
自分で書く場合は、記述する範囲を最低限に留めることを心がけるのが良いのではないかなと思います。
今回は自分で書く方法についてのみ記述します。

WordPressのライフサイクル

フレームワークやシステムを紐解くにはライフサイクルを理解しておくのが重要だと個人的に思います。
WordPressもどういった順にファイルが呼ばれるかどういった順に関数が呼ばれるかがわかればいろいろ見えてくるでしょう。
というわけで、調べてみたんですが情報がない。
なのでソースを追うわけですが、「うわぁぁぁ、しんど、無理」ってなりました。
(ソース追ってて思ったのはWordPressは関数ベースなんだなと。歴史詰まってるな〜って感じです。)
なんで一旦、要所だけ抑えます。

今回抑えておくべきは、以下の2つかなと思いました。

  • functions.php
  • アクションフック

functions.php

自分で書く場合は「記述する範囲を最低限に留める」ことを心がけたいです。
カスタム投稿タイプを作るなど、なにかコアに自分で処理を追記したい場合はfunctions.phpに書くという情報が多いです。
ライフサイクルを細かく調べれていないですが、割とはじめの段階でこのファイルが呼ばれるのでしょう。
なので、出来る限りfunctions.php以外に記述を書かないとすることにします。
ワードプレスにはテーマが有るので、wp-content/themes/<使うテーマ>/functions.phpに追記していきましょう。

カスタム投稿タイプを作る

functions.phpに書けば良いことがわかったので、実際にカスタム投稿タイプを作る記述をしてみます。
カスタム投稿タイプを作るにはregister_post_type()という関数を使うみたいです。
この関数の情報は以下

説明

投稿タイプを作成または変更します。 この register_post_type() は必ず init アクションの中から呼び出してください。 init より前に呼び出すと動作しないため、新規作成または変更した投稿タイプも正常に動作しません。
init アクションについては後述)

使い方

object register_post_type( string $post_type [ , mixed $args = array() ] )

パラメータ

\$post_type
(文字列) (必須) 投稿タイプ(最大 20 文字、大文字や空白は禁止)。
初期値: なし

すでに予約されている投稿タイプ

  • post - 投稿
  • page - 固定ページ
  • attachment - 添付ファイル
  • revision - リビジョン
  • nav_menu_item - ナビゲーションメニュー

WordPress 関数の動作を妨害する文字列

  • action
  • order
  • theme

これらを\$post_typeに指定するのはNGです。
またここの文字列は一意にし衝突を避けたほうが良いので、接頭語をつけることが勧められています。

\$args
(配列) (オプション) 引数の配列。
初期値: なし

引数

第2引数\$argsについての詳細。一応全部書きます。

label
(文字列) (オプション) 投稿タイプを翻訳するための複数形の名前。
初期値: \$post_type

labels
(配列) (オプション) この投稿タイプのラベルの配列。デフォルトでは、投稿(post)の labels が階層なし投稿タイプに、固定ページ(page)の labels が階層あり投稿タイプに使用される。
初期値: 空の場合は、label の値が name へセットされ、name の値が singular_name へセットされる。

  • name - 投稿タイプの一般名、通常は複数形。省略すると \$post_type_object->label と同じ値になる。
  • singular_name - この投稿タイプのオブジェクト 1 個の名前(単数形)。デフォルトは 'name' の値。
  • menu_name - メニュー名のテキスト。メニュー項目の名前を決める文字列です。デフォルトは name の値。
  • name_admin_bar - 管理バーの「新規追加」ドロップダウンに入れる名前。デフォルトは、singular_name があればその値になり、無ければ name の値になる。
  • all_items - メニューの「すべての〜」に使うテキスト。デフォルトは name の値。
  • add_new - 「新規追加」のテキスト。デフォルトは階層あり/なしどちらの投稿タイプも "Add New"。この文字列を国際化対応にするには、gettext context を使って、投稿タイプをマッチさせてください。例: _x('Add New', 'product');
  • add_new_item - 「新規〜を追加」のテキスト。デフォルトは "Add New Post" または "Add New Page"。
  • edit_item - 「〜を編集」のテキスト。管理画面で、このラベルはカスタム投稿の編集パネルのメインヘッダーに表示されます。デフォルトは階層なしなら "Edit Post"、階層あり投稿タイプなら "Edit Page"。
  • new_item - 「新規〜」のテキスト。デフォルトは階層なしなら "New Post"、階層あり投稿タイプなら "New Page"。
  • view_item - 「〜を表示」のテキスト。デフォルトは "View Post" または "View Page"。
  • search_items - 「〜を検索」のテキスト。デフォルトは "Search Posts" または "Search Pages"。
  • not_found - 「〜が見つかりませんでした」のテキスト。デフォルトは "No posts found" または "No pages found"。
  • not_found_in_trash - 「ゴミ箱内に〜が見つかりませんでした」のテキスト。デフォルトは "No posts found in Trash" または "No pages found in Trash"。
  • parent_item_colon - 「親〜:」のテキスト。階層あり投稿タイプのときのみ使われる。デフォルトは "Parent Page"。

description
(文字列) (オプション) 投稿タイプの簡潔な説明。
初期値: ブランク

public
(真偽値) (オプション) 投稿タイプをパブリックにするかどうか。true の場合、管理画面とフロントエンド(ユーザー)の両方から利用可能。
初期値: false

  • false - 投稿タイプをパブリックにしない。他のところで明示的に用意しない限り、管理画面とフロントエンドのどちらからも使えない。
  • true - 投稿タイプをパブリックにする。フロントエンドと管理画面の両方から使えるように。

参考: exclude_from_search、publicly_queriable、show_ui および show_in_nav_menus のデフォルト値は public から継承されるが、デフォルトが設定された後はこの関係に依存せず各々が個別の機能を制御する。

exclude_from_search
(真偽値) (必須) この投稿タイプの投稿をフロントエンドの検索結果から除外するかどうか。
初期値: public 引数の反対の値

  • true - サイト/?s=検索対象文字列 の結果はこの投稿タイプの投稿を含まない。
  • false - サイト/?s=検索対象文字列 の結果はこの投稿タイプの投稿を含む。

参考:タクソノミーのタームが関連付けられた投稿のリストを表示したければ、exclude_from_search を必ず false にセットする(例:サイト/?タクソノミーのスラッグ=タームのスラッグ または サイト/タクソノミーのスラッグ/タームのスラッグ を開く)。true にセットすると、タクソノミーアーカイブのページ(例:taxonomy.php)で WordPress は投稿を見つけられず、ページ送りすると 404 エラーが発生する…

publicly_queryable
(真偽値) (オプション) フロントエンドで post_type クエリが実行可能かどうか。これは parse_request() の一部として実行される。
初期値: public 引数の値

参考:次のようなクエリが影響を受ける(リライトによっても)。

  • ?post_type={投稿タイプのキー}
  • ?{投稿タイプのキー}={投稿のスラッグ}
  • ?{投稿タイプの query_var}={投稿のスラッグ}

参考: FALSE にセットすると、カスタム投稿をプレビューも表示もできなくなる(404 エラーを返す)。

show_ui
(真偽値) (オプション) この投稿タイプを管理するデフォルト UI を生成するかどうか。
初期値: public 引数の値

  • false - この投稿タイプのユーザーインターフェースを表示しない
  • true - (管理パネルで)この投稿タイプのユーザーインターフェースを表示する

参考:投稿や固定ページのようなビルトイン(WordPress に最初から組み込まれている)投稿タイプは、意図的に false に設定されている。

show_in_nav_menus
(真偽値) (オプション) ナビゲーションメニューでこの投稿タイプが選択可能かどうか。
初期値: public 引数の値

show_in_menu
(真偽値|文字列) (オプション) 管理画面にこの投稿タイプを表示するかどうか。表示するには show_ui が true でなければならない。
初期値: show_ui 引数の値

  • false - 管理画面に表示しない
  • true - 管理画面のトップレベルのメニューとして表示する
  • メニューの文字列 - 'tools.php' や 'edit.php?post_type=page' のようなトップレベルのページを指定すると、この投稿タイプをそのサブメニューに配置する。

参考: あるプラグインが作成するメニューページのサブメニューとなるように 'メニューの文字列' を指定すると、この項目はサブメニューの最初の項目になると共に、トップレベルのリンク先を置き換える。そうしたくない場合は、プラグインが admin_menu にメニュー表示アクションをフックする add_action を実行するとき、優先度に 9 以下を指定する必要がある。
参考: この引数は show_ui の値をデフォルトとするが、それ自体のデフォルト値である public に基づく(投稿タイプがパブリックに利用可能である場合には管理画面に表示する)と考えるのが最もわかりやすい。少なくとも、これはビルトインの投稿タイプである post と page に該当する。

show_in_admin_bar
(真偽値) (オプション) この投稿タイプを WordPress の管理バーから使えるようにするかどうか。
初期値: show_in_menu 引数の値

menu_position
(整数) (オプション) この投稿タイプが表示されるメニューの位置。表示するには show_in_menu が true でなければならない。
初期値: null - デフォルトは「コメントの下」

  • 5 - 投稿の下
  • 10 - メディアの下
  • 15 - リンクの下
  • 20 - 固定ページの下
  • 25 - コメントの下
  • 60 - 最初の区切りの下(コメントの下に区切りがある)
  • 65 - プラグインの下
  • 70 - ユーザーの下
  • 75 - ツールの下
  • 80 - 設定の下
  • 100 - 二つ目の区切りの下(設定の下に区切りがある)

menu_icon
(文字列) (オプション) このメニューで使用するアイコンの URL、または Dashicons のアイコンの名前。
初期値: null - デフォルトは投稿アイコン
用例

  • 'dashicons-video-alt' (Dashicons のビデオアイコンを使う)
  • 'get_template_directory_uri() . "images/cutom-posttype-icon.png"' (現在のテーマに含まれているイメージを使う)

capability_type
(文字列|配列) (オプション) 閲覧/編集/削除の権限を構築する文字列。権限を構築するベースにこの引数を使う場合、配列を指定すると複数形の名前を変更できる。例えば array('story', 'stories') を指定すると、最初の要素が単数形の権限、二番目が複数形の権限として使われる。これは、配列を指定しない場合に自動生成される名前("storys" になる筈)の代わりになる。'capabilities' パラメータによって明示的にセットされなければ、この 'capability_type' パラメータが権限構築のベースとして使われる。これを有効にするには map_meta_cap を true にする必要がある。
初期値: "post"
使用できる権限タイプの幾つか(たぶん網羅したリストではない):

  • post (デフォルト)
  • page

これらのビルトイン(組み込み済み)タイプは使用できない:

  • attachment
  • mediapage

capabilities
(配列) (オプション) この投稿タイプの権限の配列。
初期値: capability_type を使って構築される
デフォルトでは、権限の配列に 7 種類のキーを入れることができる:

  • edit_post, read_post, それと delete_post - これら 3 つはメタ権限であり、通常はコンテキストに応じて、対応する基本権限に変換される。コンテキストは例えば、編集/閲覧/削除される投稿(post)と、チェックされるユーザーまたは権限グループ(role)で決まる。つまり、これらの権限は通常、ユーザーや権限グループに対して直接に許可されるものではない。
  • edit_posts - この投稿タイプのオブジェクトの編集可否を制御する。
  • edit_others_posts - この投稿タイプの、他のユーザーが所有するオブジェクトの編集可否を制御する。投稿タイプが投稿者をサポートしなければ、これは edit_posts と同様に振る舞う。
  • publish_posts - この投稿タイプのオブジェクトの公開可否を制御する。
  • read_private_posts - プライベートなオブジェクトの閲覧可否を制御する。

参考:後の 4 つは基本権限であり、WordPress コアのいろいろな箇所でチェックされる。
さらに 7 種類の基本権限があるが、それらは map_meta_cap() を除く WordPress コアでは直接参照されない。map_meta_cap() は、最初に説明した 3 つのメタ権限を受け取り、コンテキストによってユーザーや権限グループに対してチェックされる一つ以上の基本権限へ変換する。下記の権限は map_meta_cap() の中でのみ使われる。そのため、下記の権限は 'map_meta_cap' 引数を true にして(デフォルトは false)投稿タイプを登録したときにデフォルトで割り当てられる。

  • read - この投稿タイプのオブジェクトの閲覧可否を制御する。
  • delete_posts - この投稿タイプのオブジェクトの削除可否を制御する。
  • delete_private_posts - プライベートなオブジェクトの削除可否を制御する。
  • delete_published_posts - 公開されているオブジェクトの削除可否を制御する。
  • delete_others_posts - 他のユーザーが所有するオブジェクトの削除可否を制御する。投稿タイプが投稿者をサポートしなければ、これは delete_posts と同様に振る舞う。
  • edit_private_posts - プライベートなオブジェクトの編集可否を制御する。
  • edit_published_posts - 公開されているオブジェクトの編集可否を制御する。

map_meta_cap
(真偽値) (オプション) WordPress が持つデフォルトのメタ権限処理を使用するかどうか。
初期値: null
参考:false を指定すると admin 権限グループのユーザーは投稿タイプを編集できなくなります。その場合は edit_post 権限をすべての権限グループ(投稿タイプを追加または編集する)へ追加しなければなりません。

hierarchical
(真偽値) (オプション) この投稿タイプが階層を持つ(例:固定ページ)かどうか。true の場合、親を指定できるようになる。編集ページに親を選択するボックスを表示するために、'supports' パラメータに 'page-attributes' を含めなければならない。
初期値: false
参考:このパラメータは元々固定ページのために用意された。もし多数のエントリーを作る(ざっと 100 を超えるような)つもりなら、メモリー不足の問題が発生するだろう。このパラメータを true にすると、その投稿タイプの管理画面を表示するときに、WordPress はその投稿タイプのすべてのエントリーをすべてのメタデータと一緒に取得するから。

supports
(配列|真偽値) (オプション) add_post_type_support()/en を直接呼び出すエイリアス。バージョン 3.5 以降では、配列の代わりに真偽値 false を指定することによりデフォルトの動作(title と editor)を止めることができる。
初期値: title と editor

  • 'title' (タイトル)
  • 'editor' (内容の編集)
  • 'author' (作成者)
  • 'thumbnail' (アイキャッチ画像。現在のテーマが post-thumbnails をサポートしていること)
  • 'excerpt' (抜粋)
  • 'trackbacks' (トラックバック送信)
  • 'custom-fields' (カスタムフィールド)
  • 'comments' (コメントの他、編集画面にコメント数のバルーンを表示する)
  • 'revisions' (リビジョンを保存する)
  • 'page-attributes' (メニューの順序。「親〜」オプションを表示するために hierarchical が true であること)
  • 'post-formats' (投稿のフォーマットを追加。投稿フォーマットを参照)

参考:アイキャッチ画像を使用できるカスタム投稿タイプを使うときは、テーマがアイキャッチ画像をサポートしているか、または add_theme_support() を呼び出しているかチェックすること。

register_meta_box_cb
(callback) (オプション) 編集フォームのメタボックスをセットアップするのに呼び出すコールバック関数を指定する(関数名を文字列で指定)。コールバック関数は 1 つの引数 \$post (現在編集中の投稿の WP_Post オブジェクト)を受け取る。コールバックの中では remove_meta_box()/en と add_meta_box() を呼び出す。
初期値: なし

taxonomies
(配列) (オプション) この投稿タイプで使用する、登録されたタクソノミーの配列(category や post_tag など)。register_taxonomy_for_object_type() を直接呼び出す代わりに使用可能。タクソノミー(カスタム分類)は register_taxonomy() で登録する必要がある。
初期値: タクソノミー無し

has_archive
(真偽値|文字列) (オプション) この投稿タイプのアーカイブを有効にする。デフォルトでは、アーカイブのスラッグとして \$post_type が使われる。
初期値: false
参考:リライトが有効になっている時は、適切なリライトルールが生成される。rewrite オプションを指定して、使用されるスラッグを変更することもできる。

permalink_epmask
(文字列) (オプション) リライト用 endpoint ビットマスクのデフォルト値。詳細は Trac チケット 12605 および Make WordPress Plugins ブログの endpoint を要約した投稿を参照。
初期値: EP_PERMALINK
参考:バージョン 3.4 から、この引数は rewrite の 'ep_mask' 引数によって置き換えられました。

rewrite
(真偽値|配列) (オプション) この投稿タイプのパーマリンクのリライト方法を変更する。リライトを避けるには false を指定する。
初期値: true - \$post_type をスラッグに使う
\$args 配列

  • 'slug' => 文字列 パーマリンク構造のスラッグを変更。デフォルトは \$post_type の値。翻訳可能であること。
  • 'with_front' => 真偽値 Should the permalink structure be prepended with the front base. (例:パーマリンク構造が /blog/ である場合、false ならリンクは /news/、true なら /blog/news/ になる。)デフォルトは true
  • 'feeds' => 真偽値 この投稿タイプについてフィードのパーマリンク構造を作成する。デフォルトは has_archive 引数の値
  • 'pages' => 真偽値 パーマリンク構造をページ送りに対応させる。デフォルトは true
  • 'ep_mask' => 定数 バージョン 3.4 以降 この投稿タイプに endpoint マスクを割り当てる。詳しくは Trac チケット 19275 および Make WordPress Plugins ブログの endpoint を要約した投稿を参照。
    • これを指定せず permalink_epmask がセットされていると、permalink_epmask の値が使われる。
    • これを指定せず permalink_epmask もセットされていなければ、デフォルトの EP_PERMALINK になる。

参考:プラグインの内部で投稿タイプを登録する場合は、有効化と無効化のフック(下記の「有効化するときリライトルールをフラッシュする」を参照)の中で flush_rewrite_rules() を呼び出すこと。もし flush_rewrite_rules() /enを使わない場合は、カスタム投稿タイプが正しいパーマリンク構造を表示するために、管理画面の 設定 > パーマリンク設定 を開いてパーマリンク構造を更新(変更を保存)する必要がある。

query_var
(真偽値|文字列) (オプション) この投稿に使用する query_var キーの名前。
初期値: true - $post_type の値

  • false - query_var キーを使用しない。URL 形式のクエリ /?{query_var}={投稿のスラッグ} では表示できない。
  • 文字列 - /?{この引数で指定した文字列}={投稿のスラッグ} で意図したとおり表示される。

参考: 'publicly_queryable' パラメータが false のとき、この query_var パラメータは無効。query_var パラメータを指定するとカスタム投稿タイプのクエリ変数が WordPress の query_var 配列に追加され、認識されるようになる。この配列にないクエリ変数は WordPress によって取り除かれる。
true を指定すると、カスタム投稿タイプ(例:book)を次の形式でリクエストできる: example.com/?book=life-of-pi
true ではなく文字列を指定すると(例:'publication')次の形式でリクエストできる: example.com/?publication=life-of-pi

can_export
(真偽値) (オプション) この投稿タイプをエクスポート可能かどうか。
初期値: true

実際にやってみる

register_post_type()を使い、上記の引数の意味を理解すればもう出来たも同然!
今回は「Qiitaの投稿」という名前でカスタム投稿タイプを作成してみます。
functions.phpの一番下に(どこでも良いけどわかりやすくするために下)以下を追記します。

functions.php
register_post_type(
  'oreore_qiita',
  [
    'label' => 'Qiitaの投稿',
    'public' => true,
  ]
);

これで管理画面の左メニューに「Qiitaの投稿」と出てきます。
管理画面
他、第2引数の設定次第でまだまだカスタマイズが可能です。
ここまででカスタム投稿タイプを作ることが出来ましたが、このやり方はWordPressのお作法的によくなさそうです。

アクションフック

add_action()を使うことで、WordPressが用意するアクションや自分で作ったアクションが動く際に、このフックで登録した関数を呼び出すことが出来ます。
用意されているアクションの中にinitがあります。
このアクションでregister_post_type() を登録してやるとお作法的に良いみたいです。
add_action()関数の情報は以下

説明

特定のアクションに関数をフックします。

使い方

bool add_action( $hook, $function_to_add, $priority, $accepted_args );

パラメータ

\$hook
(文字列) (必須) \$function_to_add がフックされるアクション名。アクションフック名一に覧ついては プラグイン API/アクションフック一覧 を参照。テーマまたはプラグインファイル内のアクション名も指定できる。または特別なタグ "all" を使えば、すべてのフックで関数が呼び出される。
初期値: なし

\$function_to_add
(コールバック) (必須) フックする関数名。注: 'callback' タイプとして PHP ドキュメンテーションに掲載されている文字列形式構文のみが有効
初期値: なし

\$priority
(整数) (オプション) 特定のアクションに関連づけられている関数が実行される優先順序を指定する。少ない数であれば早く実行され、同じ数の優先度である関数はアクションに追加された順序で実行される。
初期値: 10

\$accepted_args
(整数) (オプション) フックした関数が受け入れられる引数の数。WordPress 1.5.1 以降では、フックした関数は、対応する do_action() あるいは apply_filters() が実行される時に、余分に引数を取ることができます。例えば、アクション comment_id_not_found は、これにフックする関数に、リクエストされたコメントの ID を渡すことができます。
初期値: 1

アクション一覧

たくさんアクションが有るみたいです。以下が参考になります。
プラグイン API/アクションフック一覧

最終形態

add_action()init時にregister_post_type()を使う形にするのが理想なので、

functions.php
function oreore_register_post_type_qiita() {
  register_post_type(
    'oreore_qiita',
    [
      'label' => 'Qiitaの投稿',
      'public' => true,
    ]
  );
}

add_action('init', 'oreore_register_post_type_qiita');

まとめ

WordPressのカスタム投稿タイプをソースをいじる方法で作成しました。
functions.phpとアクションフックを抑えて、ドキュメントを呼んでいけば良さそうです。

参考

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

CakePHP4のasciiAlphaNumericについて

CakePHP2,3系では、alphaNumericが、日本語を通してしまう問題がありました。

CakePHP4では、asciiAlphaNumericが用意されたため、半角英数字のみを受け入れるバリデーションが出来ます。

ここまでの過程

【参考】

英数字のみを受け入れるフォームを作りたいときに、CakePHPのalphaNumericバリデーションを使って実装すれば簡単だと思っていました。

しかし・・

日本語がバリデーションをスルスル通過する(泣)

調査していくと、CakePHP2,3系も同様の問題を抱えていて、例えば、2,3系では、カスタムバリデーションを自分で書いて日本語などの全角文字を防いでいたりと、他のアプローチで対応する必要がありました。

alphaNumericは、CakePHP2,3では以下のようになっています。

Validation.php
    public static function alphaNumeric($check)
    {
        if (empty($check) && $check !== '0') {
            return false;
        }

        return self::_check($check, '/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/Du');
    }

以下が4系のalphaNumericです。

4系でもalphaNumericバリデーションは、日本語を通す挙動になっていました。

Validation.php
    public static function alphaNumeric($check): bool
    {
        if ((empty($check) && $check !== '0') || !is_scalar($check)) {
            return false;
        }

        return self::_check($check, '/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/Du');
    }

4系のalphaNumericには、!is_scalar($check)が追加されています。

!is_scalar($check)は、値がスカラー値かどうかをチェックするメソッドです。スカラーに関しては、下記の記事が分かりやすいです。

スカラーについての記事

スカラー値の判定をすることで、配列などもチェックされるようになりました。

alphaNumericとasciiAlphaNumericの違いは?

こちらがasciiAlphaNumericバリデーションです。

Validation.php
     public static function asciiAlphaNumeric($check): bool 
     { 
         if ((empty($check) && $check !== '0') || !is_scalar($check)) { 
             return false; 
         } 
         return self::_check($check, '/^[[:alnum:]]+$/'); 
     } 

なぜalphaNumericは、日本語が通過してしまうのだろうと思い調査してみました。

alphaNumericは内部で、/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/Duという正規表現を通しています。

この正規表現は、日本語を通過してしまいます。

それに対して、asciiAlphaNumericは内部で /^[[:alnum:]]+$/という正規表現を通しています。

これにより、日本語が通過しなくなりました。

最後まで読んでくださり、ありがとうございました。

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

Laravel-SnappyでPDFを出力する際のページ番号の表示を制御する

はじめに

この記事ではLaravel-Snappyを利用してPDF出力処理を既に実装されていることを前提にしています。
Laravel-Snappyの導入方法については触れません。

ページ番号の表示方法

オプションで表示する方法

Laravel-Snappyを使ってPDFにページ番号を表示したい場合、以下のようにPDFインスタンスを作成する際のオプションを指定することで簡単に実装することができます。

$pdf = PDF::loadView('pdf')
    ->setOption('footer-center', '[page] ページ')); // フッター中央 [page]でページ番号が自動で入ります

ただし、出力されるPDFの総ページ数が1ページの場合でも表示されてしまいます。
1ページの時はページ数を非表示にする場合はページの表示箇所を自前で実装する必要があるようです。
オプション指定の方法でどうにかできないか調べましたが見つかりませんでした。

自前で表示する方法

ページ数の情報はPDFに読み込ませるHTMLのLocationオブジェクトから取得することができます。
Location.hrefには以下が格納されています。
pageが現在のページ番号
topageが総ページ数

file:///tmp/knp_snappy5e3b69aa559ad4.24507587.html?page=1&section=&sitepage=1&title=&subsection=&frompage=1&subsubsection=&isodate=2020-02-06&topage=2&doctitle=&sitepages=2&webpage=&time=&date=

この情報を用いて、ページ番号を表示する為にフッターを用意します。

footer.blade.php
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body class="bg-white">
<div class="text-center">
    <span id="pdfkit_page_current"></span>
</div>
<script type="text/javascript">
    (function () {
        var pdfInfo = {};
        var x = document.location.search.substring(1).split('&');
        for (var i in x) {
            var z = x[i].split('=', 2);
            pdfInfo[z[0]] = unescape(z[1]);
        }
        var page = pdfInfo.page || 1; // ページ番号
        var pageCount = pdfInfo.topage || 1; // 総ページ数
        // 改ページがある場合のみページ番号を表示します
        if (pageCount > 1) {
            document.getElementById('pdfkit_page_current').textContent = page;
        }
    })();
</script>
</body>

用意したフッターをPDFに設定するにはオプションで指定します。

$pdf = PDF::loadView('pdf')
    ->setOption('footer-html', \View::make('footer')->render() // 用意したフッターを設定
    ->setOption('margin-bottom', 10)); // フッターの余白を設定します

以上になります。

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

FuelPHP のTaskから呼んだクラス内で ENV指定したConfigが取れない時

こんにちわ。パグです。備忘録です。


FuelPHP のTaskから呼んだクラス内で ENV指定したConfigが取れない時がありました。
具体的にいうと、Function run(){}の中からさらにStaticClassを呼んでたんだけど
その中で取得しようとしてるConfig::get()がapp/config直下のものしか取れなくて、
ENVで指定したdb.phpを取得できてないというもの。
以下はFuelPHP1.8の環境で試しました。

FUELPHPのタスクは

env FUEL_ENV=ENVName php oil r [taskname]

で呼び出しますが、タスクの中にさらにStaticのクラスとかが呼ばれていて

function run(){
     $return = \Func_Class::static($value);
}
Func_Class::staticの中では
Config::get("db.xxx");

Func_Classのなかでみているのはapp/config配下のdb.phpのようです。
でも私はapp/ENV/db.phpを呼び出したい・・・。
そんな時はTask内で上書きしてしまいましょう。

function run(){
   \Config::load('db','db');←これを追加する
     $return = \Func_Class::static($value);
}

ConfigLoadをすると、TaskでSetEnvしたENV配下のConfigが取得できるので、
dbという名前で上書きします。
以降は

function run(){
     $return = \Func_Class::static($value);
}
Func_Class::staticの中では
Config::get("db.xxx");

こんな感じで呼び出せば、指定ENV配下のConfigが取得できるようになります。
なんか細かくみてないけど
Fuel/Taskって NameSpaceが違うから、Fuel\Coreで普通に呼び出せる
意識しないで取得できる Overwriteとかがされてないみたいなんだよね。

通常のOrmModelとかの方はちゃんとSetEnvできてるんだけど。

というわけで困った人はお試しください。

ではまたの。

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

Laravel DB.com ” New!! チーム共有機能 ”

Laravel DB.com ってなに?

Laravel DB.com ってなに?前回の紹介記事です!
https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

New「チーム共有機能」

シェア機能のことです。

「シェアData 送信側」

1. [シェアData]作成

2. [シェアData]IDをコピー

ここでコピーしたIDを相手に知らせます。

「シェアData 受信側」

1.[シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。

2.「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了しました。

受信側にデータが入りましたので、受信側もそのデータを活用できるようになります。

現在はベータ版機能として、「1データのみ共有可能」 になります。
それだけでも、便利になるはずですので、お試しいただければと思います。

Laravel専用ツール!!

日本から世界へ!世界で使われるプロダクトになることを願っております。
是非、応援の程よろしくお願いいたしますm(_ _)m
ツイッターのフォローも興味があれば!!
Twitter: LaravelDB.com


LaravelDB.com解説ページ一覧

コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』

https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

LaravelDB.com 対応カラム一覧

https://qiita.com/daisu_yamazaki/items/92dc3cc599a264c3fb0f

LaravelDB.com テーブル命名ルール

https://qiita.com/daisu_yamazaki/items/1cb5987cc6d1008def82

LaravelDb.com integerの注意点

https://qiita.com/daisu_yamazaki/items/f2e6d58cfa20fa81fd54

LaravelDB.com Faker(テストデータ投入)

https://qiita.com/daisu_yamazaki/items/57669e8fa2c256d85c95

以上

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

【LaravelDB.com】New !! ”チーム共有機能 ” 早々に追加された訳

Laravel DB.com ってなに?

Laravel DB.com ってなに?前回の紹介記事です!
https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

なぜ1週間程度で”Team共有機能”を実装したのか?

私は、G'sACADEMYや大学院/大学などで先生をしてます。
Team開発や、メンターとのオンラインMTG用にあった方が助かるとの希望をいくつか聞きました。更にTwitterでもアンケートを取り、微妙な数字でしたが、とりあえず学生の意見もあったのでシンプルな機能であれば実装しようと思いました。

「どうやったら?早く、シンプルに実装可能か?」を仕様検討していたのですが、一つの方法を昨日考えつきました。昨日から朝まで実装、不具合があったので、さっきの午前中までかかり完成(ベータ版としておきます)。
必要な気がしてたので、まずは実装して、これから改善ですね。スピードは時間を有意義に使うための重要なスキルですね。これで、少しでも、オンライン上でデータのやり取りができたら良いのですが。。。また、追加機能・改修等があれば記事を追加して行きたいと思います。

New「チーム共有機能」

シェア機能のことです。

【シェアData 送信側】

1. [シェアData]作成


POINT:
この時点で共有データが作成されます。
見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう! 「データ更新したので見てください!」って後から言えるってことですね。

2. [シェアData]IDをコピー

ここでコピーしたIDを相手に知らせます。

【シェアData 受信側】

1.[シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。
こちらのIDを知っていれば誰でも読み込めます!見て欲しい人に渡しましょう!
※LOGIN(Googleアカウントで)しないと見れないことは認識しておいてください。

2.「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了しました。

受信側にデータが入りましたので、受信側もそのデータを活用できるようになります。

現在はベータ版機能として、「1データのみ共有可能」 になります。
それだけでも、便利になるはずですので、お試しいただければと思います。

Laravel専用ツール!!

日本から世界へ!世界で使われるプロダクトになることを願っております。
是非、応援の程よろしくお願いいたしますm(_ _)m
ツイッターのフォローも興味があれば!!
Twitter: LaravelDB.com


LaravelDB.com解説ページ一覧

コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』

https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

LaravelDB.com 対応カラム一覧

https://qiita.com/daisu_yamazaki/items/92dc3cc599a264c3fb0f

LaravelDB.com テーブル命名ルール

https://qiita.com/daisu_yamazaki/items/1cb5987cc6d1008def82

LaravelDb.com integerの注意点

https://qiita.com/daisu_yamazaki/items/f2e6d58cfa20fa81fd54

LaravelDB.com Faker(テストデータ投入)

https://qiita.com/daisu_yamazaki/items/57669e8fa2c256d85c95

以上

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

NTTのCOTOHA API(コトハAPI)でWordPressの自動タグ生成プラグインを作ってみた。

NTTのCOTOHA API(コトハAPI)でWordPressの自動タグ生成プラグインを作ってみました。

2020-02-20_0923.png

GUIはこんな感じになります。

jp-auto-tagというプラグインをYahoo!APIを使用し作っていましたが

何だか、バージョンが変わるらしくてその対応するのが…。
面倒くさいなぁと感じたのでコトハAPIで同じ感じのものを作ってみました。
jp-auto-tagを使用している方はjp-auto-tagを停止してjp-auto-tag2をご使用ください。
また圧縮ファイルを解凍しワードプレスのプラグインフォルダにフォルダごと、
アップロードしプラグイン有効にしAPIの設定すると自動タグが生成可能です。

ブログ投稿時にタグが自動生成されますので、確認の際は画面を再読み込みしてご確認ください。

尚、オプションデータは残り続けますので悪しからず?

プラグインのソースコードはこんな感じになります。

原文とプラグインダウンロードはこちら

<?php
/*
Plugin Name: jp-auto-tag2
Version: 0.1.10
Description: auto jp tag2
Author: taoka toshiaki
Author URI: https://zip358.com/
Plugin URI: https://zip358.com/plugin/jp-auto-tag2.zip
*/

class jp_auto_tag2
{

    public $db_option = "jp_auto_tag2";


    function frm_page2()
    {
        add_menu_page('jp-auto-tag2', 'jp-auto-tag2',  'manage_options', __FILE__, array($this, 'show_text_option_page2'), '', 8);
    }

    function show_text_option_page2()
    {
        wp_enqueue_style('bootstrap', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', array(), '3.3.6');
        wp_enqueue_script('bootstrap', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js', array(), '3.3.6');

        $options = get_option($this->db_option);
        if (!empty($options)) {
            $Access_Token_Publish_URL = $options["Access_Token_Publish_URL"];
            $API_Base_URL = $options["API_Base_URL"];
            $Client_ID = $options["Client_ID"];
            $Client_secret = $options["Client_secret"];
            $max_keyword_num = $options["max_keyword_num"];
        }
        include_once dirname(__FILE__) . '/jp-auto-tag-tmp2.php';
    }

    function ajax_event2()
    {
        $options["max_keyword_num"] = $obj["max_keyword_num"] = (int)$_POST["max_keyword_num"]<5?5:(int)$_POST["max_keyword_num"];
        $options["Access_Token_Publish_URL"] = $obj["Access_Token_Publish_URL"] = $_POST["Access_Token_Publish_URL"];
        $options["API_Base_URL"] = $obj["API_Base_URL"] = $_POST["API_Base_URL"];
        $options["Client_ID"] = $obj["Client_ID"] = $_POST["Client_ID"];
        $options["Client_secret"] = $obj["Client_secret"] = $_POST["Client_secret"];
        update_option($this->db_option, $options);
        print json_encode($obj);
        die(0);
    }


    function api_tag2($post_id)
    {
        $cuthttp = function ($str = "") {
            if (!$str) return $str;
            return preg_replace("/https?:\/\/[a-zA-Z0-9|%|\?|_|=|-|\.|\/]*$/m", "", $str);
        };
        $post = get_post($post_id);
        $title = $post->post_title;
        $content = strip_tags($post->post_content);
        $document = $title . $cuthttp($content);

        $options = get_option($this->db_option);

        if ($options["Access_Token_Publish_URL"] && $options["Client_ID"] && $options["Client_secret"]) {

            $curl = function ($ptn, $options, $token = "", $token_type = "", $document = "") {
                switch ($ptn) {
                    case "token":
                        $data = [
                            'grantType' => 'client_credentials',
                            'clientId' => $options["Client_ID"],
                            'clientSecret' => $options["Client_secret"],
                        ];

                        $header = [
                            'Content-Type: application/json;charset=UTF-8',
                        ];
                        $url = $options["Access_Token_Publish_URL"];
                        break;

                    case "tag":
                        $data = [
                            'document' => $document,
                            'max_keyword_num'=>$options["max_keyword_num"],
                        ];

                        $header = [
                            'Authorization: ' . ucfirst($token_type) . ' ' . $token,
                            'Content-Type: application/json;charset=UTF-8',
                        ];
                        $url = rtrim($options["API_Base_URL"],"/")."/nlp/v1/keyword";
                        break;
                }
                $curl = curl_init();
                curl_setopt($curl, CURLOPT_URL, $url);
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
                curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
                curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($curl, CURLOPT_HEADER, true);
                $response = curl_exec($curl);
                $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); 
                $header = substr($response, 0, $header_size);
                $body = substr($response, $header_size);
                $res = (object)json_decode($body, true); 
                curl_close($curl);
                return $res;
            };
            $res = $curl("token", $options);
            $access_token = $res->access_token;
            $token_type = $res->token_type;
            if ($options["API_Base_URL"] && $document && $access_token){
                $res = $curl("tag", $options, $access_token, $token_type, $document);
                foreach($res->result as $key=>$val){
                    $tags[] = $val["form"];
                }
                wp_set_post_tags($post_id, implode(",", array_unique($tags)), false);
            }
        }
    }
}

$jp_auto_tag2 = new jp_auto_tag2();

add_action('save_post', array($jp_auto_tag2, 'api_tag2'));
add_action('publish_post', array($jp_auto_tag2, 'api_tag2'));
add_action('admin_menu', array($jp_auto_tag2, 'frm_page2'));
add_action('wp_ajax_ajax_event2', array($jp_auto_tag2, 'ajax_event2'));
<form id="ajax-frm">
<table class="table">
    <tr>
        <td>
            プラグイン説明:jp-auto-tag2はapi.ce-cotoha.comのAPIを<br>
            使用し投稿文を解析しキーワードをタグを抽出します。<br>
        </td>
    </tr>
    <tr>
        <td>
            <a href="https://api.ce-cotoha.com/contents/index.html" target="new">https://api.ce-cotoha.com/contents/index.html</a><br>
            上記のURLよりユーザー登録を行いAPI情報をそれぞれ入力ください。全て必須項目になります。
        </td>
    </tr>
    <tr>
        <td>
            Access Token Publish URL:<br>
            <input type="text" name="Access_Token_Publish_URL" value="<?=$Access_Token_Publish_URL?>"   class="form-control">
        </td>
    </tr>
    <tr>
        <td>
            API Base URL:<br>
            <input type="text" name="API_Base_URL" value="<?=$API_Base_URL?>"   class="form-control">
        </td>
    </tr>
    <tr>
        <td>
            Client ID:<br>
            <input type="text" name="Client_ID" value="<?=$Client_ID?>"   class="form-control">
        </td>
    </tr>
    <tr>
        <td>
            Client_secret:<br>
            <input type="text" name="Client_secret" value="<?=$Client_secret?>"   class="form-control">
        </td>
    </tr>
    <tr>
        <td>
            抽出するキーワード(タグ数)5以上:<br>
            <?php
            $max_keyword_num = (int)$max_keyword_num<5?5:$max_keyword_num;
            ?>
            <input type="text" name="max_keyword_num" value="<?=$max_keyword_num?>"   class="form-control">
        </td>
    </tr>
    <tr>
        <td colspan="2"><input type="button" id="frmsubmit" value="登録する" class="form-control"></td>
    </tr>
</table>    
</form>
<script>
    jQuery(function($){
        $("#frmsubmit").on("click",function(){
            var ajaxurl = '<?=admin_url( 'admin-ajax.php');?>';
            var data = $("#ajax-frm").serializeArray();
            data.push({name:"action",value:"ajax_event2"});
            $.ajax({
               type:'POST',
               url:ajaxurl,
               data:data,
               success:function(obj){
                   console.log(obj);
                   alert("更新しました");
               }
            });

        });
    })
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacOS Catalina上で、peclでOAuthを使えるようにするまで

背景

PHPなんて相当昔に使って以来全く使っていなかったのだけど、わけあってちょっとしたサンプルを動かして挙動確認をしなくてはならなくなり、MacにPHP/PECL環境を作ったので備忘録。

環境

macOS Catalina 10.15.3

やりたいこと

PHPで書かれているサンプルプログラムをもらったのだが、その中でOAuth1.0を使っているので、

  • PHP
  • OAuth

が動く環境を作りたい

結論

多分以下をやればよい。

% brew instal php
% brew install autoconf
% brew install pcre
% pecl install oauth

以下も必要かもしれない。

% brew update
% brew upgrade

以下苦心の跡

まずMacにPHPをインストールしないとダメだろうと思った

・・・しなくてはならないと思っていたのですが、普通に最初からphpコマンドがあることを知らなかった。

% which php
/usr/bin/php

へぇ・・・と思った :sweat_smile:

でもこれが落とし穴でしたね。実際インストールし直さないとダメだったわけです。

PECL環境を作る

今回のサンプルは、oauth認証のAPI実行プログラムだったのだけど、そのためにはこいつが必要らしい。

OAuth

そのためにはPECLが使えないといけないのだけど、さすがにpeclコマンドはなかった。
のでそのインストール方法。

こちらが参考になりました。
https://qiita.com/applexco/items/f10e87477dc5d41cf751

ここに書かれているとおりすすめると、こんなことを聞かれます。

% php -d detect_unicode=0 ./go-pear.phar

Below is a suggested file layout for your new PEAR installation.  To
change individual locations, type the number in front of the
directory.  Type 'all' to change all of them or simply press Enter to
accept these locations.

 1. Installation base ($prefix)                   : /Users/yuzu/pear
 2. Temporary directory for processing            : /tmp/pear/install
 3. Temporary directory for downloads             : /tmp/pear/install
 4. Binaries directory                            : /Users/yuzu/pear/bin
 5. PHP code directory ($php_dir)                 : /Users/yuzu/pear/share/pear
 6. Documentation directory                       : /Users/yuzu/pear/docs
 7. Data directory                                : /Users/yuzu/pear/data
 8. User-modifiable configuration files directory : /Users/yuzu/pear/cfg
 9. Public Web Files directory                    : /Users/yuzu/pear/www
10. System manual pages directory                 : /Users/yuzu/pear/man
11. Tests directory                               : /Users/yuzu/pear/tests
12. Name of configuration file                    : /Users/yuzu/.pearrc

1-12, 'all' or Enter to continue:

特に変更する気もなかったのでallとしました。

あとは $HOME/pear/bin にコマンドがインストールされるのでそこにPATHを通せばOK。

OAuthのインストール

peclでOAuthをインストールしようとしてみます。

% pecl install oauth
WARNING: channel "pecl.php.net" has updated its protocols, use "pecl channel-update pecl.php.net" to update
downloading oauth-2.0.5.tgz ...
Starting to download oauth-2.0.5.tgz (49,641 bytes)
.............done: 49,641 bytes
6 source files, building
running: phpize
grep: /usr/include/php/main/php.h: No such file or directory
grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
Configuring for:
PHP Api Version:
Zend Module Api No:
Zend Extension Api No:
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.

こんな感じでNGになりました。

autoconfがないのが原因らしいので追加。

% brew install autoconf
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
aws-elasticbeanstalk       firebase-cli               nushell
cfn-lint                   fonttools                  serverless
chronograf                 glib                       sphinx-doc
cointop                    gmp                        subversion
devspace                   istioctl                   terragrunt
dwarfutils                 juju                       topgrade
erlang                     jupyterlab                 wtf
ethereum                   kube-aws
exploitdb                  node@12

==> Downloading https://homebrew.bintray.com/bottles/autoconf-2.69.catalina.bott
==> Downloading from https://akamai.bintray.com/ca/ca510b350e941fb9395522a03f9d2
######################################################################## 100.0%
==> Pouring autoconf-2.69.catalina.bottle.4.tar.gz
?  /usr/local/Cellar/autoconf/2.69: 67 files, 3.0MB

再チャレンジ。

(略)
configure: error: Couldn't find pcre.h, try installing the libpcre development/headers package
ERROR: `/private/tmp/pear/install/oauth/configure --with-php-config=/usr/bin/php-config' failed

まだなんか出る。

今度はpcreがないのが原因らしいので同じくbewする。

% brew install pcre
==> Downloading https://homebrew.bintray.com/bottles/pcre-8.44.catalina.bottle.t
==> Downloading from https://akamai.bintray.com/f8/f8ac266e04f984fa55091a43f0fdc
######################################################################## 100.0%
==> Pouring pcre-8.44.catalina.bottle.tar.gz
?  /usr/local/Cellar/pcre/8.44: 204 files, 5.5MB

再び。

In file included from /private/tmp/pear/install/oauth/oauth.c:12:
/private/tmp/pear/install/oauth/php_oauth.h:18:10: fatal error: 'php.h' file not found
#include "php.h"
         ^~~~~~~
1 error generated.
make: *** [oauth.lo] Error 1
ERROR: `make' failed

うーん。 :thinking:

このphp.hについては、こちらを参考にしたのですが、私の場合はこれだけでは解消しませんでした。

https://qiita.com/s0hno/items/756088b8c208a2b6428b

何か違いがあるのでしょうね。

で見つけたのが こちら

brewでphpインストールし直す というもの。

% brew install php

ただこれだけだと従来のphp(/usr/bin/php)が先にヒットするので、/usr/local/binが先にヒットするようにPATHをいじる必要はあります。

ここまでやると、 pecl install oauth が通るようになりました。

まとめ

ということで、macOS Catalinaでphpサンプル(要peclモジュール)を動かすやり方についてまとめました。
ホント久しぶりにPHP触ったのと、今回のサンプルを動かしたかっただけなので環境づくりもやっつけ感満載ですが参考になれば。

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

Moodle 3.8 マニュアル - Banner LMB

原文

Banner LMB

Banner/Luminis Message Broker は Banner-Moodle を統合する enrolment モジュールであり、元々は Eric Merrill により開発されました。Modules と Plugins ページ[1] からダウンロードでき Moodle Tracker のここ [2] にあります。

内容

1 何なのか
2 どのように動くのか
3 実装時の考慮...
4 SSL 証明書
5 README ファイルから

1 何なのか

Banner/LMB enrolment プラグインは Luminis Message Broker 経由で Banner から XML フラットファイルか live feed を処理できます。"people" "courses" "enrollments" などの情報が Moodle のコースにおいてユーザ、コースとユーザのエンロールをセットアップするために使用されます。

2 どのように動くのか

3 実装時の考慮...

Below is a list of considerations when installing and running the Banner/LMB Plugin:

Luminis Message Broker:

  • Firewall port must be open to allow outgoing traffic to the Moodle instance (in case of outsourced Moodle)

  • Does NOT like SSL certificates from certain companies, or which do not have the primary Moodle destination as the first listed domain on the cert. (aka Common Name on SSL Cert must match the LMB destination) (See more on SSL Certificates below)

Moodle:

  • watch out for permissions settings on the directories such as the /enrol/lmb, enrol/lmb/upload/ etc. - when improperly set, the logs did not work, nor the processing of the XML files/folder

4 SSL 証明書

以下は動作することが知られています。

  • Digicert
  • Geotrust

以下は動作しないことが知られています。

  • PositiveSSL

5 README ファイルから

GENERAL

This enrollment plugin can digest XML from the Luminis Message Broker, allowing realtime Banner to Moodle integration, as well as full XML extractions from Banner.

You can use this module with or without Luminis Message Broker. If you do not use Luminis Message Broker, you can instead use this module to import XML files from banner on a manual or automated basis.

This is a heavily modified version of the IMS Enterprise plugin.

Unlike the Moodle 1.5 version of this module, the current can be almost completely customized from with the standard Moodle configuration pages. If you need to make changes for your specific install, please let me know, so I can look into making it into a preference item (also take a look at the todo list below).

INSTALLATION

  1. Copy enrol/lmb into the enrol/ directory on your Moodle server.

  2. Copy lang/en_utf8/enrol_lmb.php into the lang/en_utf8/ directory on your Moodle server.

  3. Copy lang/en_utf8/help/enrol/lmb into the lang/en_utf8/help/enrol/ directory on your Moodle server.

  4. You should secure enrol/lmb/secure/ with a username/password using .htaccess.

  5. Login to your Moodle server as an admin user, and visit the 'Notifications' page. Moodle will automatically setup the tables for this module.

  6. Under Course->Enrolments edit the settings for Luminis Message Broker. NOTE: You must save the setting at least once, even if you don't make any changes, before you use this module. This is a bug that will be fixed later.

LMB --- You can configure, through the Luminis Message Broker interface, the LMB module to be a HTTP consumer. You should point it to enrol/lmb/secure/liveimport.php on your Moodle server, and enter the HTTP username and password you setup previously. You should only use SSL (HTTPS) to ensure security of the interface.

USAGE

When used with the Luminis Message Broker, you will generally import a complete XML from banner before the start of a term/semester, and the Luminis Message Broker will continuously send messages that will keep Moodle up-to-date throughout the term.

If you are using this module without Luminis Message Broker, you can configure the module to import a full XML file on a regular basis. To do this, call importnow.php from a script or cron, much in the same way that the Moodle cron is polled.

You should disable users from being able to change the course and user idnumber fields, these are used to reference courses. This may change in the future.

When doing mass imports, terms should come before courses and then crosslisted courses, users should come before enrolments (which need to come courses and crosslistsing). There are tools in work that will make this order less important.

MULTI-FILE EXTRACTS

Place a directory at the XML Folder path that you have specified in the settings. In this folder, place any XML files to be processed (must end in .xml), as well as an empty file called 'start'. The 'start' file and directory must be writable by the webserver.

When this is complete, you call extractprocess.php to preform the extract.

When the processing starts, the module will remove the file 'start', and create a file called 'processing'. After extract processing has completed, the 'processing' will be removed, and a 'done' file created. These status files allow people/scripts to check the state of the run. It is important that the XML files are not modified/replaced while the extract is processing, or inconsistent results may occur. Should a problem occur during extract, such as the processor dies, or the XML files are inadvertently modified/removed, it is safe to re-process the extract with a consistent set of XML.

In the event an incomplete XML set is processed, students/instructors may be inadvertently removed from their courses. Re-processing with a complete XML set will reinstate users into their courses and no data is lost.

カテゴリ:Contributed code(翻訳準備中)|Enrolment(翻訳準備中)|管理者
メインページ

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

PHPでFizzbuzz最短43バイト

今回は解決できてませんし、解決の糸口すら掴めていません。

PHPのFizzBuzz最短は長らく56バイトでした。

<?while($i++<100)echo$i%3?!$$i=$i:Fizz,$i%5?$$i:Buzz,~ ;
<?for(;$i++<100;)echo$i%3?!$$i=$i:Fizz,$i%5?$$i:Buzz,~ ;

ところで先日なんとなくshinh.orgを見てみたんですよ。

01.png

43?????????

他の回答者がみな56バイトで横並びになっているところに突然3/4の長さですよ。
しかもここ、もはや古代語と言って差し支えないPHP5.3.8ですからね。
一体全体どうなってんだ?
完全に理解の範疇を超えています。

さらに目を引くのが0B / 30B / 13Bという文字の使用割合です。
これは基準が公開されていて、順にバイナリ0バイト、英数30バイト、記号13バイトを使っているという意味になります。

56バイト達成者は全員1B / 26B / 29Bとか1B / 28B / 27Bのような割合になっているので、43バイト達成者は記号が圧倒的に少ないです。
さらに改行を表すchr(245)のバイナリを使っていません。

しかしですねここまでくると、最低限必要そうな記号を上げていくだけで、
・PHP開始タグ<?
・ループwhile($i++<100)
・改行および末尾"\n";

これだけで既に記号を12文字使ってるのですよ。
記号の削減は、小手先の変更でどうにかなるレベルではありません。
どうやら根本的に考え方を変えなければならないようです。

が、正直何一つ思いつきません。

私にはもう、<?=file_get_contents("path/to/fizzbuzz");みたいな何らかの回避ルートを見つけ出したとしか考えられません。
まあ、最近の他のコンテストを見てみると見事に何かされた形跡があるので、それが正解なのかもしれませんけどね。

ちょっと私のレベルではどうにもできなかったので、誰か答えよろ。

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