audio/mpeg

MPEGは「音声、映像関連の規格」
MP3は「音声データを扱うときの規格のひとつ」
MIMEタイプは「ファイルの種類を表す情報」

mp3ファイルはaudioタグで囲う

<h1>MP3の再生</h1>
<audio src="test.mp3" controls></audio>

PHPで浜崎あゆみの昔のアルバムを聞きまくろう

10個リクエストを送ります。
重複してレスポンスが返ってくるので、array_uniqueで重複を削除します。

<?php

require 'vendor/autoload.php';

$session = new SpotifyWebAPI\Session(
    'id',
    'secret',
    'http://192.168.33.10:8000/callback/'
);

$api = new SpotifyWebAPI\SpotifyWebAPI();

$count = 10;

if (isset($_GET&#91;'code'&#93;)) {
    $session->requestAccessToken($_GET['code']);
    $api->setAccessToken($session->getAccessToken());


    $tracks = $api->search('浜崎あゆみ', 'track', array(
        'limit' => $count
        ));
 
// print_r('<pre>');
// var_dump($tracks);
// print_r('</pre>');
    for($i=0; $i<$count; $i++){
        $track_id&#91;&#93; = $tracks->tracks->items[$i]->album->id;
    }


    // print_r($track_id);
} else {
    $options = [
        'scope' => [
            'user-read-email',
        ],
    ];

    header('Location: ' . $session->getAuthorizeUrl($options));
    die();
}

$track_id = array_unique($track_id);
foreach($track_id as $value){
    echo "<iframe src=\"https://embed.spotify.com/?uri=spotify:album:".$value."\" width=\"300\" height=\"380\" frameborder=\"0\" allowtransparency=\"true\"></iframe>";
}
?>

あ、これは結構いい。昔の曲が聞ける。

LUNA SEA行ってみよう。 mothersはかっこよかった。

一曲全部じゃないのが惜しいが。

PHPで浜崎あゆみを聞いてみよう

spotify api
$api->searchでtrackが返ってきます。

<?php
require 'vendor/autoload.php';
$session = new SpotifyWebAPI\Session(
    'id',
    'secret',
    'http://192.168.33.10:8000/callback/'
);

$api = new SpotifyWebAPI\SpotifyWebAPI();
if (isset($_GET&#91;'code'&#93;)) {
    $session->requestAccessToken($_GET['code']);
    $api->setAccessToken($session->getAccessToken());
    $tracks = $api->search('浜崎あゆみ', 'track', array(
        'limit' => 1
        ));
    $track_id = $tracks->tracks->items[0]->album->id;
    print_r($track_id);
} else {
    $options = [
        'scope' => [
            'user-read-email',
        ],
    ];
    header('Location: ' . $session->getAuthorizeUrl($options));
    die();
}
// print_r('<pre>');
// var_dump($tracks);
// print_r('</pre>');
?>
<h1>spotify</h1>
<iframe src="https://embed.spotify.com/?uri=spotify:album:<?php echo $track_id; ?>" width="300" height="380" frameborder="0" allowtransparency="true"></iframe>

まあまあいいんだけど、全部サビ前からサビの30秒位なんだよな。
1曲全部聞けないと、物足りない感が尋常ではない。

あ、iframeこんな感じです。うーん、違うな。やっぱりイントロから聞けないと絶対ダメでしょ。

spotify api invalid redirect uri

index.php

$session = new SpotifyWebAPI\Session(
    'client id',
    'client secret',
    'http://192.168.33.10:8000/callback'
);

spotify

invalid URIのレスポンスが返ってくるため、stackoverflowを見ていたら、

stackoverflow
https://stackoverflow.com/questions/32956443/invalid-redirect-uri-on-spotify-auth
app.js:
var redirect_uri = ‘http://localhost:8888/callback’;
Spotify > My application:
http://localhost:8888/callback/

それでもエラーで
なんでだろうと思っていたら、
myappとlocalで、callback urlは完全一致(バックスラッシュ)にしないといけないようです。
http://192.168.33.10:8000/callback/
http://192.168.33.10:8000/callback/

泣きたくなってきた。

mysql テーブル連携

まず、一つ目のテーブルを作くってデータを入れます。

create table cake.base(
	id int unsigned auto_increment primary key,
	code int,
	name varchar(255)
);

insert into base(code, name) values
('3689', 'イグニス'),
('4579', 'ラクオリア創薬'),
('3825', 'リミックスポイント');

二つ目のテーブルを作って、こちらにもデータを挿入。

create table cake.data(
	id int unsigned auto_increment primary key,
	code int,
	price int,
	time datetime default null
);

insert into cake.data(code, price, time) values
('3689', '3240', now()),
('3689', '3245', now()),
('3689', '3250', now()),
('3689', '3245', now());

INNER JOINでカラムを指定して結合する
SELECT base.code, base.name, data.price, data.time FROM base INNER JOIN data ON base.code = data.code;

OUTER JOIN
SELECT base.code, base.name, data.price, data.time FROM base LEFT OUTER JOIN data ON base.code = data.code;
dataが入っていないラクオリア創薬、リミックスポイントもselectされます。

更にテーブルを連結する場合も、joinでok
create table cake.market(
id int unsigned auto_increment primary key,
code int,
market varchar(255)
);

insert into market(code, market) values
(‘3689’, ‘マザーズ’),
(‘4579’, ‘ジャスダック’),
(‘3825’, ‘東証二部’);

SELECT base.code, base.name, data.price, data.time, market.market FROM base JOIN market ON base.code = market.code JOIN data ON base.code = data.code;

cake migration

// making migration 
[vagrant@localhost myblog]$ bin/cake bake migration_snapshot Initial

[vagrant@localhost myblog]$ bin/cake migrations status
using migration paths
 - /home/vagrant/fw/myblog/config/Migrations
using seed paths
 - /home/vagrant/fw/myblog/config/Seeds
using environment default

 Status  Migration ID    Migration Name
-----------------------------------------
     up  20180306042845  Initial


[vagrant@localhost myblog]$ bin/cake bake migration CreateComments post_id:integer body:string created modified

Creating file /home/vagrant/fw/myblog/config/Migrations/20180306043056_CreateComments.php
Wrote `/home/vagrant/fw/myblog/config/Migrations/20180306043056_CreateComments.php

[vagrant@localhost myblog]$ bin/cake migrations migrate
`

mysqlで確認

mysql> use cake
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------+
| Tables_in_cake |
+----------------+
| comments       |
| phinxlog       |
| posts          |
+----------------+
3 rows in set (0.00 sec)

すげー

commentController

<?php

namespace App\Controller;

class CommentsController extends AppController
{
	public function  add()
	{
		$this->viewBuilder()->layout('my_layout');
		$comment = $this->Comments->newEntity();
		if($this->request->is('post')){
			$comment = $this->Comments->patchEntity($comment, $this->request->data);
			if($this->Comments->save($comment)){
				$this->Flash->success('Comment Add Success!');
				return $this->redirect(['controller'=>'Posts','action'=>'view', $comment->post_id]);
			}else {
				$this->Flash->success('Error');
			}
		}
		$this->set('post', $comment);
	}

	public function delete($id = null)
	{
		$this->viewBuilder()->layout('my_layout');
		$this->request->allowMethod(['post', 'delete']);
		$comment = $this->Comments->get($id);
		if($this->Comments->delete($comment)){
			$this->Flash->success('Comment Delete Success!');
			
		}else {
			$this->Flash->success('Error');
		}
		return $this->redirect(['controller'=>'Posts','action'=>'view', $comment->post_id]);

	}
}
?>

cake update

controllerでgetして、$this->Posts->save

public function edit($id = null)
	{
		$this->viewBuilder()->layout('my_layout');
		$post = $this->Posts->get($id);
		if($this->request->is(['post','patch','put'])){
			$post = $this->Posts->patchEntity($post, $this->request->data);
			if($this->Posts->save($post)){
				$this->Flash->success('Edit Success!');
				return $this->redirect(['action'=>'index']);
			}else {
				$this->Flash->success('Error');
			}
		}
		$this->set('post', $post);
	}

deleteは $this->Posts->deleteになる

public function edit($id = null)
	{
		$this->viewBuilder()->layout('my_layout');
		$this->request->allowMethod(['post', 'delete']);
		$post = $this->Posts->get($id);
		if($this->Posts->delete($post)){
			$this->Flash->success('Delete Success!');
			
		}else {
			$this->Flash->success('Error');
		}
		return $this->redirect(['action'=>'index']);

	}

cake form helper

<?= $this->Form->create($post); ?>
<?= $this->Form->input('title'); ?>
<?= $this->Form->input('body', ['rows'=> '3']); ?>
<?= $this->Form->button('Add'); ?>
<?= $this->Form->end(); ?>

なるほど、だんだんテンションが下がってきた。

DBへのinsertをcontrollerで書きます。

public function  add()
	{
		$this->viewBuilder()->layout('my_layout');
                $post = $this->Posts->newEntity();
		if($this->request->is('post')){
			$post = $this->Posts->patchEntity($post, $this->request->data);
			$this->Posts->save($post);
			return $this->redirect(['action'=>'index']);
		}
		$this->set('post', $post);
	}

validatorはmodelに書きます。

<?php

namespace App\Model\Table;

use Cake\ORM\Table;
use Cake\Validation\Validator;

class PostsTable extends Table
{
	public function initialize(array $config)
	{
		$this->addBehavior('Timestamp');
	}

	public function validationDefault(Validator $validator)
		{
			$validator
			->notEmpty('title')
			->requirePresence('title')
			->notEmpty('body')
			->requirePresence('body')
			->add('body', [
				'length' => [
					'rule' => ['minLength', 10],
					'message' => 'body length must be 10+'
					]
				]);
		return $validator;
	}
}

controllerでvalidationの設定

public function  add()
	{
		$this->viewBuilder()->layout('my_layout');
		$post = $this->Posts->newEntity();
		if($this->request->is('post')){
			$post = $this->Posts->patchEntity($post, $this->request->data);
			if($this->Posts->save($post)){
				return $this->redirect(['action'=>'index']);
			}else {
				// error
			}
		}
		$this->set('post', $post);
	}

cake Linkの作成

index.ctp

Htmlヘルパーを使う場合
Html->linkとする テーブルのカラムを指定し、コントローラーとアクションを作成。
うーん、慣れるまで時間がかかりそう。

<?php foreach($posts as $post):?>
		<li><?= $this->Html->link($post->title, ['controller'=>'Posts', 'action'=>'view']); ?></li>
	<?php endforeach; ?> 

ブラウザで確認。反映されてますね。

urlヘルパーを使う場合

<a href="<?= $this->Url->build(['action'=>'view', $post->id]); ?>">
		<?= h($post->title); ?></a>

Htmlヘルパーの方がコード量が少なく楽そうに見えます。

cake Element

共通ヘッダーをつくります。
app/src/Template/Element/my_header.ctp

<header>My Blog</header>

作ったelementをレイアウトファイルに書き込みます。
app/src/Template/Layout/my_layout.ctp

<!DOCTYPE html>
<html>
<head>
    <?= $this->Html->charset() ?>
    <title>
        <?= $this->fetch('title') ?>
    </title>
    <?= $this->Html->css('styles.css') ?>
</head>

<body>
		<?= $this->element('my_header') ?>
        <section class="container">
        <?= $this->fetch('content') ?>
        </section>

</body>
</html>

サーバーを再起動します。
bin/cake server -H 192.168.33.10 -p 8000

ブラウザ読み込みます。なるほど、エレメントはパーツですね。

PostController.phpはAppControllerのclassをextendしているので、AppControllerのクラスに記載してもOK
$this->viewBuilder()->layout(‘my_layout’);