cakephp3 で更新の後、2つ前のページ(=つまり編集画面に来る前)

cakephp3 で更新の後、2つ前のページ(=つまり編集画面に来る前)

A.一覧画面(もしくは他の画面) → B.編集画面(編集)更新ボタン押す → C.edit()アクションで更新、Aに戻す。

というふうなことを普通にBakeでCRUDでつくるとIndexに戻すだけでOKなのですが、別の不特定多数のペ0−字へ戻す場合の話。

A > B > Cの時点での referer()は、Bとなっていて同じページなってしまう。そこで実質2つ前に戻してたい。  解決砲。ControllerのafterFileterで2つ前のページをSessionに入れておく。

まずAppControllerにafterFileterを追加。(beforeFileterの逆で書くActionの後に実行される)

public function afterFilter(Event $event)
{
    $this->Session = $this->request->session();
    $this->Session->write('referer2',$this->referer());
    return parent::afterFilter($event);
}

とすることで、Cのedit()アクション時点では referer2が更新前で、2つ前のAのアドレスがある。
のでedit()アクションで更新の後以下のようにすればできた。

return $this->redirect($this->Session->read('referer2'));

cakephp3 データの削除(関連などの削除方法)

cakephp3 データの削除(関連などの削除方法)

  • 基本的な削除方法。エンティティーを送る。
$order = $this->Order->get(3);
$this->Order->delete($entity);
  • まとめて削除はconditionsを指定する
    ※delteAllは afterDeleteとかのCallbackが呼ばれない。
$this->Order->deleteAll(['status_id'=>3])
  • 関連テーブルを消す。
    関連元のhasManyオプションで指定。(deleteAllで消される)
        $this->hasMany('OrderDetails', [
            'foreignKey' => 'order_id',
            'dependent' => true
        ]);
  • 関連テーブルを消す。
    関連元のhasManyオプションで指定。afterDeleteとかのコールバックも呼びたい
        $this->hasMany('OrderDetails', [
            'foreignKey' => 'order_id',
            'dependent' => true,
            'cascadeCallbacks'=>true
        ]);

参考 :
https://book.cakephp.org/3.0/ja/orm/deleting-data.html

cakephp3で関連の関連(cakephp2でのrecursiveの深い所取得)

cakephp3で関連の関連(cakephp2でのrecursiveの深い所取得)

関連を取得する場合は、[‘contain’]オプションで指定。

$order = $this->Orders->get($id, [
            'contain' => [ 'OrderDetails']
]);

関連のさらに関連まで取得の場合は配列オプションをつなぐ。

$order = $this->Orders->get($id, [
            'contain' => [ 'OrderDetails' =>[
                'Products'
            ]]
]);

余分なもの取得せずに、見た目にスッキリ直感的になった。

MAMPのmySQLをコマンドラインから使うときにソケットファイルが無い場合。

MAMPのmySQLをコマンドラインから使うときにソケットファイルが無い場合。

ソケット(mysql.sock)がApplication/MAMP以下にあるのでリンク貼るある。

ln -s /Applications/MAMP/tmp/mysql/mysql.sock /tmp/mysql.sock

cakephp3 でエンティティー(Entity)からエラーの取得と設定

cakephp3 でエンティティー(Entity)からエラーの取得と設定

エラーの取得

$product = $this->Products->newEntity([
    'id' => null,
    'name'=>'商品名',
    'supplier_id'=>null,
    'price'=>null
]);

$product->getErrors(); //全てのエラー
$product->getError('supplier_id');//フィールド指定

エラーの設定

$orderDetail->setError('name',[
    'hogehoge'=>'テストのエラーです。'
]);
//設定したエラー内容は以下になります。
[
    'name' => [
        'hogehoge' => 'テストのエラーです。'
    ]
]

cakephp3でExcelを読んだり書いたりするプラウグイン(robotusers/cakephp-excel)を使ってみた。

結果をいうととてもシンプル使いやすくて○。

  1. コンポーザーでインストール
$ composer require robotusers/cakephp-excel

bootstrap.php最後に追記

$ bin/cake plugin load Robotusers/Excel -b

どこか使うところでuse

use RobotusersExcelRegistry;

あとは読み込んでentityとして取得できました。

$table = $registry->get('/your/excel/path/filename.xlsx');
$rows = $table->find()->all();
foreach ($rows as $key => $row) {
    debug($row);
}
exit;

あと便利そうなオプション、
開始行を指定、開始列指定、列名(=フィールド名)を変換するマップとか。


