伊藤清徳の垂直落下式ムーンサルトプレス

PerlとかPHPとかMySQLとか...がんばっても8割だ。

Author: admin (page 1 of 28)

北九州市気に入った! 宿泊と移動が一石二鳥 名門大洋フェリー 新門司港→大阪南港

先日、福岡→大阪の連続出張がありまして、僕が住む愛知県からはどういくのがいいのか?ということを考えていたら船を使うのがいいのでは?という結論に達して、実行してきたレポートです。

日程

8/7 福岡市博多駅付近にて某ボランタリーチェーンより依頼の7時間ぶっ通しセミナー
8/9 大阪南港地域のクライアントにてITコンサルティング

という日程でした。
8/6の夜には福岡に入っておく必要がある、8/8は空きではあるが、名古屋に帰るのはナンセンス。ということで、8/6の夜中部国際空港を発で、8/8のうちに大阪に移動、8/9夜帰宅。というイメージで調べていたところ、福岡と大阪を結ぶ海の定期航路があることがわかったので使ってみようということになったわけです。

8/6 ANA 449便 名古屋(中部)-福岡 20:10-21:35(この便はタイ国際航空などのコードシェアです)
8/6-8/8 福岡市内宿泊
8/7 セミナー登壇
8/8 北九州市小倉に移動し、19:50よりから船便
8/9 08:30 大阪南港到着

ANA449便のコードシェアや運用機材、福岡市内の宿泊などについては、特徴的なものをあえて選んで移動していますが、これについてはまたそのうち。

北九州市

8/8のホテルチェックアウトから、船便出航時刻の19:50までは時間がありましたので、博多駅から小倉駅に移動し、1日小倉の駅周辺でPCを開ける場所にて仕事をする予定でおりました。あまり調べずに福岡に向かい、前日に調べたところ、北九州市と呼ばれる地域はとても広いことを知り、小倉と門司は近いものの、門司港と新門司港は山で隔てられてなかなかに距離があり、小倉と近いのは門司港のほうで、僕が船に乗るのは新門司港の方だということがわかり、ちょっと焦りました。。。。
幸いなことに小倉駅から無料送迎バスがでています。よかったー(でもバスで40分!?)

小倉駅からすぐのところに1日利用で1000円のコワーキングスペース秘密基地さんがあったので、そちらを使うことに。

こんなかんじの雰囲気。夜はダイニングバー経営になるみたいですが、最近はシェアオフィス寄りなコワーキングが多いのですが、こちらは原義なコワーキングを感じました。

昼食は、どうも鉄鍋餃子で有名店があるということで、いくことに

総本店でした。実はわたくし、どうも最近では「唐揚の人」みたくなっていますが、某ミシュラン・ガイドにのった餃子専門店の主人に「博士」と呼ばれるほど餃子好きでして・・・行ってまいりました。評価は別のタイミングで。


北九州市民が博多になくて小倉にあるものとして胸を張るという、小倉城の横には、名古屋になり水辺のおされなコメダがありました(が、僕はコーヒーが苦手なのでめったに行きませんが)。商店街にはさまざまな種類のお店があり、活気もそこそこ。北九州気に入りました。

いよいよ出航

18:30になり、新門司港へいくバスが小倉駅から出ますので、バスにのって新門司港へGoです!。40分のバス。19:50に船が出ますので、このバスが、航路の一部分と言えます。バスの道すがら門司港も通りまして、門司港もレトロで良い雰囲気ですね。

今回乗る船はフェリーで名門大洋フェリーという会社が1日2往復運行しています。「名門」ってなかなか自己主張が強い名前だなと思ったら、昔は名古屋と門司港を結ぶ定期航路を運営していた会社で、そこから「名門」とついたようです。なんかすみません。
ちなみに。新門司港からは「阪九フェリー」という会社の船も出ているようですが、こちらは大阪が泉大津に着くので、大阪市からはちょっと遠くなります。

今回利用する便は、新門司港19:50発 大阪南港フェリーターミナル08:30着という 移動と宿泊が一石二鳥で終えることができるベストな便です。いわゆる雑魚寝から個室まであります。今回はカプセルホテル的なクラスがあるので、そちらを利用しました。

食事は付いていませんが、片道6200円で移動と宿泊が一気に行えるのはとても便利です。

船内

19:50定刻通り出航!

(標準時刻ではない実際の)時差では愛知県と20分くらいの差があり、19:50はちょうど日暮れの時間です。左手には北九州市の山々、右手には北九州空港を見ての出航です。清々しいですね。


昨年いったクルーズ船の旅よりは小さいですね。当たり前ですけど(しまった!続編の記事書いてない!!) 一つ良いのは浴場があって、大人の男が7人位入っても大丈夫くらいの湯船のある浴場です。小さい船なのでエンジン音はどうかなーと思いましたが、全然気にならないですね。揺れもあまり気になりません。晴天ということもあるでしょうけれども。



食事は夜1500円朝700円のバイキング方式。バイキング方式なのであまり期待していませんでしたが、思ったよりは美味しいものがそろっており、定期航路だからなのか企画も考えられていて、今回はビンチョウマグロと甘海老が食べ放題でした。悪くないですね。


生ビールは、1杯500円で売っています。売店でもお酒などは売っています。

この船には、ラウンジ的なものはなく、小さいテレビを見るスペースと展望スペースがあるくらいです。クルーズ船とちがって、車を運ぶため駐車スペースが多いからでしょう。

船内にはwifiがあります。1日30分3回まで使うことができます。0時で次の日の扱いとなるため、この船では合計3時間までつかうことができます。この航路は瀬戸内海を通りますので、人口がある地域を通っていることになりますから、比較的安定して電波をつかみます。しかし、客室は船という性質上 壁がかなり堅牢に作られており、客室には船内wifiが届きにくいです。 僕が普段持ち歩いているauのデータ専用SIMとほぼ同じ強さを示していたので、船内wifiの大元は、au系の回線であろうかとおもいます。

大阪南港はユニバに近いこともあり、九州方面からユニバに遊びに行く家族連れ、夏休みの時期ということもあり、大学や高校の部活の合宿の行き帰りの団体、トラック運ちゃんが多いようでした。ビジネスマンはすこしだけいましたが、航空や新幹線に比べると比率は少ないです。

