Hiroto's diary

プログラミング関連とイベントの感想を書くかもしれない

Laravelのpublicディレクトリを変更する。

タイトルの通り、Laravelのpublicディレクトリを変更する方法。hiroto-k.netサブドメイン統合に際してpublicディレクトリを動かしたのと、その後のテストでコケたのでその時のメモです。

先に書いておくと、この方法ではphp artisan serveコマンドは使えず、Homestead等で開発する必要が出てきますのでご了承ください。


まず最初に変更後のディレクトリ構成。サブドメイン毎でpublicディレクトリを分ける為、publicの中にapinetresourcesを掘り、既にあるindex.php等をコピーしました。。

f:id:Hiroto-K:20180607005533p:plain

次に各ディレクトリのindex.phppath.publicをバインドします。少し面倒ですが全てのindex.php$appの下に書き加えます。

<?php

// 省略

$app = require_once __DIR__.'/../../bootstrap/app.php';

$app->bind('path.public', function () {
    return realpath(__DIR__);
});

// 省略

書き換え後、Homesteadのsitestoで新しいpublicディレクトリの位置を指定してリロードすれば完了。HTTPからのアクセスでは、public_path等の返り値が設定したディレクトリで帰ってきます。

テスト時のディレクトリ設定

で、ここまでHTTPからのアクセスは何の問題も無いのですが、このまま何も設定せずにphpunitを走らせると、path.publicはデフォルトのpublicで帰ってきます。コントローラーなどでpublic_pathを使っていると確実にコケて非常に厄介です。

という訳でTestCaseからpublicディレクトリを変更出来るようにします。

TestCase.php$publicPathSuffixプロパティを生やすのと、createApplicationpath.publicをバインドする処理を追加。

<?php

use Illuminate\Contracts\Console\Kernel;
use Laravel\BrowserKitTesting\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{

    // 追加
    /**
     * Suffix of public path.
     *
     * @var string
     */
    protected $publicPathSuffix;

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        // 追加
        if (isset($this->publicPathSuffix)) {
            $newPublicPath = public_path($this->publicPathSuffix);
            $app->bind('path.public', function () use ($newPublicPath) {
                return $newPublicPath;
            });
        }

        $app->make(Kernel::class)->bootstrap();

        return $app;
    }
}

コントローラー等のテストではpublicPathSuffixを設定。

AppsControllerTest.php

<?php

class AppsControllerTest extends TestCase
{
    protected $publicPathSuffix = 'net';
}

これでphpunitを走らせると、publicPathSuffixを設定したテストケースではpublicディレクトリの位置が変更されてテストが実行されます。

Laravelの .env の値はconfig()経由で使う。