$table = $registry->get($xls,null, []);
“`

cakephp3で画像(など)をアップロードするプラグインUpload Plugin 3.0を使ってみる。

cakephp3でプラグインいろいろ選んでいたけど一番余分なものいらなくシンプルに使えそうなUpload Plugin 3.0を使ってみました。

Upload Plugin 3.0
https://github.com/FriendsOfCake/cakephp-upload

必要環境 / ライセンス

  • CakePHP 3.x
  • PHP 5.4+
  • The MIT License (MIT)

ドキュメント

(ブログ記事シンプルで判りやすい)

使ってみた

  1. まずプロジェクトのルートでコンポーザーのコマンドでインストール
    composer require josegonzalez/cakephp-upload

  2. cakeコマンドでbootstrap.phpに追記
    bin/cake plugin load Josegonzalez/Upload

  3. テーブルに photo,dir フィールドを追加(varchar(255)

  4. モデルクラスにプラグイン利用の記述を追記

public function initialize(array $config)
{
        $this->addBehavior('Josegonzalez/Upload.Upload', [
            'photo' => [
                'path' => 'static{DS}{model}{DS}{field}{DS}'
                'nameCallback' => function ($data, $settings) {
                    return uniqid().'-'.strtolower($data['name']);
                }
            ],
            
        ]);
  1. フォームタグのcreateに ‘type’=>’file’追加。
<?= $this->Form->create($product, ['type' => 'file']) ?>
  1. inputタグを追加
<?= $this->Form->control('photo', ['type' => 'file']); ?>
  1. webroot/file ディレクトリを作成(要書き込みパーミッション)

  2. いざアップロード
    無事アップロードできました。


すこしだけハマった所。

最初ビヘイビアの記述に以下のようにフィールド名のみを書いていると

$this->addBehavior('Josegonzalez/Upload.Upload', [
        'photo'
]);

タイプがダメよとエラーが。

Invalid data type, must be an array or ArrayAccess instance. 

アップロードのファイルタイプと一瞬おもったけど。これはプラグインがオプションを拾うところで、Array を期待するがStringなのでエラーになっていた。
以下のように空のArrayにしたらOKだった。

$this->addBehavior('Josegonzalez/Upload.Upload', [
        'photo' => []
]);

オプションのpathを記述した場合に{primaryKey}がAdd(新規)で使えなく断念した

        $this->addBehavior('Josegonzalez/Upload.Upload', [
            'photo' => [
                'path' => 'static{DS}{model}{DS}{field}{DS}{primaryKey}'
            ]
        ]);

これでちゃんと写真が取得できるようになった。

object(AppModelEntityProduct) {

    'id' => (int) 1,
    'supplier_id' => '1',
    'status_id' => (int) 11,
    'name' => 'テストセット',
    'description' => 'テスト',
    'unit_price' => (int) 1304,
    'photo' => '008.jpg',
    'dir' => 'static/Products/photo/1',
    'note' => '',
    'created' => object(CakeI18nFrozenTime) {

Cakephp3で商品の親子構造、多対多のセット商品などのテーブル設計

商品(りんご、ばなな、メロン)テーブルがあって、単品の商品とかセット商品とかをうまく組み合わせたい。
例えば。。

フルーツ盛り合わせ(りんごx1、ばななx2、メロンx1) とか

りんご盛り(りんごx20) とか

などなどとにかく複雑なものがあとから来ても大丈夫な設計を考えて、以下のような感じにしてみた。

数量のnumがポイント

数量のnumがポイント

productsテーブルをproductsテーブル自身と多対多で繋いで中間のテーブルはproducts_componentsとするで、数量は中間テーブルに ‘num’ として持っていたら何にでもなりそうな気がする。

具体的なCakePHPの話。

Modelは ProductsTable.phpを中心に以下のアソシエーションを貼る。

[Products]
    belongsToMany :  [Components]
    hasMany : [ProductsComponents]

ProductsTable

以下のように2つのアソシエーションを指定。

        $this->hasMany('ProductsComponents', [
            'className'=>'ProductsComponents',
            'foreignKey'=>'product_id'
        ]);

        $this->belongsToMany('Components', [
            'joinTable' => 'products_components',
            'className'=>'Components',
            'foreignKey'=>'product_id',
            'targetForeignKey' => 'component_id'
        ]);

(細かい個別の条件によるとおもうのですが、途中はまって’targetForeignKey’ が無いとうまく動かないケースがあって入れてますが実際はいらないかも><..)

belongsToMany : Components

これは’Components’って名前をつけているけど実態は ’Products’テーブルで、
Productsが、ProductsをbelongsToManyとするとおかしくなったので別名に。
実態はこんな感じ。

class ComponentsTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);
        $this->setTable('products');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->belongsToMany('Products', [
            'joinTable' => 'products_components',
            'className'=> 'Products',
            'foreignKey'=>'component_id',
            'targetForeignKey' => 'product_id'
        ]);
    }
}

hasMany : ProductsComponents

これは普通に定義のみ(あとでバリデーションとかetc入れる予定)

<?php
class ProductsComponentsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);
        $this->setTable('products_components');
        $this->setPrimaryKey('id');
    }
}

フォームとコントローラーでの保存とか。

基本belongsToMany(多対多)のばあいは、FormHelperでチェックボックスがだせるのですが、numを入力させたりがあるのでフォームは直に書いて行きます。

まずは belongsToManyのComponents用のフォーム
これはCakePHPが書き出すものを真似る感じで。最初のvalueが空のはチェックが無かった場合に値がUndefindにならない用。

<input type="hidden" name="components[_ids]" value="">
<input type="hidden" name="components[_ids][]" value="1"> アイテム1
<input type="hidden" name="components[_ids][]" value="2"> アイテム2
<input type="hidden" name="components[_ids][]" value="3"> アイテム3

で、もう一つそれぞれに対してnum用のフィールドが必要で、以下のように、‘productsComponents’ で指します。(と言うのは、上の多対多はProductsテーブルのsaveでまとめて保存できたが、中間テーブルに保存するnumがsaveの中では、一回で保存できなかったため、分けています。(afterSaveとかでどうにかとも考えたのですが、Controllerに書いて見通しが良いほうが簡単そうだったので。)

<input type="text" name="productsComponents[{component_id}][num]" value="1">個

component_id の部分は、セットに含まれるproductsのid (=componentsテーブルのid)
で、2つのフォームを合わせて以下のようにフォームを生成。

<input type="hidden" name="components[_ids]" value="">
<input type="hidden" name="components[_ids][]" value="1"> りんご x 
<input type="text" name="productsComponents[1][num]" value="1">個

保存のとこ。(Controller)

基本的には belongsToMany : Componentsのところは何も考えず、Productsをsave()すれば勝手に保存される。(削除してのも消してくれる)

で、中間テーブルのnumの保存方法は一旦Productsを保存してそのあと中間テーブルのnumを保存する手順にしています。

 $product = $this->Products->patchEntity($product, $this->request->getData(),['associated' =>  ['Suppliers','Statuses','Components']]);
            $connection = ConnectionManager::get('default');
            $connection->begin();
            if ($this->Products->save($product)) {
                if(!empty($product->productsComponents)){
                    foreach($product->productsComponents as $component_id => $item){
                        $query = $this->Products->ProductsComponents->query();
                        $query->update()
                            ->set(['num' => (int)$item['num']])
                            ->where(['product_id' => $id,'component_id'=>$component_id])
                            ->execute();
                    }
                }
                $this->Flash->success(__('The product has been saved.'));
                $connection->commit();
                return $this->redirect(['action' => 'index']);
            }else{
                //error
            }

CakePHP3で定数とかを設定してJSでも使いまわしたりCSSで利用したりしてみる。

Cakephpで作っていて、いろいろController/ActionでCSSやJavascriptをうまく使えないかと思って、定数 > JS/HTMLのClass名へ落とし込んでみました。(なにかもっとスマートにできそうな気もします。)

まずAppController.phpで。Routerをuse

use CakeRoutingRouter;

config以下とか、どこかでやったほうが良いとか有りますが、
action,controller名なども加えてまとめたくこの場所。

public function initialize()
{
    parent::initialize();
    $this->loadComponent('RequestHandler');
    $this->loadComponent('Flash');

    define('FULL_BASE_URL',Router::fullBaseUrl());
    define('CONTROLLER_NAME',strtolower($this->name));
    define('ACTION_NAME',strtolower($this->request->action));

で、HTML側
JavaScriptの定数用してと、bodyクラスにconntroller-action名を割当

    <script>
        var FULL_BASE_URL = '<?= h(FULL_BASE_URL) ?>';
        var CONTROLLER_NAME = '<?= h(CONTROLLER_NAME) ?>';
        var ACTION_NAME = '<?= h(ACTION_NAME) ?>';
    </script>
</head>
<body class="<?= h(CONTROLLER_NAME.'-'.ACTION_NAME) ?>">

full_base_urlは jsでも取れるとか気になりますが、見通しがよくなるようにまとめてみました。

cakephp3でのトランザクション

利用するコントローラーでまずConnectionManagerをuse;

use CakeDatasourceConnectionManager;

Cakephp2と基本的には同じく、begin()で初めて、処理が終わってcommit() 、例外時は rollback()。

$connection = ConnectionManager::get('default');
$connection->begin();

if ($this->Products->save($product)){
    $connection->commit();  //OK時
}else{
    $connection->rollback();    //エラー時
}

commit()を行わずに終了した場合は、保存されなかった。
(2017.10.03現在の ver3.5.0にて確認)

Page 16 of 46

Powered by WordPress & Theme by Anders Norén