クルーズ船とちがい、夜に遊ぶ要素はありませんし、8:30には下船をすることを考えると6:30には起きてないといけませんから、寝るのを早くする人が多いです。カプセルの部屋にテレビもありませんしね。



朝になると、少ないビジネスマンたちが今日の仕事の準備をしています。7:00ごろには、明石海峡大橋をくぐり、阪神工業地帯を眺めながら、大阪へのアプローチとなります。

7:50くらいにはパイロットが近づいてきて大阪南港への最終アプローチ。時間通り8:30に南港へ下船開始となります。

なお、船では「パイロット」は操縦士ではなく水先人のことです。航空では「アビエイター」が元々の操縦士ですが、現在は「パイロット」で通じます。ただし、空海両方の事業に関わる人との間ではpilotとaviatorは使いわけることが多い。ビジネス用語としては両方知っておいたほうがいいですね。

大阪南港フェリーターミナル

大阪南港フェリーターミナルは、大阪市内ではあるものの少々不便なところにあります。公共交通機関では、大阪市高速電気軌道南港ポートタウン線(ニュートラム)駅がありますが、梅田周辺までは、乗り換えが2回発生し、およそ50分程度かかります。ユニバーサル・スタジオジャパンまではバスが出ており30分程度かかります。

今回良かったのが、南港フェリーターミナルと出張先のクライアントの本社が歩いて10分程度のところにあるためアクセスが良かったためつかいましたが、たとえば梅田周辺で10:00からのアポとなると、ちょっと時間的には危険な感じもします。アポ時間と行かなくてはならない場所によっては、出張で使うのはリスクが伴います。そういったいみでも、今回はレポートを書いておこうと思ったわけです。

現場からは以上です!

「超読」の世界を見てきた。

どうもこんにちは 伊藤です きよっちです。

おひさしぶりです。いい加減書けよ。とクライアント各位には叱られそうですが。。。

さて、昨日2019-07-12に「超高速読書法(略称:超読)」という、一般的には「速読」と言われるものの体験会があったので、いってきました。参加費は5000円と決して安くはないのですが、これはなかなかカルチャーショックだったので書きます。

http://cho-doku.com/
↑こちらが公式?(トップページタイトルが「top」だよ!書き換えよ!!)
http://cho-doku.com/seminars/
↑こちらが体験会の案内ページ。

僕の速読の前提知識

速読というのは、何十年も前からビジネスや勉強のスキルとして、存在しているのは当然知っていましたが、何なのかは全く知りませんでした。
この体験会に参加するにあたり、いくつかのYouTubeやサイトの情報をチェックしてはおきました。

  • 一瞬で文字を判断できるように、本を読む上で、その本から得たい答えの問いを持っておく
  • 飛ばし読み斜め読みをする

が「一般的には多そう」でした。
なるほど。これならば「都合のいい情報だけをピックアップする」という人間の脳構造の特性である「確証バイアス」に近い考えなのでできなくはない?感じはしました。

が、結論から言うと、超読は

これらとは一線を画す「ちゃんとした読書」でした!!

超読は技術理論

体験会では、その名前の通り、超読の玄関口となる技術を体験体感する会となっていました(ネタバレになるので書きません)。前述の「一般的な速読」はどちらかというと「要約するための考え方」というほうが正しくて「読む」ということとは別の概念なのかな?とおもいます。 それに対して超読は、早く読むための身体コントロール技術でした。つまりは、まさに「読む」という行動そのままです。

体験会の案内ページに「感情移入しながら小説を読める」とありますが、まさにこれだなと。要約の方法では、行間を読む事はできませんから。僕は仕事柄、平均すると月間で10冊超えるくらいの技術本とビジネス本を読みますが、これらも行間が読めないと、ほとんど役にたたないと思っていますので(そもそも日本語の構造がそうなってる)、何も小説に限った話ではない、本を読むためのベース技術だろうなとおもいました。

しかし身体コントロール技術なので習得の難易度はそこそこ高そうでもありました。(毎週受講で3ヶ月必要。その後の追加補講はいくらでも通っていいらしい)

怪しくないのか?

ぶっちゃけると、怪しいです(笑) いや、体験会で体験すること、聞くことはまったく怪しくなく、むしろ、納得できるものがとても多いのです。 しかし、超読を習得・修得している、みなさんが「変わっている」のです。これはポジティブな意味で、普段の生活で速読の技術は「必要条件」かといわれると、そんなことはなく、単純なニーズと言う意味では速読は不要と言えます。それをわざわざできるようになって、自分のライフスタイルに厚みをつけようと考えている人たちなわけですから、変わっているとしか言いようがありません。でも、明確なのはふんわりとした精神論や自己啓発より、はるかに意味があることをやっている(し、そういったことは、身体技術としての超読を身に着けて、本を読む中で見い出せばいいのでは?)のは、すごくわかりました。なので、やるかやらないかは別として体験会にだけでもいってみる価値はあると言えます。

ぼくだけの感想

今回の体験会では、僕の友人が実演をしてくれました。彼女の目の動きや身体の使い方を目の当たりにしたのですが、その感想は、「梅原大吾と同じ目をしている」でした。

梅原大吾は、日本のeスポーツの第一人者で、対戦格闘ゲームで世界に知られるプレーヤーです。対戦格闘ゲームは、決まった座標範囲内で、周辺視野を広くとり、1/60秒で情報取得し判断するというゲーム性で成り立っています。心拍数コントロールや筋肉の使い方制御など、ビデオゲームといえど、ほとんどやることはスポーツと変わりないです。

これを3ヶ月で身につけられるとなると、超読は何か分野において世界に行ける可能性のある技術だなと思いました。

ところであんた習うの?

かなりやりたいとおもっています。

が、決して安くはない本セミナー受講料(高いといっても安いんですが)、そして東京でしかいまのところやってない。
というのがあるので、準備をしなければ。。。。まずは東京でコンサルティングの仕事を1つばかり増やしたいなと。

で、作る仕事はとりあえず2020年4月くらいまではストップします宣言!します。

P.S

開発者の先生がマイルオタクだったので、ぼくはそこにも惹かれています。

現場からは以上です。

楽天の注文フォームに無茶させるver.2.1 スマホ対応

どうもどうもおひさしぶりです。伊藤です。
全然ブログ書かねぇじゃねぇかとクライアント各位に叱られそうですが、全くそのとおりでございます。

