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にて確認)

バーコードについてJANの基本とPHPのライブラリを調べてみたのでメモ。

バーコードには1次元バーコード(お菓子とかパン缶詰とかについている俗に言うバーコードハゲ的な者)
2次元バーコードとは、QRコード等、横方向だけでなく縦にも情報を持つもの総称

今回は、業務上1次元バーコードについて調べた。

代表的な種類

JAN(EAN、UPC) CODE39 CODE128 NW–7 ITF   
文字 数字0–9 数字と、アルファベット/記号は一部 数字/アルファベット/記号(フルASC) 数字と、アルファベット/記号は一部 数字0–9
桁数 13 or 8 可変 可変 可変 14 or 16

今回使うJANの種類3種類

標準タイプ13桁 標準タイプ13桁 短縮タイプ8桁
詳細 2001年1月以後の登録 2000年12月以前の登録 小さい物用

今後使う2001年1月以降のタイプついてのみ詳細

説明 GS1事業者コード(9桁) 商品アイテムコード(3桁) チェックデジット(1桁)
コード例 458240192 927 3
設定者 流通システム開発センターが付与 事業者にて設定 計算式で算出

PHPにて画像生成するのでライブラリなど調査

githubでスターの一番多かった上のもので大丈夫かな。ライセンスあGNU

名称 url ライセンス、料金など
PHP Barcode Generator https://github.com/picqer/php-barcode-generator GNU Lesser General Public License v3.0
php-barcode https://github.com/rlt3/php-barcode/ MIT License
Barcode.php http://www.pao.ac/barcode.php/ 21,600
Image_Barcode2 https://pear.php.net/package/Image_Barcode2/download PHP License
PHP-Barcode https://ashberg.de/php-barcode/download/ GNU General Public License

Gitコマンド打ち込むのが苦痛でDashに登録してタイプ数をへらす。

git ほげほげ、gitほげほげ、何度も打ち込んで大変なのでDashにスニペットとして登録してタイプ数をへらしてみました。

楽。

楽。

よく使うものを登録しておけばタイプ数が約半分くらいになります。
git status の10文字が、 gits; 5文字。
git commit -m ‘’ の16文字が、gitc; の5文字。
git commit -am ‘’ の17文字が、 gitam; の6文字。
git commit –amendとかも。18文字タイプが、 ‘gitamd;’ 7文字。

とかなり楽になり、細かいコミットが面倒でなく1つの事 = 1コミットでも面倒でなくなりそうです。

登録したスニペットたち

登録したスニペットたち

cakephp3で基本のorderを指定する場合はbeforeFind()メソッドをTableクラスに追加する。

以前のCakePHP2では、Modelのクラスのプロパティー $orderに指定すればよかったですが、なくなっているので以下のようにbeforeFindメソッドをTableクラスに追加で可能。

class StatusesTable extends Table
{
     *
     *
     *
    public function beforeFind(Event $event ,Query $query, $options, $primary) {
        if(!isset($query->order)){
            $query->order(['Statuses.type'=>'ASC']);
        }
        return $query;
    }

     *
     *
     *

すこしはまったのが、以前までの感覚で ‘Status.type’ ってやると、エラーとなった。
Table = 複数系との思考に切り替えないとだ。(Statuses.typeでOK)

cakephpで複数単語名のコントローラーのルーティングでエラー

コントローラー名を OrderDetailsConttollerという風に
Order , Details からなる場合のルーティングがかわっていた。

CakePHP2までは、
http://example.com/order_details/
で良かったけど。CakePHP3では。

http://example.com/order-details/

となっているみたい。

/src/config/route.php を覗いてみると。  

Router::defaultRouteClass(DashedRoute::class);

という設定があり、デフォルトルートはDashed ルートClassだよと、なっている。
Router::defaultRouteClassの設定でいろいろルーティングを設定できるみたい。
(ルートをモジュールチックにしていろんなURLに対応できるようにしたんだなぁ。たしかにメディアのリニューアルとかだと、URL変えたくないし独自ルールをくつけるならココでオリジナルのクラスにかえちゃうと楽そう。)

参考 :
https://book.cakephp.org/3.0/ja/development/routing.html

cakephp3で自動でフィールドのタイプを認識できなかった。(due_dateという名称がわるいの??)

cakephp3で自動でフィールドのタイプを認識できなかった。(due_dateという名称がわるいの??)

cakephp3で自動でフィールドのタイプを認識できませんでした。あるテーブルで。
due_date という名前のフィールド(カラム)をか使っていたのですが、どうしてもうまく行かず。
dueという名称にしたらうまく判定できました。

FormHelper > モデルのschemaとかまでは正しく’date’と認識されているのだけど、その先の何処かで’text’と判定されてしまっていたみたい。掘り下げたいけどタイムアップでとりあえず対策のみメモ。

Cakephp3で、入力する日付フォーマットを日本語にする方法

Cakephp3で、入力する日付フォーマットを日本語にする方法

src/View/AppView.phpで定義すると楽に実装できました。
– 基本形成はdateWidgetで
– 細かいオプション変数としてテンプレートに渡して +=で必要分はテンプレートで追加

//src/View/AppView.php
class AppView extends View
{
     *
     *
     *
     
    public function initialize()
    {
        $this->Form->templates([
            'dateWidget' => '{{year}}年{{month}}月{{day}}日{{hour}}:{{minute}}',
        ]);
        $this->set('dateFormat',[
            'monthNames' => false,
            'minYear' => date('Y')-10,
            'maxYear' => date('Y')+10,
            'default' => date('Y-m-d H:i:s')
        ]);
    }
}
//your template 
echo $this->Form->control('due',$dateFormat+['label'=>'期限']);

PHPで連想配列をマージ(extend)するのは簡単だった。

PHPで連想配列をマージ(extend)するのは簡単だった。

今頃感ですが意外と使ったことがなかった。。orz..

$data = ['name'=>'yamaaaaa','job'=>'engineer'];
$data+= ['age'=>40];
print_r($data);//Array ( [name] => yamaaaaa [job] => engineer [age] => 40 )

php7にて確認。

Page 17 of 47

Powered by WordPress & Theme by Anders Norén