Hiroto's diary

プログラミング関連を書くかも

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に完全に移行しました。

f:id:Hiroto-K:20180207171531j:plain

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がデータベースを作成します。

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

iTunesで大きいサイズのジャケットを取得するブックマークレット

iTunesでサイズの大きいジャケット(アートワーク)を取得するブックマークレットを書いた。iTunesのアートワーク自動取得機能が使えない時や、ジャケットのサイズの大きい物にしたい時などに使えます。

iTunesで大きいサイズのジャケットを取得する。 · GitHub

ブックマークに登録するコード

未圧縮のコード

実際の動作。新しいタブで開きます。

RubyのThorで設定出来ないコマンド名を無理矢理設定する

Rubyで簡単にCLIツールを作れるThor。

github.com

Thorを使って、runshellなどのコマンド名でコマンドを作ろうとすると怒られます。

test-run.rb

require 'thor'

class Cli < Thor

  desc 'run', 'Run command.'
  def run
    puts 'Running "run" command.'
  end

  desc 'shell', 'Shell command.'
  def shell
    puts 'Running "shell" command.'
  end

end

Cli.start(ARGV)

実行すると…

$ ruby test-run.rb
/path/to/ruby/2.4.0/gems/thor-0.20.0/lib/thor/base.rb:557:in `is_thor_reserved_word?': "run" is a Thor reserved word and cannot be defined as command (RuntimeError)
    from /path/to/ruby/2.4.0/gems/thor-0.20.0/lib/thor/base.rb:620:in `method_added'
    from test-run.rb:6:in `<class:Cli>'
    from test-run.rb:3:in `<main>'

と怒られる。

設定出来ないメソッド名

Thor::THOR_RESERVED_WORDSで定義されているワードが使えません。

具体的には

invoke
shell
options
behavior
root
destination_root
relative_root
action
add_file
create_file
in_root
inside
run
run_ruby_script

です。

runshellは割りと使うので普通に困る。(startconsoleなどの別のコマンド名にすれば回避出来るとかは無しで…)

解決策

適当なメソッド名で書いた上で mapを使って紐付け

どうせ紐付けるのでメソッド名は何でも良いんですが、後ろに_commandとでも付けておくのがベタ。

require 'thor'

class Cli < Thor

  desc 'run', 'Run command.'
  def run_command
    puts 'Running "run" command.'
  end

  # runコマンドをrun_commandに紐付け
  map 'run' => 'run_command'

  desc 'shell', 'Shell command.'
  def shell_command
    puts 'Running "shell" command.'
  end

  # shellコマンドをshell_commandに紐付け
  map 'shell' => 'shell_command'

end

Cli.start(ARGV)

実行してみる。

$ ruby test-run.rb
Commands:
  test-run.rb help [COMMAND]  # Describe available commands or one specific com...
  test-run.rb run             # Run command.
  test-run.rb shell           # Shell command.

$ ruby test-run.rb run
Running "run" command.

$ ruby test-run.rb shell
Running "shell" command.

これで怒られずにrunshellコマンドを作成出来ます。

Twitterで280文字のツイートをする

Twitterは9月26日に、ツイート可能な文字数を140文字から280文字に増やすための実験を開始すると発表。

blog.twitter.com

しかし、対象ユーザーは無作為に選ばれる上に日本語, 中国語, 韓国語は対象ではありません

APIにパラメータをつければ誰でも280文字ツイートが出来たので280文字投稿の方法を紹介。

この記事ではPHPCowitterを用いて投稿しますが、他の言語やライブラリでもパラメーターをつければ投稿可能です。

280文字の投稿方法

簡単に言ってしまうとツイートを投稿するAPIであるstatuses/updateを叩く際、パラメーターにweighted_character_count=trueを付ければ投稿可能です。

とりあえずサンプルコードを書いてみます。

<?php

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

use mpyw\Cowitter\Client as CowitterClient;

// ランダムな文字列を生成。
function rand_str($len = 10) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789';
    $str = '';
    for ($i = 0; $i < $len; ++$i) {
        $str .= $chars[mt_rand(0, 61)];
    }
    return $str;
}

$client = new CowitterClient(['CK', 'CS', 'AT', 'ATS']);

// パラメーターにweighted_character_count=trueを付けて280文字をツイート。
// また、ツイートの文字数が140文字を越すためtweet_mode=extendedを使用。
$params = [
    'status' => '280文字ツイート'. PHP_EOL. rand_str(270),
    'tweet_mode' => 'extended',
    'weighted_character_count' => true,
];
$response = $client->post('statuses/update', $params);

// 280文字全てを取得するため、textではなくfull_textを使用。
echo 'text : '. $response->full_text. PHP_EOL. PHP_EOL;
echo 'length : '. mb_strlen($response->full_text);

実行すれば分かると思いますが、これだけで誰でも280文字投稿が可能です。

ここで注意が必要なのは、140文字オーバーのツイートを投稿する時や取得する時にはパラメーターにtweet_mode=extendedが必要になることです。詳しくは以前書いた以下の記事をどうぞ。

hiroto-k.hatenablog.com

エラーメッセージ

280文字を越してAPIを叩いても、エラーメッセージは140文字を超えている時と同じメッセージが返ってきます。

<?php

try {
    // statusに295文字を使用。
    // 280文字を超えているのでもちろんエラーが返ってきます。
    $response = $client->post('statuses/update', ['status' => '280文字以上のツイートテスト'. rand_str(280), 'tweet_mode' => 'extended', 'weighted_character_count' => true]);

    echo 'text : '. $response->full_text. PHP_EOL. PHP_EOL;
    echo 'length : '. mb_strlen($response->full_text);
} catch (\Exception $e) {
    var_dump($e->getMessage());}
}

実行してみると

Status is over 140 characters.

とエラーが返ります。

© 2015-2017 Hiroto-K