昨年、楽天の注文フォームにむちゃさせるver2という記事を書きました。が、この記事少しだけ不具合がありました。ので、それを修正するお話。

スマホ対応

不具合の具体的な内容は、スマートフォン対応です。スマホの場合は少しだけパラメータが追加になります。

あ、その前にPCの場合の対応は、以前の記事を見てくださいね。

ちょっとだけ難しい話をきかきますが、スマホ時は、以前の方法だと、カゴにはいったと処理完了のステータスが楽天側からリターンがあるにもかかわらず、カゴにいくと中身がなくなってしまうという不具合があります。どうも楽天のスマホのカートシステムは、URIから制御を取得しているらしく、あらかじめ専用セッションにカートの追加命令が保存されていないと、URIの処理が優先されて、カートが空になるという仕様になっているようです。

AJAX送信時にパラメータ追加

結論から言えば、

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'device': 'sp',//デバイスモードをスマホに
      'userid': 'itempage',//これがないとカゴにはいらないときがある???
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  }
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

というパラメータになります。つまり device=sp と userid=itempage が追加になっているということです。
レスポンシブでページを作っている場合は、これをPCスマホ時で切り替える必要があります。

なんでもいいけど、カート追加がGETで行われる楽天さん。まじでイケてないんでHTTP勉強しなおしてください!!

SPとPC切り替え

User-Agentなどで判定すればOKです。といってもわからない方もいらっしゃるとおもうので、ページは重たくなりますが、判定ライブラリを使った場合の例示も書いておきます。

HTMLに自作JSファイルをの前に

<script src="https://cdnjs.cloudflare.com/ajax/libs/mobile-detect/1.4.3/mobile-detect.js"></script>

を呼び出します。ライブラリの詳細はこちらをごらんください。

JSファイルは以下のようにします。


///パラメータ
var params = {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  };

///モバイル判定
var md = new MobileDetect(window.navigator.userAgent);
if( md.mobile() != null ){
  params['params'] = 'sp';
  params['userid'] = 'itempage';
}

///ajax実行
$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: params
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

以前の記事で書いていないことがありました。カゴへ移動

処理完了時にカートへ移動したいということがあると思いますので、そちらをいれたバージョンも書いておきます。


///パラメータ
var params = {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  };

///モバイル判定
var md = new MobileDetect(window.navigator.userAgent);
if( md.mobile() != null ){
  params['params'] = 'sp';
  params['userid'] = 'itempage';
}

