WEB

【WordPress】Photo Express for Googleのプラグインを残しておいたばかりに「Cannot redeclare str_starts_with()」エラー

WordPressでPhoto Express for Googleのプラグインを残しておいたためエラーでサイトが表示できなくなってました。

表側のみならず管理側のログイン画面もエラーで表示されず…

以下で解決できたのでメモ。

Photo Express for Googleのプラグインはすでにメンテナンスされておらず、プラグインとしても機能していないので消しておきましょう。

Photo Express for Googleでのエラー現象

ログを見ると以下のようなエラーが出ていました。

[error] 21434#0: *63 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Cannot redeclare str_starts_with() (previously declared in /xxx/wp-includes/compat.php:387) in /xxx/wp-content/plugins/photo-express-for-google/photo-express.php on line 250"

エラーの原因

str_starts_with()というメソッドが複数定義されていてエラーになっていました。

今回でいうと「wp-includes/compat.phpの387行目」と「photo-express-for-google/photo-express.phpの250行目」。

なので、もう機能していないphoto-express-for-googleの方をなんとかすればOK。

エラーの対応方法

エラーで管理画面にログインできないので、サーバーにターミナルでログインしてなんとかするしかなさそうです。

Photo Express for Googleプラグインのファイルを削除してしまっても良いですが、いったん復旧させてからプラグインを管理画面で削除することにします。

サーバーにターミナルでログインし作業していきます。
※ファイルのパスはそれぞれの環境に合わせて変更してください。

