PHPでBuilderパターン コンストラクタに渡すパラメーターの数を時に応じて変えたい

オブジェクト指向あるある…

必須にしたいパラメーターを引数にしてコンストラクタに渡すのですが、引数が時に応じて数が変わると、その都度コンストラクタの違うバージョンを書かなきゃいけないという問題があります。

例えば、ここでの例を述べると、配送計画(Delivery Plan)というオブジェクトには、IDと日付と距離は必ず入りますが、車両IDとドライバー名はある時もないときもあります。

そういう時、たいてい下記のようにコンストラクタをコピペして初期パラメーターを設定しますね。

class DeliveryPlanBuilder{
private $id;
private $date;
private $distance;

//オプションパラメーター
private $car_id;
private $driver_name;

function __construct()
    {
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this,$f='__construct'.$i)) {
            call_user_func_array(array($this,$f),$a);
        }
}

public function __construct3($id, $date, $distance){
    $this->id = $id;
    $this->date = $date;
    $this->distance = $distance;
}

public function __construct4($id, $date, $distance, $car_id){
    $this->id = $id;
    $this->date = $date;
    $this->distance = $distance;
    $this->car_id = $car_id;
}
public function __construct5($id, $date, $distance, $car_id, $driver_name){ 
    $this->id = $id; 
    $this->date = $date; 
    $this->distance = $distance; 
    $this->car_id = $car_id; 
    $this->driver_name = $driver_name; 
} 
}

まぁ、3,4個のパラメーターならコピペでもいいかもしれませんが、10個とかなってくると、いくつもコンストラクタを作らないといけないのが苦痛ですね!

ここで、デザインパターンのBuilderパターンを使うといいお!というのが、

「Effective Java」

という本の第二章の2に紹介されております。もちろん、Javaでやる方法はこの本に紹介されています。

これをPHPで実装したい、というのが今回の目的です。

こちらのStack Overflowさんを参考にさせて頂きました。m(_ _)m

https://stackoverflow.com/questions/10961673/php-builder-pattern-without-inner-classes

class DeliveryPlan{ 
   private $id; 
   private $date; 
   private $distance; 
   //オプションパラメーター 
   private $car_id; 
   private $driver_name; 

static function createBuilder($id, $date, $distance) { 
    return new DeliveryPlanBuilder($id, $date, $distance); 
} 

function __construct(DeliveryPlanBuilder $builder){ 
    $this->id = $builder->getId();
    $this->date = $builder->getDate();
    $this->distance = $builder->getDistance();
		
    //オプションパラメーター
    $this->car_id = $builder->getCarId();
    $this->driver_name = $builder->getDriverName();
    }
	
public function getDriverName(){
    return $this->driver_name;
    }
		
}

class DeliveryPlanBuilder{
	private $id;
	private $date;
	private $distance;
	
	//オプションパラメーター
	private $car_id;
	private $driver_name;
	
	public function __construct($id, $date, $distance){
		$this->id = $id;
		$this->date = $date;
		$this->distance = $distance;
	}
	
	public function car_id($val){
		$this->car_id = $val;
		return $this;
	}
	
	public function driver_name($val){
		$this->driver_name = $val;
		return $this;
	}
	
	public function build(){
		return new DeliveryPlan($this);
	}
	
	public function getId(){
		return $this->id;
	}
	
	public function getDate(){
		return $this->date;
	}
	
	public function getDistance(){
		return $this->distance;
	}

	public function getCarId(){
		return $this->car_id;
	}
	
	public function getDriverName(){
		return $this->driver_name;
	}
}


$delivery_plan = DeliveryPlan::createBuilder(1, "2018/07/24", 150)->build();

var_dump($delivery_plan);

$delivery_plan_with_driver_name = DeliveryPlan::createBuilder(1, "2018/07/24", 150)->driver_name("山田太郎")->build();

var_dump($delivery_plan_with_driver_name);

$driver_name = $delivery_plan_with_driver_name->getDriverName(); 

var_dump($driver_name);

//下記は必須の引数がないのでエラーになります。
$delivery_plan_test_no_parameters = DeliveryPlan::createBuilder()->build();