///ajax実行
$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: params
})
.then(function(data){
  if( data.resultCode == '0'){
     location.href = 'https://ts.basket.step.rakuten.co.jp/rms/mall/bs/cartall/?shop_bid=ここにショップID';
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

location.hrefを使ってショップIDを指定したカートURLにページ移動させることでカートへ遷移します。

動作参考をば

そういえばこれまえ参考動作を作っていませんでしたね。動作させてみましょう。



辛いものをカゴにいれる

↑ここをクリックするととっても辛い商品がカゴにはいるよ!!!

で何をしているかというと

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mobile-detect/1.4.3/mobile-detect.js"></script>
<script>
$(document).ready(function(){
    var md = new MobileDetect(window.navigator.userAgent);
    
    $('.btn-send-rakuten').on(
        'click',
        function(eve){
            setProduct(0);
        }
    );
    
    var productList = [
        { 'shop_bid': '205607', 'item_id': '10002002', 'option': 'とってもからいよ!' },
        { 'shop_bid': '205607', 'item_id': '10002166', 'option': 'こっちもすごいよ' },
    ];
    
    function setProduct( current ){
        var targetProduct = productList[ current ];
        var params = {
            'shopid': targetProduct['shop_bid'],
            'units': 1,
            'itemid': targetProduct['item_id'],
            'dbasket_choice_select[]': targetProduct['option']
        };
        if( md.mobile() != null ){
          params['params'] = 'sp';
          params['userid'] = 'itempage';
        }

        $.ajax({
          url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
          type: 'get',
          dataType: 'jsonp',
          data: params
        })
        .then(function(data){
             current ++;
             if( current >= productList.length ){
               location.href = 'https://ts.basket.step.rakuten.co.jp/rms/mall/bs/cartall/?shop_bid='+productList[0]['shop_bid'];
             }
             else {
                setProduct( current );
             }
        });
    }

});
</script>
</pre>
<a href="javascript:void(0);" class="button is-danger is-fullwidth btn-send-rakuten">辛いものをカゴにいれる</a>

こんなコードです。

  1. カートに入れたい商品を配列で準備
  2. カートに入れたい商品をループ(ここで関数自体を回帰呼び出し)してAjaxコール
  3. 全商品はいったらカートにジャンプ

です。JSの書き方としては、あえて古い書き方でわかりやすくしているので、JSプログラマ諸氏はご勘弁を。

1フォームで楽天で複数商品をカートに入れる方法は他にもありますが、動的にPC/SP共通でカートに入れられるというのは、サイトの表現上役に立つことが多いかと思います。

現場からは以上です。

さくらインターネットのMySQLが5.7になってORDER BYでこまったとき

古いWebアプリケーションフレームワークをつかい
MySQL5.6までのMySQLデータベースをつかったアプリケーションを
MySQL5.7のサーバーに移転するにあたり

ORDER BY clause is not in SELECT list, references column...

というようなエラーが出た場合、MySQL5.7より「ONLY_FULL_GROUP_BY」がオンになっていることが原因だ。

このあたりは、すでに多くの先駆者が設定について書いてくださっている。

など。大変ありがたい情報だ。

しかし、さくらインターネットでは権限がない

さくらインターネットなど共有サーバーでは、これらの設定については、
権限付与がなく設定ができない。

さてどうしたものか。。。

そのセッションのみで、(MySQL Workbenchをつかっている経験から)SQLMODEの変更ができるはず。
ということで

SET SESSION sql_mode = 'NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

毎回接続時に実行できればOKなはず。と考察して、調べた結果問題なしでした。
各フレームワークや、自作のシステムの場合も、
mysqli_connect関数の直後や、new PDO()直後あたりで毎回実行するようにすればOKです。

WordPressでタイトルタグをテンプレートファイル側から指定

今日はかりゆしウェアを着ている伊藤です。どうもどうも
2018年日本は災害レベルで暑いですね。沖縄県はすずしいですよ。

さて最近連日WordPressの話題を書いています。
今日はWPのタイトルタグをテンプレート側から指定できるといいよね~ってはなしをします。

おともだちのKさんの記事を参考にして書いていきます。

前提条件

  • 包括的に全部を書き換えるのではなく、必要なタイミングでテンプレートごとに指定したい
  • テンプレートファイルからは、get_header();の直前に関数で指定したい

たぶんあるあるだと思います。SEO考えるとね。結構いじりたいところですからね。

document_title_partsフィルターを利用する

Kさんの記事によると、document_title_partsに紐づけした関数に、引数としてタイトル構造を持つ配列が渡されて、それを編集してreturnすればいいぜ。ワイルドだろー?って書いてあるます。なるほど。しかし僕のやろうとしていることだと、必要なタイミングで呼びたいという前提と、テンプレート側から指定したという条件が入るので、そのまんまは使えません。
そこで登場するのがクロージャーです。

クロージャーを使って実装

もうめんどいからソースを晒します。functions.phpにでも書いてください。

/**
 * タイトル変更
 * @param string $title
 * @param string $tagLine
 * @param string $siteTitle
 */
function setTitle( $title, $tagLine = null, $sitTitlte = null ){
    add_filter(
        'doment_title_parts',
        function( $return ) use ( $title, $tagLine, $sitTitlte ) {
            $return['title'] = $title;
            if(!is_null($tagLine)){
                $return['tagline'] = $tagLine;
            }
            if(!is_null($sitTitlte)){
                $return['site'] = $sitTitlte;
            }
            return $return;
        }
    );
}

こんかんじ。で、テンプレからは

setTitle( 'たいとるだよーん' );
get_header();

もう少しちゃんとするならクラスにして静的メッドにするのが筋でしょうねー。
クロージャーをつかっています。あんまりWPでは使われないかとおもいます。

意外に使うTipsになるのでは?
現場からは以上です。

WordPressにORMを組み込む

どうもどうも。2年近く連絡をしていなかったら育ての親である叔父が心配してきた伊藤です。

さて、WordPressにはデータベースを操作する機能というのは一応揃っています。
wpdbとかそのへんですね。ただ、基本的にはSQLを書くか、あるいは、WPのもともとのテーブル構造に対するクエリを前提としていることが多く、遠回りだったりとかしますわね。できればActiveRecordやORMがはいっているといいなーと思うわけです。

特に、従前のシステムがあって、WPでリニューアルをかけたい。というときは、従前のシステムのテーブルはそのままにリニューアルをしたいということは、多々あるわけです。

まさにそんな事を考えていたので、いっちょWordPressにORMを入れてみた。というはなしをします。

PHPのActiveRecord ORMライブラリ

PHPのActiveRecordやORMの単体ライブラリというのが、実はそもそもあまりないです。最近のPHPのフルスタックWebアプリケーションの場合、Doctrineがベースに利用されていたりとかするわけで、車輪の再開発を嫌うのか、あまりないのです。

僕の場合は、PHP5.3系の頃は、php-activerecordをよく使っていましたが、ちょっと古くなったのと、リレーションのリレーションが追えないなど、ちょっと足らないところもあります。

今回探していていい感じだなーとおもったのが、RedBeanPHPです。MySQL/MariaDBのプラグインとPHPライブラリを提供しており、かなり高速で、わかりやすいコード体系のORMになっています。しかし、MySQLのプラグインということで、共有ホスティングなどでは可搬性がさがりますので、今回は見送りました。

今回採用することにしたのがSpotORMです。Doctrine DBALをベースに作られたORMラッパーです。Relationの定義などがしやすく、僕がよく使うフレームワークYiiのModelの構造に似ているのも、採用の理由です。

ではインストール

composerでインストールします。

{
    "require": {
        "vlucas/spot2": "~2.0"
    }
}

とかですかね。composerの使い方はぐぐれ。

WPの場合は、functions.phpにコードを書く感じになるので、テンプレートディレクトリで実行すればよいかと思います。で、functions.phpの冒頭くらいに

require __DIR__.'/vendor/autoload.php';

とでも書いておけば良し。インストールが簡単です。

つかってみましょう

まず接続設定をします。

$cfg = new \Spot\Config();
$cfg->addConnection( 'mysql', 'mysql://ユーザー:パスワード@ホスト名/DB名' );
$spot = new \Spot\Locator($cfg);

DSNで書くとこんなかんじ。連想配列での指定ももちろん可能です。
設定はWPの接続情報を使い回すとかでもいいでしょう。

モデルを用意する

ORMなのでモデルクラスを用意します。
僕の場合は、functions.phpの並びに「Entity」フォルダを用意して、その中にモデルクラスを配置しました。オートローダーをとかを作るほどでもないので functions.phpに

$files =  glob( __DIR__ .'/../Entity/*.php') ;
if(!is_array($files)){ $files = array(); }
foreach( $files as $one ){
    require_once $one;
}

を追記します。
Entityの中身を一気にロードしちゃっています。もちろん必要時に読むというやり方のが正しいとは思います。

Modelファイルの中身は

 ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
        ];
    }
}

仮にこんなかんじです。fieldsになにかないと怒られることがある?っぽいので、primaryのIDだけ定義しておきました。

呼び出しに使ってみる

最後に実際にDBにクエリしてみましょう。
呼び出しをするfuncitonの中で

function hoge(){
    global $spot;
    $postMapper = $spot->mapper('Entity\Products');
    $row = $postMapper->where([
        'item_id' => '品番'
    ])->first();
    return $row;
}

こんな感じにかきます。上記例では商品DBに品番を問い合わせる。的な事をしています。

実際組み込んでいるがWPと融合しているわけではない

ここまで読むとPHPerならおわかりになるとおもいますが、WordPressのオブジェクトの他に、DBに対して接続を一つたてることになります。したがって、メモリ効率などは悪くなります。手軽さと、パフォーマンスどちらを優先するかなどで、選択することになるとおもいます。
しかし、DB操作をして、サイトデザインをWPに合わせるなどを考えると、有用ですし、SQLを書くよりはセキュリティ面でも安心してかけますね。

現場からは以上です。

超簡単なテンプレートエンジン(条件分岐なし)ならCodeigniterからパクれ

