2016年12月22日木曜日

CakePHP3 ファイルアップロード -- file upload --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


 ■ 前提
  自前でファイルアップロードを作りたい方向けに記述しています。
  CakePHP3 では、有名なファイルアップロード関連のプラグインがありますので、プラグインでもいいって方は、そちらの方が良いかもしれません。
一応、一つご紹介

cakephp-simple-upload

-----------------------------------------------

では、本題へ。
Controllerに実装することもできるのですが、それでは美しくないってことで、ModelクラスのbeforeSaveというメソッドに処理させます。
beforeSaveメソッドについては、こちらを 参照

 == Model ==
     public function beforeSave($event, $entity, $options)
    {
        // entityにファイルが指定されているかどうか
        if ($entity->has('upload_file')) {
            $upload_file = $entity->get('upload_file');
            // ファイルが正しくアップロードされているか
            if ($upload_file['error'] === UPLOAD_ERR_OK && $upload_file['size'] !== 0) {
              
                // ファイルを保存する
                $file = new File($upload_file['tmp_name']);
                $file->copy('/example/dest/upload_file');
            } else {
                // ファイルが指定されていなかった場合、上書きされてしまわないようにする
                $entity->unsetProperty('upload_file');
            }
        }
    }

今回はDB保存する直前に動作するbeforeSaveで処理しましたが、インクリメントされたIDをファイル名に入れたいなど思われる方は、afterSaveメソッドで処理させると良いかもしれません。

最後に問題が、この指定方法だとファイルアップロードに失敗した場合のエラーハンドリングがうまくできないんですよね。
どなたかご教授頂けると幸いです。


 ===================================================

 ■ Premise

  It is written for those who want to make file upload on their own.

  In CakePHP 3, there is a famous plugin related to file uploading, so if you can plugin it may be better there.

  Once, one introduction

cakephp-simple-upload

-----------------------------------------------

 Then, to the main subject.

 It can also be implemented in Controller, but let's say that it is not beautiful and that it is handled by the Model class beforeSave method.

 For the beforeSave method, see here

 == Model ==

     public function beforeSave($event, $entity, $options)
    {
        // Whether a file is specified for entity
        if ($entity->has('upload_file')) {
            $upload_file = $entity->get('upload_file');
            // Is the file uploaded correctly?
            if ($upload_file['error'] === UPLOAD_ERR_OK && $upload_file['size'] !== 0) {
               
                // Save the file
                $file = new File($upload_file['tmp_name']);
                $file->copy('/example/dest/upload_file');
            } else {
                // Do not overwrite if file is not specified
                $entity->unsetProperty('upload_file');
            }
        }
    }


 This time it processed with beforeSave which operates just before saving the DB, but if you think that you want to include the incremented ID in the file name, it may be better to process it with the afterSave method.

 Finally, if the problem is this specification method, error handling can not be done well if the file upload fails, right?

 I hope someone can take a professor.




2016年10月27日木曜日

cakephp3 phpunit 利用時に使える便利なmethodなどについて

PHP Ver 5.6.27
CakePHP Ver 3.1.9


公式ページを見れば記載があるが、少しわかりにくいのでPHPUnitで利用されるファイルやメソッドについて書いておく。


Fixtures:
実際のアプリケーションに使われているデータを破壊することなく テストができる
 ・各フィクスチャで必要なテーブルを作成します
 ・フィクスチャにデータが存在すれば、それをテーブルに投入します
 ・テストメソッドを実行します
 ・フィクスチャのテーブルを空にします
 ・データベースからフィクスチャのテーブルを削除します

記述例(xxxControllerTest.php)
---------------------------------------------------
public $fixtures = [ 'app.user', 'app.article' ];
---------------------------------------------------


setUp method:
テストメソッドの前に毎回呼び出されます
テストされるオブジェクトの生成や、テストのためのデータの初期化に使われます
メールなどの設定はここに記述すると便利
※parent::setUp() を呼び出すことを忘れないでください

記述例(xxxControllerTest.php)
---------------------------------------------------
public function setUp() {
  parent::setUp();

  Email::dropTransport('default');
  Email::configTransport('default', [ 'className' => 'Debug', ]);
}
---------------------------------------------------


