読者です 読者をやめる 読者になる 読者になる

ただの技術者ぶろぐ

大阪に住んでる技術者が書く覚書ぶろぐ。

3.1系???

・・・おや!? 3.0系のようすが・・・!

ptix.co

EC-CUBE3で遊ぼう3日目

EC-CUBE3で遊ぼう3日目

プラグインジェネレーター コマンドのバグを直そう。

いきなりハードルを上げた感がありますが、今回は画面がどうとか、機能がどうとかではなくて、純粋なコアコードのバグ修正(機能改善?)です。

前回プラグインジェネレーターを用いて管理画面の機能を追加しましたが、その中に[lower_code]なる記述が残っていたと思います。

NewSample0001ServiceProviderから抜粋
    public function register(BaseApplication $app)
    {
            // プラグイン用設定画面
        $app->match('/'.$app['config']['admin_route'].'/plugin/NewSample0001/config', 'Plugin\NewSample0001\Controller\ConfigController::index')->bind('plugin_NewSample0001_config');

        // 独自コントローラ
        $app->match('/plugin/[lower_code]/hello', 'Plugin\NewSample0001\Controller\NewSample0001Controller::index')->bind('plugin_NewSample0001_hello');

の独自コントローラの部分ですが、[lower_code]となっています。これ本来の処理を考えると「$app->match(‘/plugin/newsample0001/hello’, ‘Plugin\NewSample0001\Controller\NewSample0001Controller::index’)->bind(‘plugin_NewSample0001_hello’);」となるべきだと思いますが、置き換えられていません。それを修正しましょう。

ec-cube3のソースコード全体から[lower_code]を検索した場合、以下のファイルのみがヒットします。 f:id:rock-3:20170216102414j:plain [src/Eccube/Command/PluginCommand/Resource/ServiceProvider.php]内のみですね。

プラグインジェネレータを実行すると、[src/Eccube/Command/PluginCommand/PluginGenerator.php]が動作します。そこで[src/Eccube/Command/PluginCommand/Resource/ServiceProvider.php]を用いて新しいプラグインのServiceProviderを構成しています。ここを改修しましょう。

ここで選択肢として考えるべきなのは

  • ServiceProviderに[lower_code]を変換する処理を追記・修正する。
  • そもそも[lower_code]という表現がここでしか使われてないので、別の内容に書き換える。

のいずれにするかという事です。いずれの案でも動くように対応してみましょう。

ServiceProviderに[lower_code]を変換する処理を追記・修正する。

プラグインジェネレーターコマンドを実行して、新プラグインディレクトリ内に各種ファイルを構成するのは[src/Eccube/Command/PluginCommand/PluginGenerator.php]のcreateFilesAndFoldersメソッドです。

メソッド内で[src/Eccube/Command/PluginCommand/Resource]以下のファイルをそれぞれ読み出し、新プラグイン用に変換したのちに書き出しを行っています。EC-CUBE3.0.13では、260行目前後に以下のような処理がありServiceProviderを作っているので、そこに新しい項目を追加します。

[src/Eccube/Command/PluginCommand/PluginGenerator.phpの260行目付近]
// ここからが既存の処理
// ServiceProvider
$pluginFileBefore = file_get_contents($this->app['config']['root_dir'] . '/src/Eccube/Command/PluginCommand/Resource/ServiceProvider.php');
$from = '/\[code\]/';
$pluginFileAfter = preg_replace($from, $code, $pluginFileBefore);
$from = '/\[author\]/';
$pluginFileAfter = preg_replace($from, $author, $pluginFileAfter);
$from = '/\[year\]/';
$pluginFileAfter = preg_replace($from, $year, $pluginFileAfter);
// ここまでが既存処理

// ここに以下の処理を追加する
$from = '/\[lower_code\]/';
$pluginFileAfter = preg_replace($from, mb_strtolower($code), $pluginFileAfter);

ServiceProviderのファイル内容を読み出したのち、[]で囲まれた文字列があれば置き換える処理に[lower_code]を加えています。

[lower_code]を別の表現に書き換える。

そもそも[lower_code]はServiceProviderでしか使われていない指定文字列です。PluginGenerator.phpのControllerやForm作成処理の中で[code_name]を小文字のプラグイン名に変更している処理が存在しています。ServiceProvider側を変更してみましょう。

[src/Eccube/Command/PluginCommand/PluginGenerator.phpの260行目付近]
// ここからが既存の処理
// ServiceProvider
$pluginFileBefore = file_get_contents($this->app['config']['root_dir'] . '/src/Eccube/Command/PluginCommand/Resource/ServiceProvider.php');
$from = '/\[code\]/';
$pluginFileAfter = preg_replace($from, $code, $pluginFileBefore);
$from = '/\[author\]/';
$pluginFileAfter = preg_replace($from, $author, $pluginFileAfter);
$from = '/\[year\]/';
$pluginFileAfter = preg_replace($from, $year, $pluginFileAfter);
// ここまでが既存処理

// ここに以下の処理を追加する
$from = '/\[code_name\]/';
$pluginFileAfter = preg_replace($from, mb_strtolower($code), $pluginFileAfter);
[src/Eccube/Command/PluginCommand/Resource/ServiceProvider.phpの[lower_code]]
// 独自コントローラ
$app->match('/plugin/[lower_code]/hello', 'Plugin\[code]\Controller\[code]Controller::index')->bind('plugin_[code]_hello');

// ログファイル設定
$app['monolog.logger.[lower_code]'] = $app->share(function ($app) {

    $logger = new $app['monolog.logger.class']('[lower_code]');

    $filename = $app['config']['root_dir'].'/app/log/[lower_code].log';
    $RotateHandler = new RotatingFileHandler($filename, $app['config']['log']['max_files'], Logger::INFO);
    $RotateHandler->setFilenameFormat(
        '[lower_code]_{date}',
        'Y-m-d'
    );

    $logger->pushHandler(
        new FingersCrossedHandler(
            $RotateHandler,
            new ErrorLevelActivationStrategy(Logger::ERROR),
            0,
            true,
            true,
            Logger::INFO
        )
    );

    return $logger;
});

以下のように変更する。

// 独自コントローラ
$app->match('/plugin/[code_name]/hello', 'Plugin\[code]\Controller\[code]Controller::index')->bind('plugin_[code]_hello');

// ログファイル設定
$app['monolog.logger.[code_name]'] = $app->share(function ($app) {

    $logger = new $app['monolog.logger.class']('[code_name]');

    $filename = $app['config']['root_dir'].'/app/log/[code_name].log';
    $RotateHandler = new RotatingFileHandler($filename, $app['config']['log']['max_files'], Logger::INFO);
    $RotateHandler->setFilenameFormat(
        '[lower_code]_{date}',
        'Y-m-d'
    );

    $logger->pushHandler(
        new FingersCrossedHandler(
            $RotateHandler,
            new ErrorLevelActivationStrategy(Logger::ERROR),
            0,
            true,
            true,
            Logger::INFO
        )
    );

    return $logger;
});

これで、いずれの修正を行った場合でも、[lower_code]が変更される形になりました。一応こんな感じで修正できますが、基本的にEC-CUBEのコアコードは触らないほうがいいですし、この問題も次期バージョンでは改修されるみたいです。 github.com 一次対応とプラグインジェネレーターがどう動いているかの確認という感じですね。

今日はここまで。

EC-CUBE3で遊ぼう2日目

前回の改修で作ったプラグインに機能を追加してみます。

今回のテーマとしては、

  • 管理画面に画面を作る
  • 管理画面のサイドメニューに親メニューを追加する
  • 管理画面のサイドメニューに子メニューを追加する
  • 子メニューから作成した画面に移動する

この辺りを達成してみます。

作成した画面では、$appの内容をダンプする以下のような形にしましょう。 f:id:rock-3:20170215191226j:plain

各画面へ移動する為のパスを設定するのは、ServiceProviderと呼ばれるファイルです。

プラグインジェネレーターで作成した場合、[プラグイン名]ServiceProvider.phpという名前で作られているので、そこに追加していきます。

(例)NewSample0001ServiceProvider.php
    public function register(BaseApplication $app)
    {
        // プラグイン用設定画面
        $app->match('/'.$app['config']['admin_route'].'/plugin/NewSample0001/config', 'Plugin\NewSample0001\Controller\ConfigController::index')->bind('plugin_NewSample0001_config');

        // 独自コントローラ
        $app->match('/plugin/[lower_code]/hello', 'Plugin\NewSample0001\Controller\NewSample0001Controller::index')->bind('plugin_NewSample0001_hello');

        // Form
        $app['form.types'] = $app->share($app->extend('form.types', function ($types) use ($app) {
            $types[] = new NewSample0001ConfigType();

            return $types;
        }));

        $app->match('/'.$app['config']['admin_route'].'/support/new',
            'Plugin\NewSample0001\Controller\NewSample0001Controller::new_page')->bind('debug_viewer');

        // Repository

        // Service

        // メッセージ登録
        // $file = __DIR__ . '/../Resource/locale/message.' . $app['locale'] . '.yml';
        // $app['translator']->addResource('yaml', $file, $app['locale']);

        // load config
        // プラグイン独自の定数はconfig.ymlの「const」パラメータに対して定義し、$app['[lower_code]config']['定数名']で利用可能
        // if (isset($app['config']['NewSample0001']['const'])) {
        //     $config = $app['config'];
        //     $app['[lower_code]config'] = $app->share(function () use ($config) {
        //         return $config['NewSample0001']['const'];
        //     });
        // }

        // ログファイル設定
        $app['monolog.logger.[lower_code]'] = $app->share(function ($app) {

            $logger = new $app['monolog.logger.class']('[lower_code]');

            $filename = $app['config']['root_dir'].'/app/log/[lower_code].log';
            $RotateHandler = new RotatingFileHandler($filename, $app['config']['log']['max_files'], Logger::INFO);
            $RotateHandler->setFilenameFormat(
                '[lower_code]_{date}',
                'Y-m-d'
            );

            $logger->pushHandler(
                new FingersCrossedHandler(
                    $RotateHandler,
                    new ErrorLevelActivationStrategy(Logger::ERROR),
                    0,
                    true,
                    true,
                    Logger::INFO
                )
            );

            return $logger;
        });
    }

このregisterメソッドの中に、

         // 管理画面 親メニューを追加する
        $app['config'] = $app->share($app->extend('config', function ($config) {
            $nav = array(
                'id' => 'support',
                'url' => 'support',
                'name' => 'サポート',
                'has_child' => 'false',
                'icon' => 'cb-eye',
                'child' => array(
                    //管理画面 子メニューを追加する
                    array(
                        'id' => 'debug_viewer',
                        'url' => 'debug_viewer',
                        'name' => 'デバッグ機能',
                    ),
                ),
            );

            $config['nav'][] = $nav;

            return $config;
        }));

みたいな形で記述するとサイドメニューが作成されます。 [support]というIDがついたarray型が親のメニューを定義して、[child]というIDがついたarray型が子のメニューを定義しています。 親メニュー:support → 子メニュー:debug_viewerという形ですね。 iconの種類はcb-eyeという目のマーク。なんかセンスないですね。すいません。

ただ、この状態で管理画面を起動しても、以下のようにエラーになってしまいます。 f:id:rock-3:20170215191233j:plain

これは、debug_viewerというurlを指定しているが、対応する処理が存在しませんという事です。

その為、registerメソッド内に

$app->match('/'.$app['config']['admin_route'].'/support/new',     'Plugin\NewSample0001\Controller\NewSample0001Controller::show_debugview')->bind('debug_viewer');
//※$app['config']['admin_route']にはインストール時に指定した、管理画面のパスが入っています。

という記述を追加してやる事で、エラーが出なくなります。

ちなみに簡単に説明すると、 $app->matchというのはルーティング(URLのあて先)を指定するメソッドで、 GET、POST、PUT、DELETEいずれのルーティングであっても、対応するという意味です。これがたとえばPOSTのみしか受け入れない(直リンクは拒否するがformの値を渡すとか)場合だったら$app->postで指定する事になります。 二つのパラメータはそれぞれ、ルーティング名と対応するコントローラを指します。

さて、ルーティング名と対応するコントローラーを設定したので、管理画面自体の表示はできるようになりましたが、クリックしてもエラーが発生します。

対応するコントローラがないためなので、コントローラを作成しましょう。 先に記述したコントローラでは「'プラグイン名\Controller\NewSample0001Controller::show_debugview'」と記述しています。指定クラスファイル::指定メソッドという構成になっているので、show_debugviewというメソッドを定義します。

既にNewSample0001Controllerクラスにはindexメソッドが定義されているので、そのままコピーしてメソッド名のみshow_debugviewに書き換えます。

プラグイン名/Controller/NewSample0001Controller::show_debugviewメソッド
public function show_debugview(Application $app, Request $request)
{
    return $app->render('NewSample0001/Resource/template/index.twig', array(
        // add parameter...
    ));
}

$app->renderは画面を描画するメソッドですが、対応するtwigファイル(描画html)が存在しません。

プラグイン名/Resource/template/admin関数内にdebug_write.twigファイルを作って以下のように記述します。

プラグイン名/Controller/NewSample0001Controller::show_debugviewメソッド
public function show_debugview(Application $app, Request $request)
{
    return $app->render('NewSample0001/Resource/template/admin/debug_write.twig', array(
        // add parameter...
    ));
}
プラグイン名/Resource/template/admin/debug_write.twig
{% extends 'default_frame.twig' %}

{% block title %}サポート画面{% endblock %}
{% block sub_title %}デバッグ機能{% endblock %}

{% block main %}
    <div class="debug_area" id="debug_area">
        {{ dump(app) }}
    </div>
{% endblock main %}

これで目的の管理機能が達成できました。

EC-CUBE3で遊ぼう1日目

基本的な開発はEC-CUBE開発ドキュメントを見ながらになります。

EC-CUBE3.0.13以前バージョンでは、プラグイン作成用プラグインを使って開発するか一から手で作っていく必要がありましたが、現バージョン(3.0.13)以降はコマンド入力で作るのが手っ取り早いと思います。

プラグインジェネレータコマンド

php app/console plugin:develop generate
# (※あ、開発ドキュメントのtypoが直ってる。)

作成コマンドを入力する 入力する必要のある項目は以下の通り

------------------------------------------------------
---Plugin Generator
---[*]You can exit from Console Application, by typing quit instead of typing another word.
------------------------------------------------------

[+]Please enter Plugin Name
Input[1] : <<新規で作るプラグイン名>>

[+]Please enter Plugin Name (only pascal case letters numbers are allowed)
Input[2] : <<新規で作るプラグイン英名:先頭は大文字で英数字利用可>>

[+]Please enter version (correct format is x.y.z)
Input[3] : <<作成バージョン x=>メジャーバージョン、y=>マイナーバージョン、z=>ビルドバージョン>>

[+]Please enter author name or company
Input[4] :<<プラグイン作成者もしくは制作会社>>

[+]Do you want to support old versions too? [y/n]
Input[5] : <<旧バージョン(3.0.8以下)もサポートするかの判定>>

[+]Please enter site events(you can find documentation here http://www.ec-cube.net/plugin/)
Input[6] :<<共通イベント設定=>※複数設定可>>

[+]Please enter hook point, sample:front.cart.up.initialize
Input[7] :<<フロント、管理イベント設定=>※複数設定可>>

ただし、Windowsコマンドプロンプト環境などで実行しても、プラグイン名などに全角文字があると

  [Doctrine\DBAL\Exception\DriverException]
  An exception occurred while executing 'INSERT INTO dtb_plugin (name, code, class_name, plugin_enable, del_flg, version, source, create_date, update_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)' with
   params ["\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "PluginName", "", 0, 0, "1.0.0", 0, "2017-02-05 15:06:23", "2017-02-05 15:06:23"]:
  SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\x00V\x00\x00v...' for column 'name' at row 1

という感じで文字コードエラーになってしまいますね。 これはコマンドプロンプトMySQL接続の場合の文字コード(CP932)関連でしょうか。Mac機では起きてないですね。

phpStormのコマンドラインツールからなら実行できたので、 多分間違いないでしょう。

 this files and folders were created.
 - C:\Eccube/app/Plugin/NewSample0001
 - C:\Eccube/app/Plugin/NewSample0001/ServiceProvider
 - C:\Eccube/app/Plugin/NewSample0001/Controller
 - C:\Eccube/app/Plugin/NewSample0001/Form/Type
 - C:\Eccube/app/Plugin/NewSample0001/Resource/template/admin
 - C:\Eccube/app/Plugin/NewSample0001/config.yml
 - C:\Eccube/app/Plugin/NewSample0001/PluginManager.php
 - C:\Eccube/app/Plugin/NewSample0001/ServiceProvider/NewSample0001ServiceProvider.php
 - C:\Eccube/app/Plugin/NewSample0001/Controller/ConfigController.php
 - C:\Eccube/app/Plugin/NewSample0001/Controller/NewSample0001Controller.php
 - C:\Eccube/app/Plugin/NewSample0001/Form/Type/NewSample0001ConfigType.php
 - C:\Eccube/app/Plugin/NewSample0001/Resource/template/admin/config.twig
 - C:\Eccube/app/Plugin/NewSample0001/Resource/template/index.twig
 - C:\Eccube/app/Plugin/NewSample0001/LICENSE

[+]Database
 Plugin information was added to table [DB.Plugin] (id=43)
Plugin was created successfully

正しく実行されたらこんな感じでプラグインディレクトリと中身が作られて、 f:id:rock-3:20170206170344j:plain

こんな感じで、EC-CUBE3の管理者画面に表示されます。 f:id:rock-3:20170206170333j:plain

一から作るとyamlで自爆したり、簡単な実装に凄い手間がかかってたんですけど、今の仕組みでは結構簡単に作れますね。 次回以降ではカスタマイズをいれてみます。

続きはまた今度。

EC-CUBE3で遊ぼう

最近EC-CUBE3が3.0.13になって プラグインジェネレーターとか付いて使いやすくなったのに 感動してる人が意外に少なかったので、 せっかくだから備忘録を書くがてら遊んでみようと思って書きます。

他の記事を書いてる方々がEC-CUBEの中の人だったり、 コミッターの人だったりしてハードルが高いので、 ハードルを下げる事に一役買いたいと思います。

ちなみに私、EC-CUBE3に使われているSymfony2やdoctrineみて 「なんじゃこりゃ、わからんぜよ」ってレベルなので、 基本からしっかり学ぶ Symfony2入門 あたりを見ながら日々勉強です。

ドラクエで言えば、 いざないの洞窟を超えてロマリアについたぐらいのレベルです。 はがねのつるぎはたかくて買えないのです。


主人公(筆者)のステータス

項目 説明
なまえ ろっく
しょくぎょう ぷろぐらま
れべる ぴーえっちぴー1ねんせい
たげんご どっとねったー、ぷらぷらー
かいはつそくど まーまー
そうび
E VS Code(タダ)
E MySQL Workbench (タダ)
E phpStorm (タダじゃない)

☆☆現在地点  プラグインジェネレータを利用してオレオレプラグインを自作してるとこ。

☆☆ゴール目標  ①EC-CUBE3のプラグインで独自の割引きを作成すること。  ②APIを利用して、他システムとの連携をすること。

日数 タイトル  キーワード
1日目 EC-CUBE3で遊ぼう1日目 プラグインジェネレーター
2日目 EC-CUBE3で遊ぼう2日目 管理画面の作成 メニューの作成
3日目 EC-CUBE3で遊ぼう3日目 コアコードを修正してみる

PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)

PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)

MySQLと他プログラム言語の整数型比較(備忘録)

最近、静的言語の言語を使う機会が減ってきたので、すぐ思い出せるように備忘録として。

MySQLC#VB.net
MySQL C#VB.net
データ型名 格納サイズ 符号なし(unsigned) データ型/符号なしデータ型
tinyint -128~127 0~255 sbyte/byte
smallint -32768~32767 0~65535 short/ushort
mediumint -8388608~8388607 0~16777215 なし/なし
int -2147483648~2147483647 0~4294967295 int/uint
bigint -9223372036854775808~9223372036854775807 0~18446744073709551615 long/ulong
float ±3.4028235E38~±1.4E-45 float
double ±1.7976931348623157E308~±4.9E-324 double
MySQLC言語
MySQL C言語
データ型名 格納サイズ 符号なし(unsigned) データ型/符号なしデータ型
tinyint -128~127 0~255 char/unsigned char
smallint -32768~32767 0~65535 short int/unsigned short int
mediumint -8388608~8388607 0~16777215 なし/なし
int -2147483648~2147483647 0~4294967295 int/unsigned int
bigint -9223372036854775808~9223372036854775807 0~18446744073709551615 long/unsigned long int
float ±3.4028235E38~±1.4E-45 float
double ±1.7976931348623157E308~±4.9E-324 double

本とか見ればわかるけど、結局ググってるもんなー。

MariaDB&MySQL全機能バイブル

MariaDB&MySQL全機能バイブル

C#逆引きレシピ

C#逆引きレシピ

MySQLのストアドとファンクションで遊ぶ その2

今回のテーマは引数、変数、返り値あたり。

前回の例文

DELIMITER $$
CREATE  PROCEDURE `demo_procedure`()
BEGIN
   select 'hogehoge' as display_text;
END$$
DELIMITER ;

このストアドを使うとdisplay_textというカラムの'hogehoge'という文字列を取得できるのですが、大体のケースではこのような使い方はしないと思います。

ストアドプロシージャの特徴として「戻り値がない」事が挙げられます。つまり、'hogehoge'を返すよう使い方が実際の運用では使えないのです。(管理ツール上、MySQLコマンドラインなどで利用するのは可能です)

こういう場合は、返したい値の内容に合わせて色々使いわけをする必要があります。たとえば、単数の返り値を取得したい場合であればファンクション、結果テーブルを取得したい場合などでは、実テーブルやテンポラリーテーブルを利用する必要があります。

余談ですが、MySQLにもOracle的なテンポラリーテーブルが使えるのですが、あんまり流行ってないご様子です。もったいないなぁ。

ファンクションの利用:

 例:都道府県コードから都道府県名のみを取得したい場合

pref_table

 CREATE TABLE `pref_table` (
  `code` smallint(11) NOT NULL,
  `name` varchar(50) DEFAULT '',
  PRIMARY KEY (`code`)
);
code name
1 北海道
2 青森県
3 岩手県
... ...

今回、テーブルの値を検索して結果を返すという処理が必要なのでテーブルを準備します。

get_pref

DELIMITER $$
CREATE FUNCTION `get_pref` 
($pref_code int)
RETURNS varchar(50)
BEGIN
-- 都道府県名
DECLARE pref_name varchar(50) default ''; 

-- 都道府県名を取得する
SELECT 
    name
into pref_name
From pref_table
where code = $pref_code
;

-- 取得結果を返す
RETURN pref_name;
END$$
DELIMITER ;

結果

SET @pref_code = 1;
select get_pref(@pref_code);
 =>北海道

前回のストアドの説明のときになかった項目のひとつに、

CREATE FUNCTION後の「($pref_code int)」という記述があります。また、その後にRETURNS varchar(50)という構文が追加されています。

これらはそれぞれ「($pref_code int)」を引数やパラメーター、「RETURNS varchar(50)」を返り値と呼ばれたりします。

これは、ファンクションに必須の構文で、返り値として変数型の内容を返すという表現です。数値を返す場合ならRETURNS integerやRETURNS smallintと記述します。

DECLARE pref_name varchar(50) default ''; はストアド・ファンクションの中で変数を宣言する構文で、 「DECLARE 変数名 型 デフォルト値(省略可)」と記述します。

'''mysql SELECT name into pref_name From pref_table where code = $pref_code ''' 以上のSELECT~INTO文は単項目のデータを取得しています。

WHERE句のキーに一致した値を変数に格納してくれますが、 複数行返るようなケースではエラーが発生します。

複数件を返すような処理は次回以降の記事にして、 今日はおしまいです。