どうもどうも。今年はそこそこブログを書いています。体調が戻ってきました。よかったよかった。決して万全ではないのですが、1年近く体調がわるかったので、まぁそのいろいろアレです。

ところで、PHPerのみなさん。
たとえば、管理画面でメールテンプレートを管理するような場合、
そのテンプレートをどう処理するか、結構悩みますよね。

同じようなかんじで、WordPressなんかで、囲みタグ型のショートコードでループをさせたいときとかもかなり悩みます。

よくある例だと、

  1. 管理画面からテンプレート編集
  2. POSTされたデータをDBに保存しつつ、テンプレートエンジン用に、ファイルにも保存
  3. 出力時に保存されたファイルをテンプレートエンジンで処理して出力

というような手順でしょうか。
まぁ悪くはないですが、セキュリティに気をつけなければいけないとか、結構手間取りますし、ファイル出力だと手軽さがなくなります。

今回はWordPressのショートコードでどうしようかなーと思ったときの対処をメモ代わりに。

やりたいこと

前提条件です。

  • WordPressの囲みタグ型ショートコードをテンプレートとして扱いたい
  • テンプレートエンジンとして動かしたいが、PHPを書くのや、Smartyのようにファイル処理を前提としない
  • 条件分岐はとりあえずいらないがループはある

という前提条件で、具体的には

[get_product_categories category="カテゴリ名"]
<ul>
    {categories}
    <li>
        <a href="{category_url}">
           {category_name}
        </a>
    </li>
{/categories} 
</ul>
[/get_product_categories]

こんなショートコードを書いてもらってテンプレート処理をしたいわけです。
囲みタグ型のショートコードについてはみなさまが書いているので、そちらをご参考に

まぁ普通にやれば

このような条件を満たそうと思うと、難しいコードでもないので、自分で簡単なテンプレートエンジンを書いてもいいとは思うのですが、いまいち気乗りがしません。自分で書くとすれば、正規表現を書いて、ゴニョゴニョとするところではありますよね。

そこで!Codeigniterの簡易パーサークラスをぱくってきます

Codeigniterには条件分岐はないですがループに対応して、ファイル処理なしで実行できる簡易テンプレートンジンがついています。これが使えないかと思ってソースを見たところ、Codeigniter本体にはほとんど依存していないことがわかり、少しの書き換えで対応ができることがわかりました。

四の五の言わずにコードを晒せ

はい。

<?php
class CI_Parser {

    /**
     * Left delimiter character for pseudo vars
     *
     * @var string
     */
    public $l_delim = '{';

    /**
     * Right delimiter character for pseudo vars
     *
     * @var string
     */
    public $r_delim = '}';


    // --------------------------------------------------------------------

    /**
     * Class constructor
     *
     * @return void
     */
    public function __construct(){
    }

    // --------------------------------------------------------------------

    /**
     * Parse a template
     *
     * @param  string
     * @param  array
     * @param  bool
     * @return string
     */
    public function parse( $template, $data, $return = FALSE ){

        return $this->_parse($template, $data, $return);
    }

    // --------------------------------------------------------------------

    /**
     * Parse a template
     *
     * Parses pseudo-variables contained in the specified template,
     * replacing them with the data in the second param
     *
     * @param  string
     * @param  array
     * @param  bool
     * @return string
     */
    protected function _parse( $template, $data )
    {
        if ($template === '')
        {
            return FALSE;
        }

        $replace = array();
        foreach ($data as $key => $val)
        {
            $replace = array_merge(
                $replace,
                is_array($val)
                    ? $this->_parse_pair($key, $val, $template)
                    : $this->_parse_single($key, (string) $val, $template)
            );
        }

        unset($data);
        $template = strtr($template, $replace);

        return $template;
    }

    // --------------------------------------------------------------------

    /**
     * Set the left/right variable delimiters
     *
     * @param  string
     * @param  string
     * @return void
     */
    public function set_delimiters($l = '{', $r = '}')
    {
        $this->l_delim = $l;
        $this->r_delim = $r;
    }

    // --------------------------------------------------------------------

    /**
     * Parse a single key/value
     *
     * @param  string
     * @param  string
     * @param  string
     * @return string
     */
    protected function _parse_single($key, $val, $string)
    {
        return array($this->l_delim.$key.$this->r_delim => (string) $val);
    }

    // --------------------------------------------------------------------

    /**
     * Parse a tag pair
     *
     * Parses tag pairs: {some_tag} string... {/some_tag}
     *
     * @param  string
     * @param  array
     * @param  string
     * @return string
     */
    protected function _parse_pair($variable, $data, $string)
    {
        $replace = array();
        preg_match_all(
            '#'.preg_quote($this->l_delim.$variable.$this->r_delim).'(.+?)'.preg_quote($this->l_delim.'/'.$variable.$this->r_delim).'#s',
            $string,
            $matches,
            PREG_SET_ORDER
        );

        foreach ($matches as $match)
        {
            $str = '';
            foreach ($data as $row)
            {
                $temp = array();
                foreach ($row as $key => $val)
                {
                    if (is_array($val))
                    {
                        $pair = $this->_parse_pair($key, $val, $match[1]);
                        if ( ! empty($pair))
                        {
                            $temp = array_merge($temp, $pair);
                        }

                        continue;
                    }

                    $temp[$this->l_delim.$key.$this->r_delim] = $val;
                }

                $str .= strtr($match[1], $temp);
            }

            $replace[$match[0]] = $str;
        }

        return $replace;
    }

}

Codeigniterのコントローラーオブジェクトに依存しているところなどを削って純粋にclassとして動くようになっています。

じゃWPで動かしてみよう

functions.phpあたりで、上記ファイルをrequireして

add_shortcode( 'get_product_categories', 'get_product_categories');

functionget_product_categories( $atts, $content = null ){

    //このへんで適当に引数処理してね    
        
    $parser = new CI_Parser();
    $args = array( ここに適当に条件かいてね );
    $the_query = new WP_Query($args);
    if (!$the_query->have_posts()) {
        return '';
    }
    
    $data = array();
    $data['categories'] = array();//ショートコードのループタグと名前を合わせる
    while ( $the_query->have_posts() ) {
      $the_query->the_post();
      $set = array(
        'category_name' => get_the_title(),
        'category_url' => get_the_permalink(),
      );
      $data['categories'][] = $set;
    }
    wp_reset_postdata();
    
    
    $content = $parser->parse( $content, $data );//最後にパースする。
    return $content;
}