tearDown method:
テストメソッドの後に毎回呼び出されます
テストが完了した後のクリーンアップに使われます
※parent::tearDown() を呼び出すことを忘れないでください

記述例 (xxxControllerTest.php)
---------------------------------------------------
public function tearDown() {
  parent::tearDown();
}
---------------------------------------------------


setupBeforeClass method:
クラスのテストメソッドを実行する前に一度だけ呼ばれます
※このメソッドは static でなければなりません

記述例 (xxxControllerTest.php)
---------------------------------------------------
public static function setupBeforeClass() {
  parent::setupBeforeClass();
}
---------------------------------------------------


tearDownAfterClass method: 
クラスのテストメソッドをすべて実行した後に一度だけ呼ばれます
※このメソッドは static でなければなりません

記述例 (xxxControllerTest.php)
---------------------------------------------------
public static function tearDownAfterClass() {
  parent::tearDownAfterClass();
}
---------------------------------------------------

2016年10月13日木曜日

cakephp3 pagenation & meta tag

PHP Ver 5.6.27
CakePHP Ver 3.1.9


ページネーション処理は、Cakephpのデフォルトで準備がされている

まずは、ページネーション使いますよとAppViewのinitializeメソッドで設定

---------------------------------------------------

$this->loadHelper('Paginator');

---------------------------------------------------


Controllerのinitializeメソッド内などで、ページネーションの詳細設定を行う

---------------------------------------------------

$this->loadComponent('Paginator');
$this->paginate = [
                'limit' => 10
        ];

---------------------------------------------------


limitなどはロードした後に設定

では実際に使ってみる
DBから取得してきたqueryをページネーションを使って表示

---------------------------------------------------

$this->set('articles', $this->Paginator->paginate($article->getArticlesQuery(), $this->paginate));

---------------------------------------------------


その後Viewでは気にせず普通にイテレーションしてあげればよい
但し、以下の表示は入れてあげないとページ番号が出てこないので注意

---------------------------------------------------

<?= $this->Paginator->numbers() ?>

---------------------------------------------------

ここで記述した内容はあくまで一例で、参考程度に。

詳しくは、以下参照

http://book.cakephp.org/3.0/ja/controllers/components/pagination.html


備考
rel=prevやrel=nextも自動でやってくれる
Viewファイル内で以下のようにheadタグの中に記述

---------------------------------------------------

<?= $this->Paginator->meta() ?>

---------------------------------------------------

これだけ。便利ですね~

2016年9月30日金曜日

cakephp3 Trimについて

PHP Ver 5.6.27
CakePHP Ver 3.1.9


入力値の前後にある全角/半角スペースを取り除きたい

cakephp3 の Validation では全角スペースだけが入力された場合などは、notEmpty を指定していても、
普通に通ってしまう。

ということで Trim しなければならないのですが、どこのタイミングで行うか。

調べた結果、公式に載っていました。
(http://book.cakephp.org/3.0/ja/orm/saving-data.html#before-marshal)

テーブルまたはビヘイビアクラスの beforeMarshal メソッドで行いなさいと。
 
タイミングはこれでOK。
しかし、ここに記載されている通り デフォルトのメソッド Trim だけを行ってしまうと
全角スペースは対応できない。

ってことで、全角スペースにも対応した Trim メソッドを作成することに。

----------------------------------

    public static function mb_trim($str) {
        return trim(preg_replace('/[  ]+$/u', '', preg_replace('/^[  ]+/u', '', $str)));
    }
----------------------------------
 
これで良いかと思いきや、半角スペース - 垂直タブ - 全角スペースみたいな入力を
されてしまうと対応できないんですね・・・
対応が難しそうなので、このメソッドは暫定でこのままにしておいて、後々考えようと
思います。 

2016年9月23日金曜日

cakephp3 開発機と本番機でDBの向き先を変える -- custom db connection --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


cakePHPでは、Unit用と通常利用するDBの設定が別々に設定されている。

しかし、一般的には開発機と本番機は別なので、本番機にアップする際に、いちいち設定を変更しないといけない。
そいつはミスする元凶になりそうだし、面倒だなぁ

ってことで、Unit用、開発機用、本番機用の3種類設定を行い、自動的に切り替えるようにしようと思い立つ。

既存のdefault・test以外にDBの向き先を変える方法

まずは、app.phpに設定を加える(ここでは開発機用の設定をdivとして追加)

通常はこんな感じ 
----------------------------------------------
'Datasources' => [

    'default' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => '本番機',
        'log' => false, 'quoteIdentifiers' => false,
    ],
    'test' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => 'localhost',
        'log' => false,
    ],
],
----------------------------------------------

