PHP

【CakePHP2.4】BoostCakeプラグインの導入手順まとめ。

めも。

1, まずは下記URLからソースをダウンロード。

https://github.com/slywalker/cakephp-plugin-boost_cake

2, ファイルを解凍後、プラグインが梱包されたフォルダ名を「BoostCake」に変更し下記ディレクトリに格納。

app/Plugin/

3, 「app/Config/bootstrap.php」に下記を追記。

CakePlugin::load('BoostCake');

4, 「app/Controller/AppController.php」に下記を追記。

class AppController extends Controller {
	public $helpers = array(
		'Session',
		'Html' => array('className' => 'BoostCake.BoostCakeHtml'),
		'Form' => array('className' => 'BoostCake.BoostCakeForm'),
		'Paginator' => array('className' => 'BoostCake.BoostCakePaginator')
	);
}

5, レイアウトファイルのhead要素内で下記ファイルを読み込む。

echo $this->Html->script('//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')."\n";
echo $this->Html->css('bootstrap')."\n";
echo $this->Html->script('bootstrap')."\n";

6, 以上で導入は完了。使い方は書きURLを参照されたし。

http://slywalker.github.io/cakephp-plugin-boost_cake/bootstrap3.html

 

【CakePHP】find()のconditionsでNOT条件を指定する。

めも。

$this->{$this->modelClass}->find('all', array(
	'conditions' => array(
		'NOT' => array(
			$this->modelClass.'.id' => array(1, 2, 3, 4)
		)
	)
));

こんな感じ。

 

【PHP】配列の中からランダムに一つの要素を取得する。

めも。

$array = array('hoge', 'piyo', 'fuga');

$one = $array[array_rand($array, 1)];

 

【CakePHP】App::uses()の注意点。

めも。

App::uses()はクラス名とファイル名が一致していないと読み込めない。

TwitterのOauthライブラリを読み込もうとした際に、クラス名とファイル名の命名規則が異なっていたため一向に読み込めないという問題が発生。

大文字、小文字を確りと判別しているため、挙動に違和感を感じたら素直に統一させるべし。

 

【CakePHP2.4】DataSource::update()は常にfalseを返す。

これはほんとに要注意。

DBへのsave()処理時、返り値で成功失敗判定を行っている部分があったため一向に通過しないif文が発生してしまった。

というお話。

今後のトラブルシューティング時に覚えておきたいTips。

 

【CakePHP2.4】SessionComponent::setFlash()でエレメントを使う。

めも。

まず「app/View/Elements/」以下に好きなエレメントを作成する。

そしてコントローラーでsetFlash()をする際、下記のようにエレメントを呼び出す。

// elementFileName.ctpというエレメントを作成したと仮定する
$this->Session->setFlash('メッセージ', 'elementFileName');

これで作成したエレメントが呼び出される。

エレメント内では$messageに第一引数で渡したメッセージが格納されているので、任意の場所でechoしてやればよい。

 

【PHP】配列で二つの値が共に重複しない新規配列を返す関数。

必要だったので自作。

例えば以下のような配列があったとする。

$array = array(
	0 => array(
		'name' => 'tarou',
		'age' => '17',
	),
	1 => array(
		'name' => 'tarou',
		'age' => '19',
	),
	2 => array(
		'name' => 'tarou',
		'age' => '17',
	),
	3 => array(
		'name' => 'hanako',
		'age' => '15',
	),
	4 => array(
		'name' => 'alice',
		'age' => '20',
	),
	5 => array(
		'name' => 'bob',
		'age' => '17',
	),
	6 => array(
		'name' => 'alice',
		'age' => '20',
	),
	7 => array(
		'name' => 'bob',
		'age' => '17',
	),
	8 => array(
		'name' => 'tarou',
		'age' => '19',
	),
);

ここで配列中のnameとageが共に重複するレコードのみ取り除きたい。
というわけで下記関数を用意。

function array_pair_unique($array, $one, $two) {
	$first = reset($array);
	if(!array_key_exists($one, $first) || !array_key_exists($two, $first) ) {
		return $array;
	}
	$tmp = array();
	$return = array();
	foreach($array as $k => $v) {
		if(array_key_exists($v[$one], $tmp) && in_array($v[$two], $tmp[$v[$one]])) {
			continue;		
		} else {
			$tmp[$v[$one]][] = $v[$two];
			$return[$k] = $v;
		}
	}
	return $return;
}