とでもしておけば、簡単ね。

多分改造でifくらいはいける

上記の通りCodeigniterのパーサーはシンプルにできてます。
たぶん改造すればifくらいは余裕でかけると思います。
が、他方、毎回実行時にパーサーが走るわけなので、決して軽くはありません。
使い所見極めていきましょう。

なお、Codeigniter3.xはMITライセンスなのでOKですが、2.xまでのコードを使う場合は独自ライセンスなので、扱いに気をつけてください。

現場からは以上です。

楽天PayオーダーAPIをPHPからコールしてみた

どうもこんにちは伊藤です。きよっちです。半年ぶりの更新となりました。真面目に書けよ!と思われるかもしれませんが、まったくそのとおりですね。ちょっといろいろ思うところがございまして、沈んでましたが、それはまたおいおい。

はい。ところで。楽天さんの動きが激しいですね。楽天Payへの移行などなど。やらなくてはいけないことがたくさん。ちょうど今年2018年はヤマト運輸さんの配送料金大改革などなどもありましたし、Instagram上で通販が可能になったりしましたし、環境は大きく揺れ動いています。日本のECがはじまって以来、最も大きな変革期に来ているのかもしれません。ECを主戦場にしている業者さんは、自社の立ち位置、目指す場所、戦い方(戦わないという選択肢も含めて)などを明確にできる機会かもしれません。

ちょっと話がズレてしまいましたが、さて、楽天は改革を迎えているわけで、個人情報の扱い厳格化、決済方法の統一化などがあり、なるべく多くの受注操作をRMS上で行わなければならないようになってきています。

そんなこともあり、最近では、自社開発DBとの連携のために、RMS Web Service APIを利用しなければならないということもかなり増えてきました。今日はそのお話です。

RMSのAPI

まず、もってですね、楽天のRMSのAPIというのは、みなさんご存知でしょうか。2012年ごろからスタートしているRMS用のAPIです。それより以前は、公開情報(つまり商品情報ですね)を取得して、楽天外のwebサイトで活用できるようになっていたわけですが、RMS APIができてからは、RMSを楽天のシステム外で利用できるようになっています。(もちろん自社運営の楽天サイトの情報しかとることはできません) 経緯はこのあたりにくわしいです。

このRMSのAPIというやつがですね、わたしたちwebをメインにしているエンジニア・プログラマには、あまり馴染みのない「SOAP」という規格のAPIを採用しています。こいつがなかなか厄介で、webで多く使われているPHPでは、コードが冗長になりがちなのです。

そのコードについてはこちらのサイトにてサンプルが書かれていますので、参考になります。わたしも最初は参考にしました。

楽天PayOrderのAPI

上記のように、まぁぶっちゃけて言うと大変に分かりづらいSOAPを仕様したRMS APIなんですが、一回実装してしまえば、そうそう変更するものでもないので良しとしていました。

ここからが本題です。

ところがですね、楽天Pay移行により注文情報取得APIを変えなければならなくなりました。2018年以降、楽天各店は決済方法統一のため楽天Payに強制移行となりますので、既存APIを利用していた事業者さんは、いずれ、のりかえが必要ということです。 楽天Payへの移行については、こちらの記事がよくまとめられています

しかし、文句ばかりも言っていられませんので、楽天へ出店しているクライアントさんのために、API改造に着手しました。いつもそうなんですが、楽天さんのエンジニア向けドキュメントは、ぜんぜんまとまってなくて、どこにいけばわかるのかがわからない。たどり着いたとしても、実は全体像はそこだけではわからない…など前途多難なのです。

とりあえずドキュメントにたどり着いたので、従前のOrderAPIを書き換えてみる。

…あれ…なんか変だ…動かない…というよりは、楽天から返ってくる書式が変だ…
…ん???
…あれ??????
(ここまで1時間)

ようやく気付いたのですが、楽天PayOrderAPIだけが、基底となっている技術そのものを変えてきやがったのです!!!もう「やがった」といいますが許してください!

よく読めと言われれば、それまでですが、楽天PayOrderAPIだけは「JSONベースのSOAP」なのです。APIさわったことのあるエンジニアならわかるとおもいますが「お前何いってんだよ!」ってなりました。「SOAP」はもともとXML通信インターフェイスだろうよ!!!リクエストもレスポンスもJSONでやるのに、技術そのものはSOAPなのです。この矛盾に気づくのに時間がかかりました。(ここから想像するに、おそらく楽天PayのAPIの基底フレームワークはIBMのCICSだと思われます。もう少し標準的な仕様にしてくれませんかね???)

とっととコードを出せ

はい。。。ごめんなさい。。。取り乱しました。

class RMS {
    
    public static $serviceSecret;
    public static $licenseKey;
    
    // ----------------------------------------------------
    /**
     * オーダー取得
     */
    public static function searchOrder(){
        
        ////認証キーを作成
        // oAuthのが1289倍楽なんですけど!!!
        $authkey = "ESA " . base64_encode( self::$serviceSecret . ':' . self::$licenseKey );
        
        ////検索日付
        // そんなもん必須にせずに良きに計らえや!!!!
        // しかもいちいちGMTに変換しなきゃあかんのかい!!!それも良きに計らえよ!!!!
        // 第二引数で日本時間指定できるようにしておいた
        $beginDate = gmdate( 'Y-m-d\TH:i:s+0900', strtotime('2018-07-05 00:00:00'));
        $endDate = gmdate( 'Y-m-d\TH:i:s+0900', strtotime('2018-07-29 00:00:00'));
        
        //// なぜか楽天ペイAPIだけ しかも形の上ではSOAPなのに JSONでリクエスト…
        // 統一してくれんかな!!!ライブラリ書くのめんどくさいんだけど!!!!
        $requestJson = json_encode([
            'dateType' => 1,//期間検索種別
            'startDatetime' => $beginDate,//検索対象期間先頭日時(普通startじゃなくてbeginじゃねぇか??)
            'endDatetime' => $endDate,//検索対象エンド点
            'orderProgressList'=> [ 100, 200, 300, 400 ],//取得したいオーダーステータス
        ]);
        
        
        //// リクエストヘッダ作成
        // うーん なんかこの認証いまいちだなー・・・
        $header = [
            'Authorization: '.$authkey,
            'Content-Type: application/json; charset=utf-8',
        ];
        
        
        //// SOAPリクエスト
        // この辺はREST APIでのPOSTと一緒。ただし、レスポンスコードで結果コードをとるので
        // レスポンスヘッダについては厳重目にとっておくべき。
        $url = 'https://api.rms.rakuten.co.jp/es/2.0/order/searchOrder/';
        $curl = curl_init($url);
        curl_setopt( $curl, CURLOPT_POST, true );
        curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $curl, CURLOPT_HTTPHEADER, $header );
        curl_setopt( $curl, CURLOPT_POSTFIELDS, $requestJson );
        curl_setopt( $curl, CURLOPT_HEADER, true);//これを指定するとレスポンスにヘッダがついてくる
        