これに以下の内容を追加する

----------------------------------------------
     'div' => [
        'className' => 'Cake\Database\Connection',
        'driver' => 'Cake\Database\Driver\Mysql',
        'persistent' => false,
        'host' => '開発機',
        'log' => false,
    ],
----------------------------------------------

で、切り替える様にコードを改修
 通常Modelの各Tableクラスは

Cake\ORM\Table

を継承しているけど、CustomTableみたいなクラスを作成してModelの各Tableクラスはこの新しく作成したクラスを継承して、CustomTableクラスで既存のTableクラスを継承する。
(要は、間に一枚かますってこと)

で、切り替えを行うために、既に準備されている”defaultConnectionName”というメソッドで向き先を設定してあげる。

と、新しく作成するファイルはこんな感じになる 
----------------------------------------------
class CustomTable extends Table  {

    public static function defaultConnectionName() {
        if (Configure::read('debug')) {
            if (Unitだったら) {
                return 'test';
            } else {
                return 'div';
            }
        } else {
            return 'default';
        }
    }
}
----------------------------------------------

これで、設定ファイルをいちいち設定しなくても良くなる

しかし、const.phpのdebug設定は都度変えないといけない。
その対応は後々考えることに・・・

2016年9月15日木曜日

Google Translateを使用してサイトを多言語化 -- use Google Translate For Localization --

  • Google Translate 登録画面へアクセス(事前にGoogleアカウント取得)
    • https://translate.google.com/manager/website/
    1. 「今すぐウェブサイトに追加」 ボタン押下
    2. ウェブサイトの URLを入力
    3. ウェブサイトの言語を選択
    4. 「次へ」 押下
    5. 翻訳する言語を選択
      1. すべての言語を選択すると一般的に使われない言語が多いので、言語を指定を選ぶことをおすすめ
    6. 表示モード選択
    7. 詳細設定選択
    8. 「コードを取得」 ボタンを押下 
    9. 表示されたHTMLをWebサイトに貼り付け

2016年8月30日火曜日

cakephp3 Cache

PHP Ver 5.6.27
CakePHP Ver 3.1.9


画像やJavascript、CSSをキャッシュしたい場合は、以下のように設定

app.php
-------------------------------------------
    'Asset' => [
        'timestamp' => true, // 画像,JS,CSSのキャッシュを無効にする(デバッグモードのときのみ)
    ],

-------------------------------------------
デバッグモード時に限らず、常にクエリストリングを付けたい場合 : 'force'

2016年8月2日火曜日

Cakephp3 での例外スロー時のPHPUnit -- cakephp3 phpunit throw Exception --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


例外をスローした場合のUnitTestの比較方法を色々と調べてみたが、

------------------------------------------
setExpectedException(InvalidArgumentException)
------------------------------------------

私の環境では、この方法だとなぜか上手くいかない。

try catchでの方法も試してみたが、これも上手くいかない。

なので、少し力技ではあるが、以下のようにした。

------------------------------------------
$this->assertInstanceOf('InvalidArgumentException', $this->_exception);
$this->assertEquals('Invalid Argument', $this->_exception->getMessage());
------------------------------------------


注意:
NotFoundExceptionなどはフルpathで指定しないといけない。
------------------------------------------
$this->assertInstanceOf('Cake\Network\Exception\NotFoundException', $this->_exception);

cakephp3 多言語化 -- localization --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


文字変換用ファイル作成

・src/Locale/{Lang}/default.poを作成
  {Lang}は日本語なら”ja_JP”、英語なら”en_US”