上記関数を以下のようにして用いる。

$array = array_pair_unique($array, 'name', 'age');

すると下記のような結果が返る。見事に思惑通り。

array(5) {
  [0]=>
  array(2) {
    ["name"]=>
    string(5) "tarou"
    ["age"]=>
    string(2) "17"
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(5) "tarou"
    ["age"]=>
    string(2) "19"
  }
  [3]=>
  array(2) {
    ["name"]=>
    string(6) "hanako"
    ["age"]=>
    string(2) "15"
  }
  [4]=>
  array(2) {
    ["name"]=>
    string(5) "alice"
    ["age"]=>
    string(2) "20"
  }
  [5]=>
  array(2) {
    ["name"]=>
    string(3) "bob"
    ["age"]=>
    string(2) "17"
  }
}

元配列のキーは保持され、重複したレコードがある場合はその中で一番最初にヒットしたもがセットされる。
関数を呼び出す際に存在しないキー名が渡された場合、元配列をそのまま返却するようにしてみた。

 

【PHP】anitube保存用クラスを作った。

動画保存系クラスシリーズ。主に自分用。

/**
 * anitube保存用クラス
 * 
 * $ani = new anitubeDownloader();
 * $ani->saveDir = '動画保存用ディレクトリ'; // 末端DS無し
 * $ani->tmpDir = '作業用ディレクトリ'; // 末端DS無し
 * $ani->prefix = 'ANITUBE_'; // 動画保存時のファイル名先頭プレフィックス文字列
 * $res = $ani->download('*******');
 * 
 * @author Owner
 *
 */
class anitubeDownloader {
	
	// 動画保存ディレクトリ(末端スラッシュ無し)
	public $saveDir = '';
	
	// 一時作業ディレクトリ(末端スラッシュ無し)
	public $tmpDir = '';
	
	// 動画保存時のファイル名に付与するプレフィックス文字列
	public $prefix = 'ani_';
	
	// VIDEO_ID(http://www.anitube.se/video/*******/...)
	protected $vid = '';
	
	// エラーメッセージの入れ物
	protected $errors = array();
	
	// 動画閲覧ページのベースURL
	protected $watchBaseUrl = 'http://www.anitube.se/video/';
	
	// 動画情報を取得するためのAPIURL
	protected $apiUrl = 'http://www.anitube.se/nuevo/config.php?key=';
	
	// APIURLを括るデリミタ文字(ユニークキーを含む)
	protected $delimiter = "'";
	
	// HD動画の取得に成功したか否か
	protected $isHd = false;
	
	/**
	 * ダウンロードの実行
	 * @param string $mid
	 * @return multitype:|boolean
	 */
	public function download($vid = null) {
		$this->vid = $vid;
		$this->checkParams();
		if(!empty($this->errors)) return $this->errors;
		$tmpWorkPath = $this->tmpDir . DIRECTORY_SEPARATOR . md5(microtime());
		$headerFilePath = $this->tmpDir . DIRECTORY_SEPARATOR . 'h_' . $this->vid;
		self::tp($tmpWorkPath);
		self::tp($headerFilePath);
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_URL, $this->watchBaseUrl.$this->vid);
		$output = curl_exec($ch);
		
		// 動画のユニークキーストリングを取得する
		$keyStr = $this->getKeyStr($output);
		
		// 上記で取得したキーを元にAPIを叩き、XMLを取得
		$xml = @simplexml_load_string(file_get_contents($this->apiUrl.$keyStr));
		if(!$xml) return false;
		
		// 取得する動画サイズを決定する。(hd or normal)
		$videoUrl = $this->getVideoUrl($xml);
		
		curl_setopt($ch, CURLOPT_URL, $videoUrl);		
		// 動画DLセクション
		$execResult = false;
		$hdFp = fopen($headerFilePath, 'w');
		if($hdFp) {
			curl_setopt($ch, CURLOPT_WRITEHEADER, $hdFp);
			$twFp = fopen($tmpWorkPath, 'wb');
			if($twFp) {
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
				curl_setopt($ch, CURLOPT_FILE, $twFp);
				$execResult = curl_exec($ch);
				curl_close($ch);
				fclose($twFp);
			}
			fclose($hdFp);
		}
		if(!$execResult) goto exception;
		