結論から言うと…

  • env()は コントローラー, モデル, etc.. 内で直接使わない。
  • config/*.phpenv()の値を入れてconfig()から参照する。

<?php
// config/my-app.php

return [
    // configに.envの内容を入れる。
    'my-env' => env('MY_ENV'),
];
<?php
// コントローラー内など

// config() を使用。
$my_env = config('my-app.my-env');

// これはダメなパターン。
$my_env = env('MY_ENV');

何故 env() を使ってはいけないのか

本番環境でconfig:cacheコマンドを実行した際、.envファイルを読み込まないから。コレに尽きます。

.envファイルはIlluminate\Foundation\Bootstrap\LoadEnvironmentVariablesクラスのbootstrap()で読み込まれるのですが、読み込む前にconfigのキャッシュの有無を確認し、キャッシュがあった場合は.envファイルを読み込まない仕様になっています。

当該部分のLaravelのコードを引用。

<?php
// 省略

    public function bootstrap(Application $app)
    {
        if ($app->configurationIsCached()) {
            return;
        }
        $this->checkForSpecificEnvironmentFile($app);
        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        } catch (InvalidFileException $e) {
            die('The environment file is invalid: '.$e->getMessage());
        }
    }

// 省略

github.com

大体の場合の本番環境では、高速化の為に設定を一纏めにするconfig:cacheコマンドを実行すると思うのですが、前述の通りキャッシュがあると.envが読み込まれないので、env()を直接叩いてると開発時やテスト時には動くけど本番環境で死ぬ。といった事になります。(実際なった)

実際に試す

適当なコントローラーを用意してddを使って値をダンプするだけ。

<?php
class HogeController extends Controller
{
    public function getHoge()
    {
        dd(
            env('MY_ENV'),
            config('my-app.my-env')
        );
    }
}

まずはキャッシュ無しでアクセス。

"Foo"

"Foo"

テスト環境や開発環境と同じ様にキャッシュを作成していないのでどちらも同じ値が取得できます。

次にキャッシュさせてアクセス。artisanconfig:cacheを実行してbootstrap/cache/config.phpを作成します。

php artisan config:cache

で、先ほどと同じ様にアクセスしてみる。

null

"Foo"

.envファイルが読み出されていないのでenv()で取得した所で値が入ってる訳もなくnullを返します。


と言う訳で最初に書いた通り、env()は直接使わずにconfig().envの値を入れて使いましょう

iTunesの音楽とプレイリストをWALKMANに入れる

一ヶ月ほど前、iPodの容量不足に伴い音楽プレイヤーをiPodからWALKMANのNW-A47に完全に移行しました。

iTunesの音楽やプレイリストをWALKMANに移行する方法は調べれば大量に出てくるものの、その方法でやると管理が面倒…ってのが沢山だったのと、色々躓く所があったので、自分的に管理がしやすい方法を書きます。

音楽ファイルとプレイリストはSDには入れずに全てWALKMANのストレージに入れました。


音楽ファイルの移行

音楽ファイル(flac, alac, mp3, aac など)をWALKMANに転送します。

ジャケットの設定

ファイルを転送する前にしておきたいのがジャケット(iTunesではアートワーク)の設定。WALKMANでジャケットが表示されなくても良いと言う方はやる必要無し。

iTunesではCDを取り込むと自動でジャケットを検索して設定してくれる便利な機能がありますが、この機能は実際にファイルにジャケットを設定してる訳ではなく、ジャケットを取得して別ファイルに保存してiTunesで表示する時のみジャケットを出しているだけです。(この方が音楽ファイルのサイズを削減出来る。)

なのでこのまま転送するとWALKMANではジャケットが表示されません。

そして更に厄介なのがWALKMANでジャケットを表示する場合はジャケットの形式がベースラインJPEGである必要がある事。手動で設定した画像がPNGプログレッシブJPEGだと表示されません。


これを解決する為には手動でジャケットを設定する必要があります。

iTunesで購入した曲に関しては最初からベースラインJPEGでジャケットが設定されています のでやる必要がありません。CDから取り込んだ曲インターネットからダウンロードした曲などのみ設定します。

方法は色々あるのですが、楽なのが既に入ってるジャケットを変換する方法。iTunesが勝手に持ってきた時や既にジャケットを手動で設定した時はこれが楽です。

話が逸れますが、音楽はiTunesにあるのにジャケットが自動で取得されなかった場合はブックマークレットを使って取得すると楽です。

hiroto-k.hatenablog.com

話を戻してジャケットの設定。

アルバムを指2本クリックして「アルバムの詳細」を開き、ジャケットをデスクトップに放り投げて変換したものを再度設定するだけ。

  • ジャケットを取り出し。 f:id:Hiroto-K:20180205183139g:plain

  • ジャケットの画像をToyViewerなどでベースラインJPEGに変換する。

  • 一旦iTunesでジャケット削除した後、変換したものを再度入れる。 f:id:Hiroto-K:20180205183322g:plain

これで保存するとWALKMANでもジャケットが表示されるようになります。


ファイルの移動

SONYのサイトには

iTunesの楽曲リストから転送したい楽曲を選択して、ウォークマンの[MUSIC]フォルダーにドラッグ&ドロップするだけです。

と書いてあります。

www.sony.jp

実際出来るのですが、これでやると曲の数が増えるとファイルを探しにくくなる、曲名と番号が被った際に入れられなくなる等の問題が起きます。

と言う訳で、iTunesライブラリから直接持ってきて整理しやすくします。

ファイルは全て/MUSIC/Music/へ入れました。コピーすると/MUSIC/Music/アーティスト名/アルバム名/音楽ファイルの構成になります。/MUSIC/へ直接入れると既にあるディレクトリ(MUSICCLIPNWWM_REC)と混ざると厄介なのと、後述のプレイリストのファイルも探しにくくなるためです。

WALKMANでは音楽ファイルを/MUSIC/ディレクトリから8階層まで辿ってくれます。無駄にサブディレクトリを掘りすぎると8階層を超えるので、ディレクトリの掘りすぎにだけ注意が必要です。(上記の方法では4階層。音楽の管理がiTunes単一ならそこまで深く掘る必要はないですが…)


参考までに自分の場合の構成。MusiciTunesから持ってきた音楽ファイル、Playlistには後述のプレイリストファイルを格納。 f:id:Hiroto-K:20180211231154p:plain


実際に移行します。

まず始めにiTunes Mediaの位置を確認します。この位置はプレイリストを入れる時に必要になるので、プレイリストを入れる場合は適当な場所にメモしておきます。

  • iTunesの環境設定の詳細を開く。"iTunes Media"フォルダの場所をコピーしておきます。 f:id:Hiroto-K:20180211230520p:plain

  • FinderでiTunes Mediaを開く。Command + Shift + Gでパスを入力すると楽です。 f:id:Hiroto-K:20180211230644p:plain

  • 移動したら音楽ファイルが入っているMusicディレクトリへ。アーティスト名/アルバム名/音楽ファイルの構造で入っています。

  • タブや新しいウィンドウでWALKMANを開く。

  • WALKMAN側の音楽ファイルを入れるディレクトリに移動し、ファイルをコピー。自分の場合はMusicディレクトリを掘ってそこへ入れたので、/MUSIC/Music/へコピーしました。

コピーした後の/MUSIC/Music/ f:id:Hiroto-K:20180211231410p:plain

これでファイル移動は完了。

プレイリストを移行

既にiTunesにあるプレイリストを変換して内容をそのまま持ってきます。

プレイリストのエクスポート

iTunesでプレイリストを開いてm3u形式でエクスポートし、適当な作業ディレクトリに保存します。


  • ファイル -> ライブラリ -> プレイリストを書き出しをクリック f:id:Hiroto-K:20180211230256p:plain

  • 保存。フォーマットはM3Uを選択します。

    • ちなみにWALKMANで表示されるプレイリスト名はこのファイル名となります。 f:id:Hiroto-K:20180211230300p:plain

文字コードの変換とパスの置き換え

プレイリストをエクスポートしてそのままWALKMANに入れても文字コードや音楽ファイルのパスが合わないので何も表示されません。

エディターなり何なりで開いて変換と置き換えでも出来ますが、自分は面倒なので適当なスクリプトを書いてプレイリストを更新する度に手動で実行しています。

プレイリストの文字コードを変換 & パスを置き換えるbashスクリプトbash convert-playlist.shでカレントディレクトリのm3uファイルを全て変換&置き換え。

convert-playlist.sh

#!/bin/bash
set -eu

find . -maxdepth 1 -type f -name "*.m3u" | while read file
do
    file_name="$(basename "${file}")"
    tmp_file_name="${file_name}.tmp"

    echo "Move file ${file_name} to ${tmp_file_name}"
    mv "${file_name}" "${tmp_file_name}"

    echo "Run nkf"
    echo "Output to ${file_name}"
    cat "${tmp_file_name}" | nkf --ic=UTF8-MAC > "${file_name}"

    echo "Remove ${tmp_file_name}"
    rm "${tmp_file_name}"

    echo "Replace music file path"
    ruby replace-file-path.rb "${file_name}"

    echo ""
done

パスを置き換えるスクリプトitunes_pathreplace_toは自分のを入れてください。

またreplace_toは音楽ファイルが絶対パスになるようなパスが望ましいです。相対パスだとプレイリストファイルの位置によって音楽ファイルを読み込まなくなります。

replace-file-path.rb

require 'pp'
require 'pry'

itunes_path = ''.freeze
replace_to = ''.freeze

if ARGV.length == 0
    puts 'Argument is empty.'
    exit!(1)
end

file = ARGV[0]
file_realpath = File.join(__dir__, file)

unless File.exists?(file_realpath)
  puts "File #{file_realpath} does not exist."
  exit!(1)
end

puts "File : #{file}"
puts "File realpath : #{file_realpath}"

new_file_string = []

puts "Read #{file_realpath}"
File.open(file_realpath, 'r') do |f|
    f.each_line("\r") do |line|

        # コメントを読み飛ばし
        if line.start_with?("#")
            new_file_string << line
        end

        if line.start_with?(itunes_path)
            new_line = line.gsub(itunes_path, replace_to)
            new_file_string << new_line
        end

    end
end

puts "Override #{file_realpath}"
File.open(file_realpath, 'w') do |f|
    f.write(new_file_string.join("\n"))
end

m3uファイルの移動

/MUSIC/フォルダの下にm3uファイルをコピー。絶対パスで書いている限り、8階層以内ならどこに置いても同じです。

自分の場合は前述の通り音楽ファイルとの混合を防ぐ為、/MUSIC/Playlist/へ入れました。 f:id:Hiroto-K:20180211231702p:plain

WALKMANをPCから取り出す

PCから取り出します。外すとWALKMANがデータベースを作成します。

音楽、プレイリストが全てコピー出来ていれば完了です。

© 2015-2018 Hiroto-K