出力は下記の通りです。

object(DeliveryPlan)[2]
  private 'id' => int 1
  private 'date' => string '2018/07/24' (length=10)
  private 'distance' => int 150
  private 'car_id' => null
  private 'driver_name' => null

object(DeliveryPlan)[3]
  private 'id' => int 1
  private 'date' => string '2018/07/24' (length=10)
  private 'distance' => int 150
  private 'car_id' => null
  private 'driver_name' => string '山田太郎' (length=12)

string '山田太郎' (length=12)

後はエラー

ちなみに、私はBuilderパターン大好きで、デザインパターンのなかで一番多く使ってると思います。

作るときは面倒ですが、一度作っちゃった後のメンテナンスの容易さがたまらないです。

オブジェクト作るときに、->build()って作るのもなんとなくかっこいいのだ(`・ω・´)

Javaでも、他の言語でも、なんとかかんとかビルダー->build() ってやって作るオブジェクトは、このBuilderパターンで作られているわけですね。

UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position

Pythonで頻発するこのエラー…。

Unicodeとか、バイト文字列とかいいから、さっさと直したい!!(´ω`)

…。はい、その声にお答えします。

.encode(‘utf-8’)をつけてしまいましょう。(´ω`)

下記はサンプルです。

print('日本語やでー'.encode('utf-8'))

 

Python 日付の名前のファイルを作るサンプルコード

またまた、Pythonのコピペ用コードです。

日付の名前のファイルを作りたいとき、ありますよね?ログ取ったりとか。

そういう時にちゃちゃっとファイルを作るやつです。

import os
import datetime


def saveTrainExamples(str):

    folder = "C:\\hogehoge\\test_dir"
    if not os.path.exists(folder):
        os.makedirs(folder)
    filename = os.path.join(folder,  datetime.datetime.today().strftime('%Y-%m-%d') + ".txt")
    with open(filename, "a") as f:

        str = "\n" + str
        f.write(str)


saveTrainExamples("What a little monkey!")

 

ちなみに、私は開発はWindowsでやってますが、年月日の形式を

filename = os.path.join(folder,  datetime.datetime.today().strftime('%Y/%m/%d') + ".txt")

でやっていたら、下記のようなエラーが出ました。

FileNotFoundError: [Errno 2] No such file or directory: ‘C:\\hogehoge\\test_dir\\2018/07/19.txt’

なんでなんで??と思っていたら、そっか、Windowsでは/の入ったファイル作れないんですね…。

 

Let’s Encryptでワイルドカード証明書を発行する

Let’s Encryptがワイルドカード証明書に対応したので発行してみました。

下記コマンドでワイルドカード証明書を発行できます。

# certbot-auto certonly --manual \
  -d *.example.com -d example.com \
  --preferred-challenges dns-01 \
  --server https://acme-v02.api.letsencrypt.org/directory

すると、下記のように認証用にDNSのTXTレコードの追加を要求されます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

vduUg7XfNjwQ8hCEHZD0owLHo6Z-sf9Wmm8w5345maY

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

要求されたとおりにTXTレコードを追加します。

_acme-challenge.example.com. 300 IN TXT "vduUg7XfNjwQ8hCEHZD0owLHo6Z-sf9Wmm8w5345maY"

TXTレコードの追加がDNSサーバーに反映された後に、Enterを押します。

TXTレコードの確認するには下記コマンドを実行します。

# dig _acme-challenge.example.com TXT

TXTレコードの検証が成功すれば、ワイルドカード証明書が発行されます。

参考URL: https://community.letsencrypt.org/t/getting-wildcard-certificates-with-certbot/56285

コピペ用 Python ミリ秒を時:分:秒の文字列に変換する

コピペ用のサンプルコードです。
ミリ秒を時:分:秒のフォーマットにして返します。

def millSecToStr(millSeconds):
    m, s = divmod(millSeconds / 1000, 60)
    h, m = divmod(m, 60)
    str_duration = "%d:%02d:%02d" % (h, m, s)

    return str_duration


#例
str_time = millSecToStr(10000000)
print(str_time)

#2:46:40 が出力されます。