・変換文字列を記載
  msgid "{変換前の文字列}"
  msgstr "{変換後の文字列}"

例:
  msgid "Tweet"
  msgstr "ツイート"

・ini_setで言語の指定
  ini_set('intl.default_locale', 'ja_JP');

これで文字列が変換される。

但し、default.poファイルはキャッシュされるので、追記や変更を行った場合は、tmp/cacheディレクトリを削除するのをお忘れなく。

2016年7月16日土曜日

Google API Client について

Google API Client 1系では、Google_Auth_AssertionCredentials というクラスを利用するが、2系からは使い方が変わったらしい。

資料もあまりないので、自分でドキュメント調べて、実装しようと試みたが、私の利用しているレンタルサーバーはSSLに対応していないので、そもそも利用できなかった。

実装する前に、ちゃんと調べるべきだった。。。

2016年7月1日金曜日

cakephp3 phpunit利用時にメールを飛ばさないようにする方法 -- do not mail send to use phpUnit --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


SetUpメソッドに以下の内容を記載

------------------------------------------------
    public function setUp()
    {
        parent::setUp();

        Email::dropTransport('default');
        Email::configTransport('default', [ 'className' => 'Debug', ]);
    }
------------------------------------------------
※parent::setUp() を呼び出すことを忘れずに


cakephp3 get Controller Name / Action Name

PHP Ver 5.6.27
CakePHP Ver 3.1.9


いつも忘れちゃうので、備忘録として

  • Controller Name
    • ControllerまたはViewで$this->name

  • Action Name
    •  ControllerまたはViewで$this->request->action

2016年6月29日水曜日

cakephp3 subdirectory .htaccess edit

PHP Ver 5.6.27
CakePHP Ver 3.1.9



 レンタルサーバー等を利用した場合等にサブドメインをサブディレクトリに指定することがありますよね。
 その場合には、 以下の設定を変更しないとcakephp3がうまいこと動きません。
 ただ、これだけだけど数時間はまった・・・

  • レンタルサーバー利用時に変更しなければならない.htaccessのmod_rewirte設定
    • app/.htaccess
      • RewriteBase / を追加
    • app/webroot/.htaccess
      •  RewriteBase / を追加


2016年6月28日火曜日

cakephp3 sitemap plugin install

PHP Ver 5.6.27
CakePHP Ver 3.1.9


サイトマップを準備する

cakephp3 にはサイトマップ用のプラグインが準備されているので、利用する。

・インストール
  $ cd app/plugins/
  $ git clone https://github.com/fm-labs/cakephp3-sitemap Sitemap

・Bootstrap.phpにプラグインをロードする内容を追記
--------------------------------
    Plugin::load('Sitemap', ['autoload' => true, 'routes' => true]);
--------------------------------

・SitemapController.phpの作成
--------------------------------
<?php
namespace App\Controller; 

use App\Controller\AppController;

class SitemapController extends AppController
{
    public function initialize()
    {
        $this->loadComponent('Sitemap.Sitemap');
    }

    public function index()
    {
        $this->Sitemap->createIndex();

        $this->Sitemap->create();

            $this->Sitemap->addLocation(
                    ['controller' => '[ControllerName]', 'action' => '[ActionName]', $id], // url
                    1, // priority
                    $value->modified, // last modified date
                    'hourly' // change frequency
            );
    }
}
--------------------------------

Controllerの内容はapp/plugins/Sitemap/src/Controller/SitemapController.phpに記述されているので参考になります。

2016年6月27日月曜日

SEO対策 keyword 選定 tool

SEO対策 KeyWord選定ツール

  ブログ検索
    Ritlweb: http://blog.ritlweb.com/



  キーワード検索数予測ツール
    Rishirikonbu: http://rishirikonbu.jp/





    キーワードウォッチャー: https://www.keywordwatcher.jp/


    Uber: https://ubersuggest.io/


  キーワード検索数調査ツール
    Keyword Tool: http://keywordtool.io/

    GoodKeyword: http://goodkeyword.net/

    Freet+: http://tool.ferret-plus.com/

    SEOチェキ: http://seocheki.net/

    Ranking Checker: http://broadentry.com/rankingchecker/

    Bull: http://bullseo.jp/


  キーワード作成支援ツール
    Google Keyword Planner: https://adwords.google.com/ko/KeywordPlanner/Home