$ vi /xxx/wp-content/plugins/photo-express-for-google/photo-express.php
     function convert_to_https( $url ) {
-         if ( ! str_starts_with( $url, "https" ) && str_starts_with( $url, "http" ) ) {
+         if ( ! pe_str_starts_with( $url, "https" ) && pe_str_starts_with( $url, "http" ) ) {
             $url = 'https' . substr( $url, 4 );
         }
 
         return $url;
     }

-     function str_starts_with( $haystack, $needle ) { 
+     function pe_str_starts_with( $haystack, $needle ) {
         return substr( $haystack, 0, strlen( $needle ) ) === $needle;
     }

【変更点その1】
「function str_starts_with( $haystack, $needle ) {」でstr_starts_with()メソッドを宣言していて重複していたのでメソッド名をpe_str_starts_withに変更。
※変更後のメソッド名はpe_str_starts_withじゃなくて、ほかの名称でも大丈夫です。

     function str_starts_with( $haystack, $needle ) { 
     ↓
     function pe_str_starts_with( $haystack, $needle ) {

【変更点その2】
str_starts_with()メソッドを使用していた箇所「if ( ! str_starts_with( $url, "https" ) && str_starts_with( $url, "http" ) ) {」のメソッド名を変更後のメソッド名に変更。

         if ( ! str_starts_with( $url, "https" ) && str_starts_with( $url, "http" ) ) {
         ↓
         if ( ! pe_str_starts_with( $url, "https" ) && pe_str_starts_with( $url, "http" ) ) {

これでエラーが解消に画面が表示できると思います。

そしたら管理側にログインし、プラグインのところからPhoto Express for Googleを削除できます。

phalconで1つのformに複数のentity(model)の値を設定

Phalconでformに複数のentity(model)を設定する方法をメモ。

ちなみに今回試したPhalconのバージョンはPhalcon5。

RobotsモデルとCategoryモデルがあり、その2つのモデルの値をEditFormに設定するパターンで記載します。

2つのやり方で実現できたので2つ例を挙げます。

マージパターン

$robots = Robots::findFirst($robotsId);
$category = Category::findFirst($robots->category_id);

$form = new EditForm((object) array_merge($robots->toArray(), $category->toArray()));

RobotsとCategoryそれぞれの値を取ってきて、array_merge()してFormに値を設定します。

Formに配列を渡せないので、array_mergeした配列を(object)でキャストする必要があります。

createBuilderパターン

$result = $this->modelsManager->createBuilder()
    ->columns([
        'Robots.robots_id',
        'Robots.robots_name',
        'Category.category_name',
    ])
    ->addFrom('App\Models\Robots', 'Robots')
    ->innerJoin('App\Models\Category', 'Robots.category_id = Category.category_id', 'Category')
    ->where('Robots.robots_id = :robots_id:', ['robots_id' => $robotsId])
    ->getQuery()
    ->getSingleResult();

$form = new DirectManifestForm($result);

「$this->modelsManager->createBuilder()」でまとめてDBから値を取ってきて、Formに値を設定するパターン。

注意点としては、columns()でカラムを指定すること。

カラムを指定しなかったり、「*」ですべてのカラムを持ってくるとうまく値を代入できません。

    ->columns([
        'Robots.*',
        'Category.*',
    ])

のようにすると、「$result->Robots->robots_id」や「$result->Category->category_name」のようにしないと値を取得できないので、Formに値を設定できないようです。

Phalconで日付の比較バリデーション!開始日と終了日が間違っていないかチェックする方法。

Phalconで日付の比較バリデーションの方法をメモ。

開始日と終了日を入力するフォームがあった場合、開始日が終了日よりも後ろの日付にもしくは、終了日が開始日よりも前の日付になっていないかバリデーション。

ちなみに今回試したPhalconのバージョンはPhalcon5。

Phalconで日付の比較バリデーション

以下は、終了日のフォームにCallbackバリデーションを使用して実現しています。

開始日のフォームでバリデーションしてもOK。

use \Phalcon\Filter\Validation\Validator\Callback;


$endDate = new Date('end_date');
$endDate->setLabel('終了日');
$endDate->addValidators([
    new DateValidator([
        'format' => 'Y-m-d',
        'message' => '終了日に日付を入力してください。'
    ]),
    new Callback([
        'callback' => function ($data) {
            if ($data['start_date'] && $data['end_date']) {
                if (strtotime($data['start_date']) > strtotime($data['end_date'])) {
                    return false;
                }
            }
            return true;
        },
        'message' => '終了日は、開始日以降の日付を入力してください。'
    ])
]);
$this->add($endDate);

バリデーションの解説

new Callback([

Callbackバリデーションを定義しています。

'callback' => function ($data) {

$dataには送信されてきたFormデータがArrayで入っています。。

if ($data['start_date'] && $data['end_date']) {
    if (strtotime($data['start_date']) > strtotime($data['end_date'])) {
        return false;
    }
}
return true;

$data['start_date']が開始日(Y-m-d形式)、$data['end_date']が終了日(Y-m-d形式)。
strtotimeして日付を比較。

バリデーションエラーの場合に「return false」、バリデーションOKの場合は「return true」。

PhalconのUniquenessバリデーションで登録時と更新時でバリデーション変えるには?

phalcon5にはデータが一意か(重複しないか)バリデーションするクラスが用意されています。

そのクラスがUniquenessです。

マニュアルはこちら。
https://docs.phalcon.io/5.0/ja-jp/filter-validation#uniqueness

このUniquenessバリデーションを使うパターンとしては、ログイン情報を登録するテーブルのメールアドレスなどがあります。

この時、ハマったポイントがあったのでメモ。

Uniquenessバリデーションでハマった点

例えば以下のようなフォーム作成したいとします。

  • ログインのアカウント情報を登録するフォームを作成
  • アカウント情報テーブルに登録するメールアドレスは一意とする

この時にメールアドレスが重複していないかUniquenessバリデーションを使って書くと以下のようになります。
今回はバリデーションクラスを作成せず、form生成時にバリデーションを記載とします。

$mail = new Text('mail');
$mail->setLabel('メールアドレス');
$mail->setFilters('email');
$mail->addValidators([
    new PresenceOf([
        'message' => 'メールアドレスを入力してください。',
        'cancelOnFail' => true,
    ]),
    new Email([
        'message' => 'メールアドレスの形式で入力してください。'
    ]),
    new StringLength([
        'max' => 300,
        'messageMaximum' => 'メールアドレスは300文字以内で入力してください。'
    ]),
    // ここが重複チェック(一意か)のバリデーション
    new Uniqueness([
        'model'   => new Account(),
        'convert' => function (array $values) {
            // メールアドレスを暗号化してテーブルに保存している場合は、メアドを暗号化して検索
            // $valuesにはformで送信されてきたメールアドレスが格納されています
            $values['mail'] = crypt($values['mail']);
            return $values;
        },
        'message' => 'すでに登録済みのメールアドレスです。'
    ])
]);
$this->add($mail);

これで、formから送られてきた値をisValid()でバリデーションした際にUniquenessのバリデーションを実行できます。

今回は'model'で設定したAccountクラス(Accountテーブル)のmailカラムに、すでに同じメールアドレスが登録されていないかチェックしています。

で、新規登録時も更新時も同じFormを使用することが多いと思いますが、この時に新規登録の場合はいいのですが、更新時に問題が発生します。

更新時にメールアドレスを変更せずに更新しようとすると、Uniquenessのバリデーションにひっかかって更新できないのです。

例えば、以下のようなデータがあった場合に、

id mail
1 aaaa@bbb.com
2 hoge@hoge.com

id=1のデータを更新する場合に、メールアドレスを変更せずに更新とすると、id=1の「aaaa@bbb.com」と重複しているのでバリデーションエラーになります。

この場合は更新できてほしいです。

ハマりポイントの解決方法

実現したいバリデーションは以下です。

  • 新規登録時はすべてのデータと比較し重複していないかチェック
  • 更新時は自身のデータ以外と比較して重複していないかチェック

上記の方法では、新規登録時は条件を満たしていますが、更新時の条件は満たしていません。

この問題を解決するために'except'オプションを使用します。

'except'オプションは、指定した値を除外して重複チェックしてくれます。

マニュアルはこちらです。
https://docs.phalcon.io/5.0/ja-jp/filter-validation#using-except-for-fields-sql-operation-value-not-in-except

$mail = new Text('mail');
$mail->setLabel('メールアドレス');
$mail->setFilters('email');
$mail->addValidators([
    new PresenceOf([
        'message' => 'メールアドレスを入力してください。',
        'cancelOnFail' => true,
    ]),
    new Email([
        'message' => 'メールアドレスの形式で入力してください。'
    ]),
    new StringLength([
        'max' => 300,
        'messageMaximum' => 'メールアドレスは300文字以内で入力してください。'
    ]),
    // ここが重複チェック(一意か)のバリデーション
    new Uniqueness([
        'model'   => new Account(),
        'convert' => function (array $values) {
            // メールアドレスを暗号化してテーブルに保存している場合は、メアドを暗号化して検索
            // $valuesにはformで送信されてきたメールアドレスが格納されています
            $values['mail'] = crypt($values['mail']);
            return $values;
        },
        'except' => $entity->mail, // ここを追加
        'message' => 'すでに登録済みのメールアドレスです。'
    ])
]);
$this->add($mail);

exceptには更新しようとするデータのメールアドレスを指定します。

$entityはformクラスのconstructで受け渡ししています。

if ($id) {
    // 更新時はテーブルからデータを取得
    $account = Account::findFirst($id);
} else {
    // 新規登録時はデータなし
    $account = new Account();
}
$form = new AccountForm($account);

こうすることで、新規登録時は$entity->mailはnullなので、mailがnull以外のデータと比較してくれ、

更新時は、$entity->mailが更新しようとするデータのmailなので、自身のmail以外のデータと比較してくれます。

id mail
1 aaaa@bbb.com
2 hoge@hoge.com

こちらのデータの例でいうと

  • 新規登録時はすべてのmailと比較
  • id=1のデータ更新時はid=1のmail以外のmailと比較

となり、実現したかったバリデーションとなります。

Phalcon5のトランザクションでハマった!Rollbackできない、トランザクションが始まらない問題。

Phalcon5でDBにデータを登録する際に、トランザクションを使おうと思ってハマったのでメモ。
DBはMariaDB。

マニュアル見てやってもなぜか動かない…

マニュアルはこちら。
https://docs.phalcon.io/5.0/ja-jp/db-models-transactions

上から順番に試してみましたが、なぜかトランザクションが効かない…

Rollbackができるか以下のパターンで検証してみました。

まずは、$this->db->begin()のやつ。

$this->db->begin();

// $customerにデータ設定は省略
$result = $customer->save();

$this->db->rollback();

「PHP message: There is no active transaction」のエラーが発生。
トランザクションが開始されていないようです。

次は、TxManager()のやつ。

use Phalcon\Mvc\Model\Transaction\Manager as TxManager;

$manager = new TxManager();
$transaction = $manager->get();

// $customerにデータ設定は省略
$result = $customer->save();

$transaction->rollback();

Rollbackされず、データが保存されました。

phalcon5でトランザクションを使う方法

参考になるのが、Phalcon5のマニュアルだとisolatedの2つ目の「Transactions can be used to delete a number of records, ensuring that everything is deleted correctly」のところ。
https://docs.phalcon.io/5.0/ja-jp/db-models-transactions#isolated

あとは、Phalcon3.4のマニュアルが参考になります。
https://docs.phalcon.io/3.4/ja-jp/db-models-transactions#isolated-transactions

当環境では、TxManager()でsetTransaction()を使うことでトランザクションが使えました。

use Phalcon\Mvc\Model\Transaction\Manager as TxManager;

$manager = new TxManager();
$transaction = $manager->get();

$customer->setTransaction($transaction);

// $customerにデータ設定は省略
$result = $customer->save();

$transaction->rollback();

これでRollbackされ、データが登録されませんでした。

複数データを登録する場合はこんな感じ。

use Phalcon\Mvc\Model\Transaction\Manager as TxManager;

$manager = new TxManager();
$transaction = $manager->get();

// $customerにデータ設定は省略
$customer->setTransaction($transaction);
$result = $customer->save();

// $robotにデータ設定は省略
$robot->setTransaction($transaction);
$result = $robot->save();

$transaction->rollback();

これでRollbackできたので、登録失敗した場合にrollback()、成功した場合はcommit()するようにすればOK。

ちなみにMariaDB(MySQL)は、RollbackしてもAUTO_INCREMENTの値はRollbackされないので、連番ではなくなります。
2のデータ登録しようとした際にRollbackされたとすると、「1,3,4」みたいにデータができるので要注意。