        // curlセッションを実行 ヘッダが付いているので分解
        $response = curl_exec( $curl );
        
        // ステータスコード取得
        $statusCode = curl_getinfo( $curl, CURLINFO_HTTP_CODE );
        
        // ヘッダサイズを取得して、その分を切り取ってしまう方策
        // もう少しいい方法があるかもしれない??
        $headerSize = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
        $requestHeader = substr( $response, 0, $headerSize);
        $requestBody = substr( $response, $headerSize );//ここに結果JSONがかえる
        
        
        //// jsonをオブジェクトにー
        $return = json_decode( $requestBody );
        
        curl_close( $curl );
               return $return;
        
    }
    
    // ----------------------------------------------------
    
}

ざっくりとclassにしました。モダンなコードではないですし、何かが漏れていますが気にしないでください。
引数でパラメータを指定できるようにするなどの改造は必要だとおもいますし、いくつかのエンドポイントを実装するにあたってはPOST部分を独立メソッドにすべきでしょう。そこは、皆さんのセンス。

このクラスをロードしておいて

<?php
RMS::$licenseKey = 'ライセンスキー';
RMS::$serviceSecret = 'シークレットキー';
$res = RMS::searchOrder();

とかでだいたい勝てます。

以上、現場からでした。
「室井さん。どうして現場に血が流れるんだ!」(←古い)

楽天の注文フォームに無茶させるver.2

6年ほど前に楽天の注文フォームに無茶をさせるという記事をかきました。

なんかちょいちょい利用されているようでして、、、
こんな記事でも役に立つのかっていうはなしなんで、
ちゃんと最新版をつくっておきます。

何をトチくるったかajaxでカートに入れられるようになりました

以前は、フォームを作成しなければ、
カートに入れることができなかったのですが、
現在は楽天が、ajax経由で、カートにいれることができる仕組みを用意しました。
なかなかトチ狂っています。
最近は遷移なしでカートに入れるUIが流行っているので、
そのために準備したのでしょう。

仕様

以前の方法と同じく、ショップIDと商品番号(RAC番号)を特定しておく必要があります。
詳しくは以前の記事を。

パラメータは、

{
  'shopid': ショップID(整数値),
  'units': 個数(整数値),
  'itemid': 商品番号(整数値),
  'dbasket_choice_select[]': 'オプション文字列',
  'dbasket_choice_select[]': 'オプション文字列2',//オプション文字列は複数可能
}

です。(上記はjavascriptのオブジェクトで表現しています)

これをajaxするだけです。
宛先は
http://direct.step.rakuten.co.jp/rms/mall/cartAdd/
です。

jQuery前提で、書くなら、

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
})
.then(function(data){
    //ここで結果を解析
});

という感じです。

ポイントは「json」ではなく「jsonp」にするところ。まぁこれは並のフロントプログラマなら意味がわかると思うので、割愛します。

えっと。。。。なんで「GET」で動いちゃうんですかね!?楽天さん!!!
HTTPの定義では、GETはデータを取得する目的で動くメソッドのはず、、、、
何かしら、データやシステム動作に影響を与える処理の場合は「POST」にしましょうね。。
楽天さん。まぁそんなこといいだしたら、CSSの仕様とか色々アレですかね!!!

というDISはここまでにして、、、、

さて返り値は

{
   addItemMessage: true,
   cartURL: null,
   dialogTitle: "買い物かごに追加されました",
   includedPostage: false,
   includedTax: false,
   itemName: null,
   itemPageUrl: null,
   price: null,
   resultCode: "0",
   resultMessage: ""
}

こんな感じの結果がかえってきます。

なんとなく見ると分かるのですが「dialogTitle」とあるように
本来は、楽天が公式的にサイト内でダイアログを表示するために使うAPIのようです。

で!またこのjsonが何だかなな仕様でして
resultCodeが「0」だと成功です(しかもそして文字列です)
そんでエラーがあればresultMessageに値が入ります。

したがって、resultCodeとresultMessageを見れば、結果がわかります。

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': ショップID(整数値),
      'units': 個数(整数値),
      'itemid': 商品番号(整数値),
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  }
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

みたいな感じですかね。あ!そもそもHTTPのエラーが帰った場合の処理は上記コードには入っていませんから注意してください。

実践!!

仮に、楽天でたまたま見つけた
https://item.rakuten.co.jp/kadenshop/4111-snw-1567/?s-id=top_normal_rk_hashist_realtime
こちらのページの商品をカートに入れてみましょう。

$.ajax({
  url: 'http://direct.step.rakuten.co.jp/rms/mall/cartAdd/',
  type: 'get',
  dataType: 'jsonp',
  data: {
      'shopid': 244202,
      'units': 1,
      'itemid': 10248876,
      'dbasket_choice_select[]': 'オプション文字列',
      'dbasket_choice_select[]': 'オプション文字列2'
  }
})
.then(function(data){
  if( data.resultCode == '0'){
    alert('OK');
  }
  else{
    alert( 'error!: ' + data.resultMessage );
  }
});

ですねー!

ショップIDと商品番号は、商品ページのカゴに入れるボタン周辺に
input type=hidden

「shop_bid」と「item_id」という名前で表示されているので、
ソースから引っ張ってこれます。

つまり他人の店でも、カートに入れる処理ができます。
まぁ何の意味もないですが。。。

で、先程の説明どおり、なんでか知らないですけど、
これGETで通っちゃうので、