		// headerFileの解析(ファイルタイプ、拡張子を取得)
		$contentType = self::parseHeaderFile($headerFilePath);
		$ext = self::getFileExtension($contentType);
		// HDファイルのダウンロードに成功していた場合、ファイル名のプレフィックスにhd_を追加
		if($this->isHd) $this->prefix .= 'hd_';
		// 実ファイルのパスを指定(お名前指定)
		$filePath = $this->saveDir . DIRECTORY_SEPARATOR . $this->prefix . $this->vid . $ext;
		// ファイル移動
		rename($tmpWorkPath, $filePath);
		
		// 作業ファイル削除
		self::u($tmpWorkPath);
		self::u($headerFilePath);
		return true;
		
		if(false) {
			exception:
			self::u($tmpWorkPath);
			self::u($headerFilePath);
			curl_close($ch);
			return false;
		}	
	}
	
	/**
	 * ファイルの存在確認と作成、及び権限の付与
	 * @param unknown $path
	 */
	protected function tp($path) {
		if(!file_exists($path)) touch($path);
		chmod($path, 0777);
	}
	
	/**
	 * ファイルの存在確認と削除
	 * @param unknown $path
	 */
	protected function u($path) {
		if(file_exists($path)) unlink($path);
	}

	/**
	 * パラメーターチェック
	 */
	protected function checkParams() {
		if(!is_dir($this->saveDir)) array_push($this->errors, 'saveDir must be a directory.');
		if(fileperms($this->saveDir) != '16895') array_push($this->errors, 'saveDir required to be permission 777.');
		if(!is_dir($this->tmpDir)) array_push($this->errors, 'tmpDir must be a directory.');
		if(fileperms($this->tmpDir) != '16895') array_push($this->errors, 'tmpDir required to be permission 777.');
		if(empty($this->vid)) array_push($this->errors, 'vid is required but not set.');
		if(!self::validateVid($this->vid)) array_push($this->errors, 'VID must be numeric.');
	}
	
	/**
	 * 動画IDの正規バリデーション
	 * @param unknown $check
	 * @return boolean
	 */
	protected function validateVid($check) {
		$regexNumOnly = '/^[0-9]+$/';
		if (preg_match($regexNumOnly, $check)) return true;
		return false;
	}
	
	/**
	 * HTMLファイルをパースして動画のユニークキーを取得
	 * @param unknown $html
	 * @return Ambigous <NULL, string>
	 */
	protected function getKeyStr($html) {
		$key = null;
		$apiPos = stripos($html, $this->apiUrl);
		if($apiPos) {
			$st = $apiPos + strlen($this->apiUrl);
			$ed = strpos($html, $this->delimiter, $st);
			$l = $ed - $st;
			$key = substr($html, $st, $l);
		}
		return $key;
	}
	
	protected function getVideoUrl($xml) {
		if(!empty($xml->filehd)) {
			$this->isHd = true;
			return (string)$xml->filehd;
		}
		if(!empty($xml->file)) return (string)$xml->file;
		return false;
	}
	
	/**
	 * ヘッダーファイルの情報を解析
	 * @param unknown $path
	 * @return Ambigous <NULL, string>
	 */
	protected function parseHeaderFile($path) {
		$hd = file_get_contents($path);
		$ct = null;
		$k = 'Content-Type:';
		$st = stripos($hd, $k);
		if($st !== false) {
			$st += strlen($k);
			$ed = strpos($hd, "\n", $st);
			if($ed === false) $ed = strlen($hd);
			$l = $ed - $st;
			$ct = strtolower(trim(substr($hd, $st, $l)));
		}
		return $ct;	
	}
	
	/**
	 * コンテントタイプより動画ファイルの拡張子を分析し返却
	 * @param unknown $ct
	 * @return string
	 */
	protected function getFileExtension($ct) {
		$e = '';
		switch($ct) {
			case 'video/3gpp':
				$e = '.3gp';
				return $e;
			case 'video/mp4':
				$e = '.mp4';
				return $e;
			case 'video/x-flv':
			default:
				$e = '.flv';
				return $e;
		}
	}
}

例の如く使い方はクラス上部のコメントアウト参照。
といいつつ一応使い方メモ。

$ani = new anitubeDownloader();
$ani->saveDir = '/path/to/saveDir';
$ani->tmpDir = '/path/to/tmpDir';
$res = $ani->download('*****');
var_dump($res);

ニコ動にしろYoutubeにしろ、これ系のクラスはやってること少しずつしか変わらないからスクレイピングの部分だけ分岐させればよいかも?