2016年5月27日金曜日

cakephp3 admin prefix routingの罠

PHP Ver 5.6.27
CakePHP Ver 3.1.9


cakephp admin prefix routingの罠
・管理画面を作成する場合などControllerをネストする場合は注意!

http://xxx.com/admin/ はsrc/Controller/{dirName}/xxxxController.phpというような
Controllerを作成するが、この{dirName}が曲者。。。

例えば 'Admin' というような名前でディレクトリを作成した場合
XAMPPを利用した場合等、Windows環境では正しく認識されるが、Linux環境では
ディレクトリ名が小文字の 'admin' としないと動作しない
原因は調べられていないが、ディレクトリ名はすべて小文字にしないといけないらしい

MissingControllerエラーが発生するのだが、そのエラーメッセージでは
src/Controller/Admin/xxxxController.phpを作成しろと言われる
しかし、ディレクトリ名が小文字でないと動作しない
バグなのかな?

相当、はまった・・・

cakephp3 how to add intl module

PHP Ver 5.6.27
CakePHP Ver 3.1.9


ディレクトリ作成
$ mkdir -p local/src
ICUインストール
intlインストール時に必要

$ cd local/src/
$ wget http://download.icu-project.org/files/icu4c/54.1/icu4c-54_1-src.tgz
$ tar xzvf icu4c-54_1-src.tgz
$ cd icu/source/
$ ./configure --prefix=$HOME/local
$ gmake
$ gmake install
intlインストール
$ wget http://pecl.php.net/get/intl-3.0.0.tgz
$ tar xzvf intl-3.0.0.tgz
$ cd intl-3.0.0
$ phpize
$ ./configure --with-icu-dir=$HOME/local
$ make
$ cd ~/local/
$ mkdir php
$ mkdir php/extension
$ cd php/extension/
$ cp ~/local/src/intl-3.0.0/modules/intl.so .
$ php -i | grep intl

make install した方が良いと思う

2016年5月5日木曜日

cakephp mysql tinyintの罠

PHP Ver 5.6.27
CakePHP Ver 3.1.9


cakephp mysql tinyintの罠

・cakephpでmysqlのtinyint型を利用するときは要注意!
tinyint(1)と設定した項目はbool型として認識される。

$this->request->dataでは正しくPOSTされてくる、
しかし、patchEntityをするとbool型になる。
何をやっても結果は一緒・・・

これはbugではないのか?とcakephp側のコードを追う
散々調べた結果、cakephpでのtinyint(1)はvarchar型などのbyteと違い、bitと認識されるらしい
相当、はまった・・・
とりあえず、tableをcreateするときに指定なしでtinyintとすることにした

2016年4月24日日曜日

cakephp3 ページネーション -- Pagination method --

PHP Ver 5.6.27
CakePHP Ver 3.1.9



cakephp3 ページネーション (Cake\View\Helper\PaginatorHelper)


利用できるメソッド一覧

基本的には以下のように利用する。
$this->Paginator->{methodName}

・templates($templates = null)
    $templates = [
        'nextActive' => '< li class="next">< a rel="next" href="{{url}}">{{text}}< /a>< /li>',
        'nextDisabled' => '< li class="next disabled">< a href="" onclick="return false;">{{text}}< /a>< /li>',
        'prevActive' => '< li class="prev">< a rel="prev" href="{{url}}">{{text}}< /a>< /li>',
        'prevDisabled' => '< li class="prev disabled">< a href="" onclick="return false;">{{text}}< /a>< /li>',
        'counterRange' => '{{start}} - {{end}} of {{count}}',
        'counterPages' => '{{page}} of {{pages}}',
        'first' => '< li class="first">< a href="{{url}}">{{text}}< /a>< /li>',
        'last' => '< li class="last">< a href="{{url}}">{{text}}< /a>< /li>',
        'number' => '< li>< a href="{{url}}">{{text}}< /a>< /li>',
        'current' => '< li class="active">< a href="">{{text}}< /a>< /li>',
        'ellipsis' => '< li class="ellipsis">...< /li>',
        'sort' => '< a href="{{url}}">{{text}}< /a>',
        'sortAsc' => '< a class="asc" href="{{url}}">{{text}}< /a>',
        'sortDesc' => '< a class="desc" href="{{url}}">{{text}}< /a>',
        'sortAscLocked' => '< a class="asc locked" href="{{url}}">{{text}}< /a>',
        'sortDescLocked' => '< a class="desc locked" href="{{url}}">{{text}}< /a>',
    ];