http://direct.step.rakuten.co.jp/rms/mall/cartAdd/?shopid=244202&units=1&itemid=10248876&dbasket_choice_select[]=オプション文字列&dbasket_choice_select[]=オプション文字列2

このURLをブラウザ直打ちでも動作確認できます。。。おいおい(´・ω・`)

どうですか?

無茶をさせているようですが、結構使える技術です。
見積結果として複数の商品を一気にカートに入れたい場合などにかなり重宝をします。
以前の記事の頃ですと、formをsubmitするなどしなければならず
セキュリティソフトウェアがブロックするなどの問題がありそうでしたが、
これならその危険性はないです。

クルーズ旅行に出てみた!出発まで編


2018年一発目のネタづくりとして、というのはアレですけども、生まれて初めて船での長旅に出てみようと思いました。7泊8日の船の旅、名古屋を出て、那覇、台湾の基隆、宮古島を経て東京へいく旅です。

いまはその旅の最後の夜。船の中でこの記事を書いています。

その感想を書いてみましょう。

なんで乗ろうと思ったのか

クルーズの旅に出ようと思ったのは、テレビ朝日で放送されている『陸海空 地球征服するなんて』という番組で、大西洋を渡るクルーズ船にのって、さまざまな人に会ってみようという企画を見たのがきっかけです。

船旅というのは、時間もかかり不便で、金持ちの道楽か何かだろう。ぶっちゃけるとそんなイメージを持っていました。が、しかし、テレビをみて、こんなにたくさんのひとに会えるのは楽しいかもしれない。そして、これだけ不便な生活を一回やってみよう。そんな風に思ったわけです。

なんとなーく調べてみると、クルーズ旅行は、いくつかのクラスがあり、外国船籍のカジュアルクラスの船ならば、2人1室での旅なら1泊10,000円程度、1人でも1泊18,000円程度で、食事つきの旅に出ることができるということを知りました。これは現実的だ。というわけで申し込んだわけです。

しかし不安は大きい

あるとき旅行代理店から電話がかかり「旅行代が安くなった」と連絡をうけました。そう言われても数千円だと思いますよね。ふつう。「45,000円のお値引きでございます」。えっ??どういうこと??何の割引???特に説明はありませんでしたが、先に振り込んでおいた旅行代金から45,000円が返ってきました。さすがにこの価格となると、安全は大丈夫なのかと不安が出てきます。

2018年に入り、いよいよ出発が近づくと、いろいろ不安も出てきました。さすが、ともいうべきなのか、海外のクルーズ専門会社、しかもカジュアルクラス。乗船券が届かない(笑)代理店に聞いても、発行してくれないんですよ(泣)みたいな反応で、おいおいと思いました。が、しかし、船に乗った後に、クルーズ旅行の慣れているひとによると、よくある話だそうです。ははは

しかし、乗船券が届いてみると、まさかの普通紙にレーザープリンタで印刷された乗船券が届くという、なんとも「カジュアル」な展開。それどころか、荷物を預けるためのタグですら普通紙。びっくりびっくりびっくり。

webのお仕事をしている身からすれば、船内でのインターネット接続については気になるところ。代理店に聞いたところ「あくまでも目安なんですが従量制で250MBあたり10USDです」との返答。ちょっと待ってくださいよ!!従量制なのはいいですけど、価格が目安ってどいうことですか!!?? なんでも、乗る船によって異なるとか…は。。。は。。。

言い知れぬ不安だけが大きくなって来ましたw そもそも、クルーズ旅行は「わざわざ行く」と決心しないかぎりは、現代の長い人生の中でも、一生行かないタイプの旅行です。ほかの旅行とは、準備がちがいますから、仕方ないですね。。。

しかし、こういった不安は、ほとんどが余計な不安に終わるわけですが、後日この不安は、実は別の悪いことへの予感だったのです。後日おはなししましょう。

さぁ出発だ。

2018年1月20日。いよいよ出発の日を迎える。あらかじめ届いた案内には、「名古屋港ガーデンふ頭に来てください。12時くらいから乗れます」とだけ。。。あいかわらず不案内ですな。というかんじで、水族館いくか、花火を見に行くかでもない限りいかない、名古屋港ガーデンふ頭へ。

近づいていくと、oh!見たことのない大きな船が!これに乗るのかとおもうと、ワクワクしてきました。そこまで考えていた不安は飛びました。というか「でっかい山があるから登ってやれ!話はそれからだ!」と言う感じかもしれませんが。

乗船に関して、審査というのは、あまり厳しくはなかったです。金属探知機を通りますが、航空機ほど厳密ではないです。その代わりなのかわかりませんが、パスポートのチェックは何重にもなっていました。港湾局、船会社、船に同上セキュリティの3回のチェックでした。

びっくりしたのが、パスポートは船のスタッフによって人質にとられます。航空機とは本当にいろいろちがうんですね。

船にのってみると



カジュアルクラスの船とはいえ、さすが洋上のホテルというつくりです。船は12フロア構造。わたしが泊まるのが、フロア6「ロンドンフロア」と名付けられたフロアです。部屋にはいるとシングルベッドが2つ。十分な広さの居住空間が保たれており、なかなかに快適そうです。

11フロアにはプールとジャグジーとバー、軽食エリアがあります。なんと開放的!!

ちなみに、わたしは最初迷いましたし、他の多くのお客さんも迷っていたのですが、食事は3食付で、出発日は夜だけという案内なのですが、実は、ブッフェは営業時間内であれば、いつでも好きなだけ食べられます。中途半端な時間に食事を取ることができずに船に乗った場合は、どうぞ最初ぶブッフェへいってみてください。

ゆったりするぞー!と言う感じがひしひしと湧いてきたところで、「ぶぉーーーーー」と汽笛が鳴り、出航となりました。

プライスレスな体験のはじまりを体感しました。マスターカード。
そんなわけで続きをどこかで書きましょうー。

出発まで編のまとめ

  • カジュアルクラス外国船籍のクルーズなら、1人でも1泊18,000円で食事付き。そして、謎の大幅割引がつく
  • わりとざっくりとした案内だが、そういうものだと思うべき
  • 価格の割にはしっかりした居住空間
  • 出発からして、プライスレスな体験
  • 船のセキュリティは航空機と違う。高飛びに使えるかも。
Older posts