・sort($key, $title = null, array $options = [])
    $key(string)
    $title(string)
    $options = [
        'url' => [],
        'model' => 'defaultModel',
        'escape' => true,
    ];

・sortDir($model = null, array $options = [])
    $model = {modelName}
    $options = [
        'url' => [
            'sort' => 'email',
            'direction' => 'desc',
            'page' => 6,
            'lang' => 'en',
        ]
    ];

・sortKey($model = null, array $options = [])
    $key(string)
    $options = [
        'url' => [
            'sort' => 'email',
            'direction' => 'desc',
            'page' => 6,
            'lang' => 'en',
        ]
    ];

・numbers(array $options = [])
    $options = [
        'before' => {numbers},
        'after' => {numbers},
        'model' => 'defaultModel',
        'modulus' => 8,
        'first' => 'First page',
        'last' => 'Last page',
        'templates' => [
            'active' => 'nextActive',
            'disabled' => 'nextDisabled',
        ],
        'url' => [],
    ];

・prev($title = '<< Previous', array $options = [])
    $title(string)
    $options = [
        'disabledTitle' => {title},         'url' => [],
        'model' => 'defaultModel',
        'escape' => true,
        'templates' => [
            'active' => 'nextActive',
            'disabled' => 'nextDisabled',
        ]
    ];

・next($title = 'Next >>', array $options = [])
    $title(string)
    $options = [
        'disabledTitle' => {title},         'url' => [],
        'model' => 'defaultModel',
        'escape' => true,
        'templates' => [
            'active' => 'nextActive',
            'disabled' => 'nextDisabled',
        ]
    ];

・first($first = '<< first', array $options = [])
    $first(string or int)
    $options = [
        'url' => [],
        'model' => 'defaultModel',
        'escape' => true,
    ];

・last($last = 'last >>', array $options = [])
    $last(string or int)
    $options = [
        'url' => [],
        'model' => 'defaultModel',
        'escape' => true,
    ];

・counter($options = [])
    $options = [
        'model' => 'defaultModel', // デフォルトではPaginatorHelper::defaultModelが呼ばれる
        'format' => 'range' or 'pages' or custom
            custom example: 'Page {{page}} of {{pages}}, showing {{current}} records out of
                {{count}} total, starting on record {{start}}, ending on {{end}}'
        ];

・current($model = null)
    $model = {modelName}

・hasNext($model = null)
    $model = {modelName}

・hasPage($model = null, $page = 1)
    $model = {modelName}
    $page = int
・hasPrev($model = null)
    $model = {modelName}

・options(array $options = [])
    $options = [
        'url' => [
            'sort' => 'email',
            'direction' => 'desc',
            'page' => 6,
            'lang' => 'en',
        ]
    ];

・generateUrl(array $options = [], $model = null, $full = false)
    $options = [
        'page' => null,
        'sort' => null,
        'direction' => null,
        'limit' => null,
    ];
    $model = {modelName}
    $full = boolean

・meta(array $options = [])
    $options = [
        'model' => 'defaultModel', // デフォルトではPaginatorHelper::defaultModelが呼ばれる
        'block' => false or {blockName},
    ];

2016年4月22日金曜日

cakephp3 バリデーション(4)
-- validation of cakephp3 --

PHP Ver 5.6.27
CakePHP Ver 3.1.9


Model

・バリデーションを行いたくない場合

$this->newEntity()、$this->patchEntity()を行うとバリデーションが行われてしまうため、
$this->patchEntity($entity, $this->request->data, ['validate' => false] )とすると
バリデーションを行わない。

但し、これ以外にもバリデーションが行われるメソッドがあるので、そこについては要調査