December 23, 2014

OpenShiftのJenkinsでGitLabのソースを取得する

BitbucketとGitLabが異なるだけで、方法は全く以下の通りなのですが、自分メモとして書き残しておきます。(大変参考にさせて頂きました。)
http://www.techscore.com/blog/2013/10/15/openshift-online-jenkins-bitbucket/

要点は、OpenShiftの~/.ssh/はパーミッション上、書き込み不可なので、代わりにGIT_SSHを使う。となります。

1.
~/app-root/data/.ssh/jenkins_id_rsa.pub をGitLabに登録する
* もちろん、新しく作成してもOKです。

2.
~/app-root/data/.ssh/jenkins_id_rsa のパーミッションを600にする

3.
~/app-root/data/.ssh/gitssh.sh を作成する
#!/bin/sh
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $OPENSHIFT_DATA_DIR/.ssh/jenkins_id_rsa "$@"
4)
~/app-root/data/.ssh/gitssh.sh に実行権限を与える

5)
Manage Jenkins > Configure System > Environment variables に以下の設定をする
name: GIT_SSH
value: 作成したgitssh.shのパス

6)
Jenkinsでビルド実行してソースが取得できればOKです。取得できなければどこかが間違っています。

OpenShiftのJenkins ServerにCapistrano 3をインストールする

OpenShiftのJenkins Serverはrubyのバージョンが1.8.7で、Capistrano 3をインストールしようとしたらエラーになりました。

簡単な方法は無いかなーと考えてみたんですが、とりあえず以下の方法でインストール出来ました。

適当な場所にbundleをコピーして、実行するrubyを変更します。
> mkdir ~/app-root/data/bin
> cp /usr/bin/bundle ~/app-root/data/bin/
vim等で ~/app-root/data/bin/bundle の先頭の行を以下に変更します。
#!/usr/bin/ruby193-ruby
適当な場所にGemfileを作ります。
source 'https://rubygems.org'

gem 'capistrano', '~> 3.3.0'
インストールと初期化がうまく行けばOKだと思います。
> ~/app-root/data/bin/bundle install
> ~/app-root/data/bin/bundle exec cap install

OpenShiftでJenkins Serverのビルドが"Waiting for next available executor"で進まない

メモです。

タイトルのとおりなんですが

Manage Jenkins > Manage Nodes > master(の設定) > "# of executors" を 1 に(0だった)

したら、進んだ。。。

December 20, 2014

LaravelのBladeテンプレートで、ビューファイルと、依存するJSやCSSファイルを整理整頓する

WEB開発をしていると、画面単位で使用するJSファイルやCSSファイルをどう管理するか、悩むことが良くあるのではないでしょうか。

そのせいでコントローラが複雑になってしまったり、コンフィグ化して管理場所が深い位置になってしまったり、スッキリと解決するのがなかなか難しい問題だと思います。共通化に失敗すれば、苦痛にしかなりません。

最近、LaravelのBladeテンプレートを用いて、その辺りがかなり解決できた(気がしている)ので、その例のメモです。Bladeテンプレートでなくても、継承式のテンプレートエンジンであれば、似たような事はできるんだろうと思います。

以下、Laravel 4.2を用いていますが、バージョンはたぶんあまり関係ありません。

1) 全体のレイアウトを管理するビューファイルを作成する
views/layouts/default.blade.php
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
@yield('append_css')
@yield('append_js')
</head>
<body>

<section id="content">
<h2>content</h2>
@yield('content')
</section>

<section id="content2">
<h2>content2</h2>
@yield('content2')
</section>

<section id="content3">
<h2>content3</h2>
@yield('content3')
</section>

</body>
</html>
このレイアウトファイルは、各画面用のビューファイルで @extends('layouts.default') されます。ポイントは @yield() している箇所です。それぞれの使い方は以下です。

@yield('append_css') ... 画面単位で使用するCSSを追加する領域
@yield('append_js') ... 画面単位で使用するJSを追加する領域
@yield('content') ... コンテンツ領域
@yield('content2') ... コンテンツ領域2
@yield('content3') ... コンテンツ領域3

この例では、3つのコンテンツ領域を縦に並べているだけですが、実際にはヘッダ領域になったり、サイドナビ領域になったり、メイン領域になったりするイメージです。このビューファイルは、レイアウトの大枠しか知りません。

2) 画面用のビューファイルを作成する
views/contents/index/index.blade.php
@extends('layouts.default')

@section('content')
@include('contents/top/_foo')
@stop

@section('content2')
@include('contents/top/_bar')
@stop

@section('content3')
@include('contents/top/_baz')
@stop
それぞれのコンテンツ領域に、後述する細分化されたビューファイルを @include() して展開しています。このビューファイルは、使用するレイアウトと、各コンテンツ領域でどのコンテンツを使用するかしか知りません。

3) 各コンテンツのビューファイルを作成する
views/contents/index/_foo.blade.php
<h3>foo</h3>

<?php
append_css('assets/css/contents/foo.css');
append_js('assets/js/contents/foo.js');
?>
views/contents/index/_bar.blade.php
<h3>bar</h3>

<?php
append_css('assets/css/contents/bar.css');
append_js('assets/js/contents/bar.js');
?>
views/contents/index/_baz.blade.php
<h3>baz</h3>

<?php
append_css('assets/css/contents/baz.css');
append_js('assets/js/contents/baz.js');
?>
これらのビューファイルは、自身のコンテンツ内容しか知りません。それぞれ内容は適当ですが、ポイントは append_css() と append_js() です。これは自作の関数で、以下になります。
<?php

if (! function_exists('append_css')) {
    /**
     * @param  mixed $styles
     */
    function append_css($styles)
    {
        static $appended = [];

        if (is_scalar($styles)) {
            $styles = [$styles];
        }

        foreach ($styles as $style) {
            if (in_array($style, $appended)) {
                continue;
            }

            $appended[] = $style;

            View::startSection('append_css');
            echo HTML::style($style);
            View::appendSection();
        }
    }
}

if (! function_exists('append_js')) {
    /**
     * @param  mixed $scripts
     */
    function append_js($scripts)
    {
        static $appended = [];

        if (is_scalar($scripts)) {
            $scripts = [$scripts];
        }

        foreach ($scripts as $script) {
            if (in_array($script, $appended)) {
                continue;
            }

            $appended[] = $script;

            View::startSection('append_js');
            echo HTML::script($script);
            View::appendSection();
        }
    }
}
@section('append_js')
{{ HTML::script('assets/js/contents/bar.js'); }}
@append
の方法で append すると、他ビューで同名のファイルが append された時、重複して読み込まれてしまいます。その防止策として、上記のように関数化しています。

これで、細分化されたコンテンツ単位で、依存するJSやCSSファイルが明確になりました。そのコンテンツが不要になれば、セットで削除するだけです。尚、他のビュー用のJSと連携する場合は、独自イベントを介するのが好ましいと思います。(Backbone.js的な考え方かも。)チーム作業時にコンフリクトしにくくなるメリットもあります。

デメリットは、コンテンツファイルが増えるとJSやCSSもファイル単位で増えるので、その結果、読み込みファイル数が多くなることです。JSは要らない場合もありますが、CSSはどんどん増えるはずなので、似たような考え方でSass等で管理した方が良いかもしれません。(実際には別の事情でそうしています。)

以下、参考までに、Sassを用いた簡単な管理例です。

A) 各コンテンツ用のSCSSファイル (上記の3と対) を作成する
B) 画面単位のSCSSファイル (上記の2と対) を作成して A を @import する
C) 画面単位のビューファイル (上記の2) で B のCSSファイルを append_css() する
D) 当然、各コンテンツのビューファイルでは append_css() しない

全体的に大切なことは、とりあえず適切な単位でファイル分割しておいて、各所で好きに組み合わせて使えること。でしょうか。

この方法はコントローラ側に一切手を付けなくて済むので、わりとおすすめなのですが、もっと良い方法があればぜひ教えて下さい。

December 7, 2014

Android Studioが"Java not found"で起動しない(Mac)

久々にAndroid Studioを起動してバージョンアップすると、以下の"Java not found"エラーが出て全く起動しなくなってしまいました。

単純に、JDKのパス設定が間違っているだけなように見えますが、Android Studioそのものが起動しないので、設定ファイルを直接編集する必要がありそうでした。

調べてみると http://stackoverflow.com/questions/16636146/using-android-studio-with-java-1-7 に、まさにその通りな回答がありました。以下、メモです。

Android Studioの"Info.plist"というファイルを編集します。(brew caskでインストールしているので以下のロケーションになりますが、インストーラでインストールしていると/Applications下になります。)
$ vim /opt/homebrew-cask/Caskroom/android-studio/0.8.9/Android\ Studio.app/Contents/Info.plist
エラーの問題となっていたのは以下の箇所でした。

私の環境では1.6ではなく1.7がインストールされていたので、以下のように変更しました。

これで、正しく起動出来ました。

November 30, 2014

Laravel SocialiteでFacebookとTwitterの認証をしてみた

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、Laravel Socialiteを別途インストールすることで、Facebook,Twitter,Google,GitHub等と連携した認証(OAuth,OAuth2)を、簡単に実装できるようになります。
https://github.com/laravel/socialite

細かな書き方などは、以下がとても参考になりました。
https://laracasts.com/series/whats-new-in-laravel-5/episodes/9

以下、Facebookを用いた認証の簡単なサンプルです。

まず、予めFacebook側で適当にアプリを作成して、発行されたキーをプロジェクトルートの .env と config/services.php に設定しておきます。尚、.envはバージョン管理せずに、各環境でこっそりと作成するべきファイルです。

.env
FACEBOOK_CLIENT_ID=xxxxxxxxxx
FACEBOOK_CLIENT_SECRET=yyyyyyyyyy
config/services.php
// 以下を追記
'facebook' => [
    'client_id' => getenv('FACEBOOK_CLIENT_ID'),
    'client_secret' => getenv('FACEBOOK_CLIENT_SECRET'),
    'redirect' => url('facebook/callback'),
],
.envで設定した値はgetenv()で取得できます。CLIからの実行時でも取得できます。この機能は"PHP dotenv"を用いているので、同様のことは他のフレームワーク等でも簡単にできます。
https://github.com/vlucas/phpdotenv

Laravel Socialiteをインストールします。
$ composer require laravel/socialite:~2.0@dev
config/app.phpのprovidersにLaravel Socialiteのプロバイダを追記します。
'Laravel\Socialite\SocialiteServiceProvider',
routes.phpに、Facebook認証用のコントローラを追加します。以下、一例。
$router->controllers([
    'facebook' => 'FacebookController',
]);
app/Http/Controllers/FacebookController.php を作成します。
<?php namespace App\Http\Controllers;

use Laravel\Socialite\Contracts\Factory as Socialite;

class FacebookController extends Controller {

    /**
     * @var Socialite
     */
    protected $socialite;

    public function __construct(Socialite $socialite)
    {
        $this->socialite = $socialite;
    }

    public function getLogin()
    {
        return $this->socialite->driver('facebook')->redirect();
    }

    public function getCallback()
    {
        $user = $this->socialite->driver('facebook')->user();

        dd($user);
    }
}
以下の流れで、ユーザ情報が正しく表示されれば成功です。

* facebook/loginにアクセスしてFacebook側にリダイレクトされる。
* Facebook側で認証してfacebook/callbackにリダイレクトされる。
* ユーザ情報が表示される。

また、以下の形で、scopesの指定も出来ました。
return $this->socialite->driver('facebook')->scopes(['user_friends'])->redirect();
同じ流れで、Twitterの認証もうまくいきました。その他、GithubProviderやGoogleProviderも用意されているので、これらも同じような方法で上手くいくのかなと思います。
https://github.com/laravel/socialite/tree/master/src/Two

標準では用意されていないサービスでも、これらを参考にして簡単に自前できそうですね。

Laravel 5のイベントリスナのアノテーションを試してみた

この機能は廃止 https://github.com/laravel/framework/commit/4d9f92ef77b3610a05f71110f539daff06e9081d されたのかもしれません。

--

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、ルーティングだけでなく、イベントリスナもアノテーションで定義できるようになります。

細かな書き方などは、以下がとても参考になりました。
http://mattstauffer.co/blog/laravel-5.0-event-annotations

以下、サンプルです。

適当な場所に適当なクラスを作成します。
<?php namespace App\EventListeners;

use Illuminate\Contracts\Logging\Log;

class Foo
{
    protected $log;

    /**
     * @param  Log $log
     */
    public function __construct(Log $log)
    {
        $this->log = $log;
    }

    /**
     * @Hears("bar.baz")
     *
     * @param  mixed $params
     */
    public function qux($params)
    {
        $this->log->debug(print_r($params, true));
    }
}
$this->log->debug() の箇所は、Laravel 5のContractsという仕組みを用いてみましたが、ファサードを用いて\Log::debug()でも同じです。

ポイントは @Hears("bar.baz") の箇所で、従来の書き方だと以下になります。
\Event::listen('bar.baz', 'App\EventListeners\Foo@qux');
次に app/Providers/EventServiceProvider.php を編集して protected $scan をオーバーライドします。値は配列で、アノテーションをスキャンしたいクラス名を書き並べます。
protected $scan = [
    '\App\EventListeners\Foo',
];
php artisan event:scan コマンドでスキャンします。
$ php artisan event:scan
Events scanned!
storage/framework/events.scanned.php がそれっぽい内容になっていれば正しくスキャンされています。
<?php 

$events->listen(array (
  0 => 'bar.baz',
), 'App\EventListeners\Foo@qux');
また、ルーティングのアノテーションと同様に app/Providers/EventServiceProvider.php の protected $scanWhenLocal をオーバーライドしてtrueを設定すると、環境値が"local"の場合に限って、リクエストの度に自動スキャンしてくれるようになりました。
protected $scanWhenLocal = true;
適当な場所でイベントをfire()してみます。
\Event::fire('bar.baz', [['xxx', 'yyy']]);
ログにパラメータが書き出されていれば成功です。
storage/logs/laravel-{yyyy-mm-dd}.log
[2014-11-30 00:10:52] local.DEBUG: Array
(
    [0] => xxx
    [1] => yyy
)
ルーティングのアノテーションと同様、デプロイ時のスキャン忘れとかには注意が必要なのかなと思いました。

November 25, 2014

Laravel 5のルーティングのアノテーションを試してみた

この機能は廃止 https://github.com/laravel/framework/commit/4d9f92ef77b3610a05f71110f539daff06e9081d されたのかもしれません。

--

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 4ではルーティングをroutes.phpで定義しますが、Laravel 5からはコントローラのアノテーションでも定義できるようになります。

細かな書き方などは、以下がとても参考になりました。
http://mattstauffer.co/blog/laravel-5.0-route-annotations

以下、簡単に試してみました。

コントローラを適当に作成します。
$ php artisan make:controller FooController --plain
メソッドとアノテーションを適当に書いてみます。
app/Http/Controllers/FooController.php
<?php namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class FooController extends Controller {

    /**
     * @Get("foo/{id}", as="foo.get")
     */
    public function getExample($id)
    {
        //
    }

    /**
     * @Post("foo/{id}", as="foo.post")
     */
    public function postExample($id)
    {
        //
    }
}
app/Providers/RouteServiceProvider.php を編集して protected $scan をオーバーライドします。値は配列で、アノテーションをスキャンしたいコントローラ名を書き並べます。
class RouteServiceProvider extends ServiceProvider {

    protected $scan = [
        '\App\Http\Controllers\FooController',
    ];
php artisan route:scan コマンドでスキャンします。
$ php artisan route:scan
Routes scanned!
php artisan route:list コマンドで確認してみます。
$ php artisan route:list
+--------+-------------------+----------+------------------------------------------------+------------+
| Domain | URI               | Name     | Action                                         | Middleware |
+--------+-------------------+----------+------------------------------------------------+------------+
|        | GET|HEAD foo/{id} | foo.get  | App\Http\Controllers\FooController@getExample  |            |
|        | POST foo/{id}     | foo.post | App\Http\Controllers\FooController@postExample |            |
+--------+-------------------+----------+------------------------------------------------+------------+
正しくスキャンされたようです。尚、スキャン内容は storage/framework/routes.scanned.php に保存されていました。

routes.php で同様の定義をするなら以下です。
$router->get('foo/{id}', ['as' => 'foo.get', 'uses' => 'FooController@getExample']);
$router->post('foo/{id}', ['as' => 'foo.post', 'uses' => 'FooController@postExample']);
また、app/Providers/RouteServiceProvider.php の protected $scanWhenLocal をオーバーライドしてtrueを設定すると、環境値が"local"の場合に限って、リクエストの度に自動スキャンしてくれるようになりました。
protected $scanWhenLocal = true;

routes.phpはどうしても肥大化しやすく、その点は解消しそうな気がします。参考記事を見ると @Where で正規表現によるマッチングにも対応しているようですね。更に、アノテーションはメソッドに対してだけでなく、コントローラに対しても書けるようで
@Resource
@Controller
等も記載されています。

ただ、routes.phpに書くと、before/afterフィルタ(Laravel 5ではMiddlewareという仕組みに置き換わります。)が一目瞭然で、その辺りで好き嫌いが別れそうな印象を受けました。また、アノテーション式だと、デプロイ時のスキャン忘れや、DB値を用いた動的なルーティングを定義する際などに注意が必要かもしれません。

最後に、Laravel 5ではルーティングのキャッシュが可能です。
$ php artisan route:cache
これは、routes.php式にもアノテーション式にも対応していました。キャッシュ内容は(少なくとも今現在は) config/cache.phpのdriver値に関わらず storage/framework/routes.php に保存されるようです。

November 24, 2014

Laravel 5のartisan schedule:runコマンドで定期実行するタスクをPHP側で管理する

Laravel 5は現在開発中です。正式リリース版では、当記事の内容と異なる可能性があります。

Laravel 5では、定期実行されるタスク(要はcron)を、PHP側で一元的に管理できるようになるみたいです。正確には、1 cron設定でNタスクを管理できる。といった感じでしょうか。

尚、この機能は、以下のライブラリに依存しているようです。
https://github.com/mtdowling/cron-expression

細かな書き方などは、以下がとても参考になりました。
http://laravel-news.com/2014/11/laravel-5-scheduler/
http://mattstauffer.co/blog/laravel-5.0-event-scheduling

以下、簡単なサンプルです。

適当なコマンドクラスを作ります。内容は、実行された時刻を標準出力するだけです。
$ php artisan make:console ScheduleExampleCommand --command=schedule:example
app/Console/Commands/ScheduleExampleCommand.php
<?php namespace App\Console\Commands;

use Illuminate\Console\Command;

class ScheduleExampleCommand extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'schedule:example';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command schedule:example';

    public function fire()
    {
        echo sprintf('%s fired at %s', $this->name, date('Y-m-d H:i:s'));
    }
}
app/Console/Kernel.php の protected $commands に、作成したコマンドクラスを登録します。
<?php namespace App\Console;

use Exception;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel {

    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        'App\Console\Commands\ScheduleExampleCommand',
    ];
同様に、app/Console/Kernel.php のschedule()メソッドでスケジューリングします。
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('schedule:example')
            ->cron('*/1 * * * * *')
            ->sendOutputTo(storage_path('logs/schedule.example.log'));
    }
cron()メソッドに、お馴染みの書式で実行間隔を指定しています。今回は、1分毎です。5分毎とかならcron()ではなくeveryFiveMinutes()とかでOKです。

参考: https://github.com/laravel/framework/blob/master/src/Illuminate/Console/Scheduling/Event.php

また、sendOutputTo()メソッドで、標準出力の保存先を指定しています。実行結果メールの送信先を指定するemailOutputTo()とかもあるみたいです。

php artisan schedule:run コマンドで、正しく実行されるか確認してみます。
$ php artisan schedule:run
Running scheduled command: /usr/bin/php5 artisan schedule:example > /home/vagrant/Code/example/storage/logs/schedule.example.log 2>&1 &
storage/logs/schedule.example.log が正しく出力されていれば成功です。どうも、今現在は追記式ではないようですね。実行の度に同一のログファイル名を出力先に指定すれば、毎回、新たなログファイルが生成されてしまいます。この辺は、そのうち追記式もできるようになる気がする。(根拠は無い。)

最後に php artisan schedule:run コマンドをcron設定してみます。Homesteadで確認しています。
$ sudo vim /etc/cron.d/schedule-example

# 以下を記述
*/1 * * * * vagrant /usr/bin/php /home/vagrant/Code/example/artisan schedule:run
storage/logs/schedule.example.log が毎分正しく更新されれば成功です。
schedule:example fired at 2014-11-23 16:43:21
cron設定は数が増えてくると、いつどのようなタスクが実行されているか把握するのが面倒な時がありますが、この方法なら一目瞭然に管理できそうですね。

November 23, 2014

Laravel Elixirを試してみた

Laravel 5ではGulpが標準採用され、Laravel Elixirというタスクツールが併せて実装されるようです。

http://gulpjs.com/
https://github.com/laravel/elixir

ドキュメントを見てみると
* Sass,Less,CoffeeScript等のコンパイル
* CSS,JavaScriptの結合やminify
* PHPUnit,PHPSpec等の実行
* イベントやルーティングのスキャン(キャッシュ化)
といった、様々なタスクがサポートされています。

そんなLaravel Elixirを、Homesteadで試してみました。尚、Homesteadには最初からGulpがインストールされています。
http://laravel.com/docs/4.2/homestead

* Laravel 5もLaravel Elixirも現在開発中なので、今後もまだまだ変わる可能性はあるでしょう。

1. Homesteadのインストールと起動

http://madroom-project.blogspot.jp/2014/11/laravel-homesteadhomestead.html

2. Laravelプロジェクトの作成とLaravel Elixirに必要なパッケージのインストール

# Homesteadにログイン
$ homestead ssh

# Laravelプロジェクトの作成
# Laravel 5は現在開発中なので"dev-develop"を付けてインストールします
$ composer create-project laravel/laravel elixir-example dev-develop

# Laravel Elixirに必要なパッケージのインストール
$ cd elixir-example/
$ npm install
以下のコマンドは全てHomesteadの中です。

3. sassのコンパイルを試してみる

gulpfile.jsを適当に編集してみます。デフォルトではbowerと絡めてBootstrapを使用するための記述がありますが、今回は使わないので削除しています。
elixir(function(mix) {
    mix.sass('app.scss');
});
上記の'app.scss'は resources/assets/sass/app.scss を指しています。こちらも適当に編集してみます。
body {
  background: #000;
}
gulpコマンドでコンパイルしてみます。
$ gulp
[20:31:25] Using gulpfile ~/Code/elixir-example/gulpfile.js
[20:31:25] Starting 'default'...
[20:31:25] Starting 'sass'...
[20:31:27] Finished 'default' after 1.91 s
[20:31:27] gulp-notify: [Laravel Elixir]
[20:31:27] Finished 'sass' after 1.98 s
[20:31:27] gulp-notify: [Error in notifier] Error in plugin 'gulp-notify'
not found: notify-send
("gulp-notify"云々のエラーはなんだろう)

public/css/app.css としてコンパイルされました。
body {
  background: #000; }
gulp watch コマンドで、ファイル変更の度に自動でコンパイルすることもできます。
$ gulp watch

4. コンパイルするファイルにバージョン名をつける

gulpfile.jsを次のようにしてコンパイルしてみます。
elixir(function(mix) {
    mix.sass('app.scss')
        .version('css/app.css');
});
すると public/build/css/app-d8890e6e.css のように、ハッシュ付きのcssファイルが生成されました。

ビューファイルで以下のように書けば
<link rel="stylesheet" href="{{ elixir("css/app.css") }}">
public/build/rev-manifest.json を参照して
<link rel="stylesheet" href="/build/css/app-d8890e6e.css">
のようにHTMLを出力してくれます。

5. minifyする

$ gulp --production
でminifyしてくれました。

6. PHPUnitを実行する

gulpfile.jsを以下のようにすると、PHPUnitを実行して、テストが失敗すれば処理を停止、テストが成功すれば処理を継続。となりました。
elixir(function(mix) {
    mix.phpUnit()
        .sass('app.scss')
        .version('css/app.css');
}); 

7. 複数ファイルを結合してみる

scssファイルを適当に2ファイル用意してみます。
resources/assets/sass/foo.scss
.foo {
  background: #000;
}
resources/assets/sass/bar.scss
.bar {
  background: #fff;
}
gulpfile.jsを次のようにします。
elixir(function(mix) {
    mix.sass(['**/*.scss'])
        .styles('css/**/*.css')
        .version('css/all.css');
});
コンパイルすると、結合されたファイルが生成されました。
public/build/css/all-993ce219.css
.bar {
  background: #fff; }

.foo {
  background: #000; }
LessやCoffeeScriptのコンパイル、JavaScriptの結合やminify、PHPSpecの実行等も、似たような感じで実行できるはずです。(たぶん。)

以上のように、Laravel 5では、Laravel Elixirを用いて様々なタスクを簡単に実行できるようになるみたいですね。

gulpはタスクが非同期で実行されるが故に、実行順序で少しハマったことがあるのですが、その辺も吸収してくれると助かるなぁと思いました。

November 17, 2014

Laravel Homesteadをhomesteadコマンドで起動してみる

Laravelが標準的に提供している仮想環境"Homestead"がCLI化され、より簡単に起動できるようになったみたいなので試してみました。
http://laravel.com/docs/4.2/homestead

当記事は
* Mac OSX Yosemite
* VirtualBox 4.3.18
* vagrant 1.6.5
で確認しています。

1. composer global require コマンドでインストールします。
$ composer global require "laravel/homestead=~2.0"
2. ~/.composer/vendor/bin/homestead にパスを通します。以下、一例。
$ ln -s ~/.composer/vendor/bin/homestead /usr/local/bin/
3. homestead init コマンドを実行して初期化します。ホームディレクトリに".homestead"ディレクトリと、必要なファイルが生成されます。
$ homestead init
Creating Homestead.yaml file... ✔
Homestead.yaml file created at: /Users/xxx/.homestead/Homestead.yaml
4. homestead edit コマンドでHomestead.yamlがエディタで開かれました。
$ homestead edit
5. 以下、Homestead.yamlの設定項目の抜粋です。必要に応じて編集します。今回は、デフォルト設定のまま進めました。
authorize ... 使用する公開鍵
keys ... 使用する秘密鍵
folders (map/toを対にして複数指定できると思います。)
  map ... ホスト側のディレクトリ
  to ... ゲスト(Homestead)側のディレクトリ
sites (map/toを対にして複数指定できると思います。)
  map ... ベースURL
  to ... ドキュメント(WEB)ルート
6. デフォルト設定に対して必要なファイルを用意します。
$ mkdir -p ~/Code/Laravel/public
$ vim ~/Code/Laravel/public/index.php
~/Code/Laravel/public/index.php
<?php

phpinfo();
7. hostsも忘れずに編集しておきます。
$ sudo vim /etc/hosts
192.168.10.10 homestead.app # 追記
8. homestead up コマンドでHomesteadを起動します。
$ homestead up
9. http://homestead.app/ に正しくアクセスできれば成功です。

October 23, 2014

Backlogの課題を確認するChrome拡張機能を公開しました

Backlogの課題を確認するChrome拡張機能を公開しました。AngularJS(1.2)製です。

IssuesViewer For Backlog
https://chrome.google.com/webstore/detail/issuesviewer-for-backlog/ohgpfaebkmddhlpapnfjdoneoibhblkb


機能はざっと、以下な感じです。(0.0.7現在)

  • 自分の担当タスクを確認
  • 通知数を確認
  • マルチアカウント対応
  • 簡単な設定

"Setting"で、認証情報と検索条件を設定します。APIキーについては http://www.backlog.jp/help/usersguide/personal-settings/userguide2378.html を御覧ください。

ご意見ご要望は https://github.com/mamor/backlog-issues-viewer/issues まで。PR大歓迎です。

作った次の日にAngularJS 1.3がリリースされたので、テストケースを書きつつ1.3に上げる予定です。気が向き次第。

October 11, 2014

CentOSでcompass watchをSupervisorで永続化してOS起動時に自動起動する

以下、普通にインストールして設定する手順ですが、Vagrant環境に仕込んでチームで共有する。とかが現実的な使い方かなと思います。

Compassのインストール
$ sudo gem install compass
Supervisorのインストール
$ sudo yum install python-setuptools
$ sudo easy_install supervisor
/etc/supervisord.conf の作成(echo_supervisord_conf の内容に以下を追記)
[program:compass-watch]
command     = compass watch --poll --app-dir /path/to/app-dir/ -c /path/to/config.rb
autostart   = true
autorestart = true
/etc/rc.d/init.d/supervisord の作成
#!/bin/sh
#
# /etc/rc.d/init.d/supervisord
# sudo chkconfig --add supervisord
# sudo chkconfig --level 35 supervisord on
#
# chkconfig: - 64 36
# description: Supervisor Server
# processname: supervisord

. /etc/rc.d/init.d/functions

NAME="supervisord"
COMMAND="/usr/bin/supervisord"
PIDFILE="/var/run/$NAME.pid"
CONFIG="/etc/supervisord.conf"

start()
{
  echo -n $"Starting $NAME: "
  daemon $COMMAND -c $CONFIG --pidfile $PIDFILE
  [ -f $PIDFILE ] && success $"$NAME startup"
  echo
}

stop()
{
  echo -n $"Shutting down $NAME: "
  [ -f $PIDFILE ] && killproc $NAME || success $"$NAME shutdown"
  echo
}

case "$1" in

  start)
    start
  ;;

  stop)
    stop
  ;;

  status)
    status $NAME
  ;;

  restart)
    stop
    start
  ;;

  *)
    echo "Usage: $0 {start|stop|restart|status}"
  ;;

esac
/etc/rc.d/init.d/supervisord のサービス登録と自動起動設定
$ sudo chkconfig --add supervisord
$ sudo chkconfig --level 35 supervisord on

September 6, 2014

Laravel4.3(5.0?)のディレクトリ構成の変更メモ

4.3でなく5.0にする案も出ているみたいですが、どうなるかよくわからないので、とりあえず4.3と呼びます。

近々、4.3を使う気がするので、ディレクトリ構成の変更について調べてみました。時期的に、4.3正式リリースの少し前になると思うんですが、4.2からのアップグレードは面倒そうなので、4.3でスタートした方が良いかなぁ。と。

https://laracasts.com/series/whats-new-in-laravel-4-3 の動画が大変参考になりました。

尚、4.3はまだ開発段階なので、多少は変わる結構変わる可能性もあると思います。

まず、appディレクトリ。
app
├── Console ... app/commands に相応
├── Http
│   ├── Controllers ... app/controllers に相応
│   ├── Filters ... app/filters.php で指定していたフィルタ処理のクラス群
│   ├── Requests ... 新機能。1リクエストに対するバリデーションルールや認証ロジック等を持つクラスを実装する
│   └── routes.php ... app/routes.php に相応
├── Providers ... グローバル空間で行われていた色々な処理がプロバイダとしてまとめられた
└── User.php ... app/models/User.php に相応
appディレクトリはcomposer.jsonのPSR-4で指定されています。
"psr-4": {
    "App\\": "app/"
}
名前空間プリフィックスである "App" が嫌な場合は "app:name" で変更できます。
$ php artisan app:name Foo
動画にもあるように app/Http/Requests に実装するクラスは、コントローラメソッドにインジェクションして使うと便利そうです。コントローラメソッドへのインジェクションも、4.3からの新機能です。

次に、その他のディレクトリ。
bootstrap
├── environment.php ... 環境を決める処理がここに切り出された

config ... app/config に相応
├── filesystems.php ... 4.3で新たに組み込まれたファイルシステムに対する設定
├── namespaces.php ... appディレクトリ以下のクラスに対して使用される名前空間の設定

database app/database ... に相応

resources
├── lang app/lang ... に相応
└── views app/views ... に相応

storage app/storage ... に相応

4.2までappディレクトリにあった多くのディレクトリがプロジェクトルートに移動されています。appディレクトリに作成するファイルは、基本的には、アプリケーションを動かすためのクラスのみ。となった感じですかね。

phpunit-seleniumでテスト失敗時にスクリーンショットを撮る。スクリーンショットのファイル名をカスタマイズする。

基本的には https://phpunit.de/manual/4.2/ja/selenium.html の "例 13.3: テストに失敗したときのスクリーンショットの取得" の通りです。

以下の3つのプロパティを適切に設定すれば、テスト失敗時にスクリーンショットが作成されます。
protected $captureScreenshotOnFailure = true;
protected $screenshotPath = '/var/www/localhost/htdocs/screenshots';
protected $screenshotUrl = 'http://localhost/screenshots';
ただし、これだけだとスクリーンショット名が、自動で決まる文字列の羅列になってしまい、どのテストで失敗したのかわかりにくいです。なのでカスタマイズしてみます。

スクリーンショット名は https://github.com/giorgiosironi/phpunit-selenium/blob/master/PHPUnit/Extensions/SeleniumTestCase.php の takeScreenshot() で決まるようです。スクリーンショットディレクトリを返すメソッド "$this->getScreenshotPath()" が呼ばれていて、このメソッドはたぶんここでしか使われていないので、オーバーライドしてみました。

まとめると、以下のようになります。$screenshotPath は、とりあえず "__DIR__" にしています。$screenshotUrlは、とりあえずドキュメントの通りです。(これどんな意味があるのだろうか。)
protected $captureScreenshotOnFailure = true;
protected $screenshotPath = __DIR__;
protected $screenshotUrl = 'http://localhost/screenshots';

/**
 * {@inheritdoc}
 */
protected function getScreenshotPath()
{
    $className = end(explode('\\', get_class()));
    $methodName = \PHPUnit_Framework_Testcase::getName(false);

    return parent::getScreenshotPath().sprintf('%s_%s_', $className, $methodName);
}
テストを失敗させて "{クラス名}_{メソッド名}_{文字列の羅列}.png"が作成されればOKです。

takeScreenshot()のオーバーライドだと丸ごと書かないといけないので、この方法を選びました。getScreenshotPath()というメソッド名の意味合いは変わってしまいますが、まあ、テストケースなので良いかなと。

Macでphpunit-seleniumを動かすメモ

Mac OS X Mavericksで確認しています。

この記事の手順では brew, composer, phpunit 等が予め必要です。

Selenium Serverをインストールします。
$ brew install selenium-server-standalone
Selenium Serverを起動します。
$ selenium-server
適当な場所にディレクトリを作って https://github.com/giorgiosironi/phpunit-selenium をインストールします。
$ composer require phpunit/phpunit-selenium:1.*
適当なテストケースを書きます。
$ more ExampleTest.php
<?php
require_once 'vendor/autoload.php';
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class ExampleTest extends PHPUnit_Extensions_SeleniumTestCase
{
    public static $browsers = [
        ['name' => 'Firefox', 'browser' => '*firefox'],
        ['name' => 'Chrome',  'browser' => '*googlechrome'],
    ];

    protected function setUp() {
        $this->setBrowserUrl('http://www.example.com/');
    }

    public function testTitle() {
        $this->open('http://www.example.com/');
        $this->assertTitle('Example Domain');
    }
}
ブラウザ名の先頭につけるAsteriskについて http://stackoverflow.com/questions/7405498/in-selenium-1-why-are-all-the-browser-commands-prefixed-with-an-asterix に記載がありました。まだよく理解していませんが。。。

実行してみます。
$ phpunit ExampleTest.php
PHPUnit 4.2.4 by Sebastian Bergmann.

..

Time: 7.01 seconds, Memory: 5.25Mb

OK (2 tests, 2 assertions)
FirefoxとChromeが起動してテストが実行されました。

テストの書き方はPHPUnitのマニュアル https://phpunit.de/manual/4.2/ja/selenium.html に記載されています。

思っていたより簡単で良かった。

August 16, 2014

Knockout.jsのforeachで少しハマったのでメモ

最近、ぶっつけ本番でKnockout.jsを使っていて、foreachの動きで少しハマったのでメモ。(慣れている人にとっては大したこと無い内容なんだろうけど。。。)

サンプルソースは以下です。
<html>
<head>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
</head>
<body>
<!-- foreach をネストする際、内側の foreach で使用する配列に "$root" をつけないとエラーになる  -->
<div data-bind="foreach: {data: array1, as: '$value1' }">
    <p data-bind="text: $value1"></p>
    <div data-bind="foreach: {data: $root.array2, as: '$value2' }">
        <p data-bind="text: $value2"></p>
    </div>
</div>

<!-- "$index" は単独だと出力できるが、文字列連結時等は "$index()" としないとエラーになる  -->
<!-- "$parentContext.$index" も同様  -->
<div data-bind="foreach: {data: array1, as: '$value1' }">
    <p data-bind="text: $index"></p>
    <p data-bind="text: $index() + ':' + $value1"></p>
    <div data-bind="foreach: {data: $root.array2, as: '$value2' }">
        <p data-bind="text: $parentContext.$index"></p>
        <p data-bind="text: $parentContext.$index() + ':' + $value2"></p>
    </div>
</div>

<script>
    function ViewModel() {
        var self = this;
        
        self.array1 = ko.observableArray(['foo', 'bar', 'baz']);
        self.array2 = ko.observableArray(['FOO', 'BAR', 'BAZ']);
    };

    ko.applyBindings(new ViewModel());
</script>
</body>
</html>

コメントに書いてある通りですが

1.
foreach をネストする際、内側の foreach で使用する配列に "$root" をつけないとエラーになる

2.
"$index" は単独だと出力できるが、文字列連結時等は "$index()" としないとエラーになる("$parentContext.$index" も同様)

で、ハマった。。。


P.S.
まだほんの少し使った程度ですが、JSはjQueryがメイン。という開発の中に、双方向バインディングだけ付け足したい時とかは便利かなと思いました。jQueryの処理も好きに書いて良い(という思想な)はずなので、例えばチームのメンバ的に、AngularJSだと(主にディレクティブ周りで)敷居が高くなってしまい導入に躊躇するような状況でも、Knockout.jsなら比較的楽に導入出来る気がします。(制約が緩いほど秩序は保ちにくくなってしまうはずですが。)



August 6, 2014

Chromeのオートコンプリートを個別に削除するメモ

メモ。

オートコンプリートリストの該当選択肢にカーソルを併せて
Windowsの場合は shift + delete
Macの場合は fn + shift + delete
で出来ました。

August 2, 2014

Macでidn_to_ascii()とidn_to_utf8()を使えるようにする

メモです。

icu4cとintlパッケージをインストールします。
brew install icu4c
sudo pecl install intl # ライブラリのパスは /usr/local/Cellar/icu4c/{VERSION} でした
php.iniに以下を記述します。(バージョン周りは適宜置換)
extension=/usr/local/Cellar/php54/5.4.25/lib/php/extensions/no-debug-non-zts-20100525/intl.so

July 29, 2014

AngularJSで一文字ずつフェードインして表示するディレクティブのメモ

以下がサンプルです。 ポイントは、一文字ずつバラしてspanタグで囲み、1spanタグずつdelay()させつつfadeIn()させる感じです。(あんまりAngularJS関係ない。。。)

先日作ったインスタグラムカルタで使っていて、テスト(spec)は https://github.com/mamor/igkaruta/blob/master/spec/directives/myFadeInCharsSpec.js みたいな感じで書きました。

Travis CIでPHPUnitを実行して、Coverallsでカバレッジを表示するメモ

メモです。

大雑把な手順は
1. Coveralls でアカウント作成と対象レポジトリの登録
2. satooshi/php-coveralls のインストール
3. .travis.yml を編集
4. .coveralls.yml を作成
になります。

1. Coveralls でアカウント作成と対象レポジトリの登録
https://coveralls.io/ から特に問題なく出来たので割愛します。

2. satooshi/php-coveralls のインストール
Travis CIとCoverallsの連携にしか必要ないので、composer.jsonには書かずに.travis.ymlでインストールするようにしました。"before_script" で "composer require satooshi/php-coveralls:0.* --dev" とかすればOKです。

3. .travis.yml を編集
"phpunit --coverage-clover build/logs/clover.xml" として、PHPUnit実行時にカバレッジファイルを出力するようにします。また "after_script" で "php vendor/bin/coveralls"として、Coverallsに通知します。

4 の.coveralls.ymlは
src_dir: .
coverage_clover: build/logs/clover.xml
json_path: build/logs/coveralls-upload.json
としました。

まとめると
https://github.com/mamor/igkaruta/commit/99c1492f9862000bd70e6a974c72ea209413f088
みたいな感じです。

後は、README.mdにバッジのURL(Coveralls側でわかります)を書けば https://github.com/mamor/igkaruta#karuta-for-instagram のように表示することが出来ました。

July 26, 2014

AngularJS と Laravel による Instagram の写真を使ったカルタを Heroku にデプロイしてみた

ふと、Instagramの写真でカルタできそうだなと思いついたので、作ってみました。

アプリケーションのURLは以下になります。
http://igkaruta.herokuapp.com/

ソースは以下で公開しています。
https://github.com/mamor/igkaruta


以下、遊び方。


1.
Login with Instagram > Play KARUTA と進みます。

2.
対象ユーザを選択して "GET PHOTOS" を押します。対象ユーザの写真有無チェックはまだしていないので、写真が取れなかったら別のユーザを選んで下さい。

3.
写真が表示されたら "START" を押します。カルタが始まります。


4.
"STOP" で一時停止、"START" で再開ができます。

5.
"GET PHOTOS" で、所謂ニューゲームになります。


以下、開発周り。


サーバサイドにはLaravelを使いました。が、InstagramのAPI(認証含む)の実行程度なので、大したことはしていません。尚、雛形には、ちまちまとオレオレ化させている https://github.com/mamor/laravel を使っています。

Herokuへのデプロイは
http://madroom-project.blogspot.jp/2014/07/herokuphppackagejsonnodejs.html
http://madroom-project.blogspot.jp/2014/07/herokuapplication-error.html
https://github.com/mamor/igkaruta/commit/462461e50c7a81dec00a2fddcf10f8760537491f
みたいな感じです。

クライアントサイドにはAngularJSを使いました。一番最初に勉強がてら作った TroubleClinic https://github.com/mamor/TroubleClinic の頃と比べて、少しはAngularJS力がついたかなぁと思います。ディレクトリ構成やソースの書き方は、Yeomanを参考にしています。

ちなみにYouFMもAngularJSで作っています。コイツもよろしくお願いしますw
https://chrome.google.com/webstore/detail/youfm/gbpebippikipomjijplmbepmginobjbj

テストはそれぞれ、PHPUnitとJasmine(+ gulp.js + Karma)で行っています。何かの参考になれば幸いです。


P.S.
アイデア等、PR歓迎です。誰か神経衰弱作って下さいw

Herokuで"Application Error"が出た

Herokuで以下のエラーが出ました。


公式ドキュメントの https://devcenter.heroku.com/articles/error-codes#h14-no-web-dynos-running を参考に
heroku ps:scale web=1
したら解決しました。

HerokuにPHPアプリをデプロイ時、package.jsonがあるとnode.jsアプリと認識されるっぽい

HerokuにPHPアプリをデプロイしようとしたら、node.jsアプリと認識されてしまいました。

composer.jsonもcomposer.lockもあるのですが、雑多なタスクをまとめるためにpackage.jsonもあったので、それが原因なんだと思います。

なので、BUILDPACK_URLを設定してみると、解決出来ました。
heroku config:set BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-php

July 20, 2014

Goutteでダウンロードしたファイルの内容を取得する

PHPのスクレイピングライブラリで有名なGoutte https://github.com/fabpot/Goutte で、ダウンロードしたファイルの内容を取得する手順メモです。

Goutteをインストールします。
$ composer require fabpot/goutte:2.*
以下、ファイルをダウンロードするサンプルです。
<?php

require_once './vendor/autoload.php';

// Goutte\Client のインスタンスを生成する
$client = new Goutte\Client;

// ダウンロードしたいファイルがあるURLにアクセスする
$client->request('GET', 'http://example.com/download.php');

// レスポンスのオブジェクトを取得する
/** @var Symfony\Component\BrowserKit\Response $response */
$response = $client->getResponse();

// コンテンツのオブジェクトを取得する
/** @var GuzzleHttp\Stream\Stream $content */
$content = $response->getContent();

// seek(0) して getContents() するとファイル内容を取得できる
$content->seek(0);
echo $content->getContents();
これで、ダウンロードしたファイルの内容を取得出来ました。ポイントは seek(0) です。これをしないと、どうにも取得できませんでした。

尚、ダウンロードさせる側に、以下のPHPファイルを置いて確認しました。
<?php

// download.txt は別途用意する
$file = 'download.txt';
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$file);
header('Content-Length: ' . filesize($file));
readfile($file);

July 17, 2014

Piwikのメンテナンスモードを切り替えるプラグインを作りました

2014-07-18 追記:
Packagistに登録したので、Composerで簡単にインストールできるようになりました。
https://packagist.org/packages/mamor/piwik-plugins-maintenance

--

前回 http://madroom-project.blogspot.jp/2014/07/piwik.html の続きです。

というわけで、出来ました。
https://github.com/mamor/piwik-plugins-maintenance

どうも Piwik Plugins Marketplace http://plugins.piwik.org/ に反映されないので、インストール方法と使い方をメモしておきます。

1.
上記のソース一式を /path/to/piwik/plugins/Maintenance として配置します。

2.
Maintenanceプラグインを有効にします。たぶん画面からできますが config/config.ini.php に以下を追記してもOKでした。
[Plugins]
Plugins[] = "Maintenance"

[PluginsInstalled]
PluginsInstalled[] = "Maintenance"
3.
有効になったか確認します。
$ ./path/to/piwik/console
maintenance
  maintenance:off                        Disable Maintenance Mode.
  maintenance:on                         Enable Maintenance Mode.
4.
実行してみます。
$ ./path/to/piwik/console maintenance:on
Now, Maintenance Mode is 1.

$ ./path/to/piwik/console maintenance:off
Now, Maintenance Mode is 0.
config/config.ini.phpを書き換えるので、書き込み権限には注意して下さい。

Piwikのプラグインを作るメモ

Piwikのメンテナンスモードをコマンドラインから切り替えたいのですが、標準では備わっていないようなので作ってみようと思います。

出来上がったらPiwik Plugins Marketplaceで公開する予定です。
http://plugins.piwik.org/
http://developer.piwik.org/guides/distributing-your-plugin

以下、必要なファイルを生成する手順のメモです。
# プラグインを作成します
$ ./console generate:plugin

# プラグインの名前を入力します
Enter a plugin name: maintenance

# プラグインの説明を入力します
Enter a plugin description: Allows you to manage maintenance mode by CLI.

# プラグインのバージョンを入力します
Enter a plugin version number (default to 0.1.0):

# 併せてAPIとControllerを作成するか入力します
Shall we also create an API and a Controller? (y/N)N

# コマンドを作成します
$ ./console generate:command

# 先ほど作成したプラグインを選択します
Enter the name of your plugin: Maintenance

# コマンド名を入力します
# この場合は "./path/to/piwik/console maintenance:on" になります
Enter the name of the command: on

プラグインを有効にした後 plugins/Maintenance/Commands/On.php を好きに編集します。

July 10, 2014

PHP5.5 で "continue 2" したらメモリ使用量が増えた

PHP5.5 で "continue 2" したらメモリ使用量が増えている気がしたので、簡単に検証してみました。

使ったソースは以下です。
<?php

/**
 * メモリ使用量をKB表記で返す
 *
 * @return string
 */
function peak_usage() {
    return (memory_get_peak_usage(true) / 1024).'KB'.PHP_EOL;
}

// 100要素数の配列を作る
// 各要素には ['a', 'b', 'c'] が入る
$array = [];
for($i = 0; $i < 100; $i++) {
    $array[] = ['a', 'b', 'c'];
}

// もう一つ適当に配列を作る
$array2 = ['x' => 'X', 'y' => 'Y'];

// $array * $array2 を反復を 100 回行う
// $array * $array2 の反復の中で即時 continue 2 する
for($i = 0; $i < 100; $i++) {
    if ($i % 10 === 0) {
        echo php_sapi_name() === 'cli' ? peak_usage() : nl2br(peak_usage());
    }

    foreach ($array as $key => $value) {
        foreach ($array2 as $key2 => $value2) {
            continue 2;
        }
    }
}
まずは、PHP 5.4 (OS X Mavericks) で確認してみます。
$ php -v
PHP 5.4.24 (cli) (built: Jan 19 2014 21:32:15)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
ブラウザからアクセス (ビルトインサーバーで実行)
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
コマンドラインから実行
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
結果に違いはありませんでした。

次に、PHP 5.5 (Vagrant x Ubuntu 13.10) で確認してみます。
$ php -v
PHP 5.5.12-2+deb.sury.org~saucy+1 (cli) (built: May 12 2014 13:45:47)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Xdebug v2.2.3, Copyright (c) 2002-2013, by Derick Rethans
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies

$ apache2 -v
Server version: Apache/2.4.9 (Ubuntu)
Server built:   Apr  1 2014 08:58:43
ブラウザからアクセスした結果 (Apacheで実行)
512KB
768KB
1280KB
1536KB
2048KB
2304KB
2816KB
3072KB
3584KB
3840KB
コマンドラインから実行した結果
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
512KB
ブラウザからアクセスした時に、かなり増えました。ちなみに "continue 2" の箇所をコメントアウトすると、ブラウザからアクセスした時も、ずっと "512KB" のままになりました。

Stack Overflowに以下の投稿が見つかり、"Zend OPcache"に関することが書かれています。
http://stackoverflow.com/questions/23231675/php-5-5-memory-leak-when-using-continue-2-inside-two-foreach-loops

2014-07-11 追記: PHP本家にも報告されていました。
https://bugs.php.net/bug.php?id=67111

再現条件は良くわかりませんが、Zend OPcacheのバグっぽいですね。

尚、foreach()している箇所をwhile()に変えたら、増加は発生せず、ずっと"512KB"となりました。応急処置するなら、この方法ですかねぇ。
reset($array);
while (list($key, $value) = each($array)) {
    reset($array2);
    while (list($key2, $value2) = each($array2)) {
        continue 2;
    }
}

July 3, 2014

PHPで2箇所の緯度経度から距離を計測するメモ

メモです。

MySQLのgeometry型とかは使用せず、以下のライブラリを用いてPHPだけで、多摩センターと新宿の距離を測ってみます。
https://github.com/mjaschen/phpgeo
(README.mdに書いてある通りですが。。。)

答え合わせには、以下を使わせて頂きました。
http://www.kyori.jp/

ライブラリをインストールして、適当なPHPを書きます。
$ composer require mjaschen/phpgeo:0.*
<?php

require_once 'vendor/autoload.php';

use Location\Coordinate;
use Location\Distance\Vincenty;

// 多摩センター
$coordinate1 = new Coordinate(35.623891, 139.42287299999998);

// 新宿
$coordinate2 = new Coordinate(35.6938401, 139.70354940000004);

echo $coordinate1->getDistance($coordinate2, new Vincenty());
"26573.928" (メートル)が出力されました。問題無さそう。

June 19, 2014

Vagrant上のCoreOSで作成したUbuntuのDockerイメージにSSHサーバーをインストールして、Macからログインするまでのメモ

メモです。

前回の記事で作成したDockerイメージを使って、タイトルの通りのことをしてみました。
http://madroom-project.blogspot.jp/2014/06/vagrant-coreosubuntudocker.html

参考:
http://docs.docker.com/examples/running_ssh_service/
http://inokara.hateblo.jp/entry/2013/09/29/090500

SSHサーバーのインストールと設定をします。尚、当記事の /etc/ssh/sshd_config の編集内容は、セキュリティ周りを考慮していません。
$ docker run -i -t mamor/ubuntu-phpdev /bin/bash

# apt-get install vim openssh-server -y
# mkdir /var/run/sshd
# cp /etc/ssh/sshd_config /etc/ssh/sshd_config.original
# vim /etc/ssh/sshd_config # 以下の編集をします。

# diff /etc/ssh/sshd_config.original /etc/ssh/sshd_config
28c28
< PermitRootLogin without-password
---
> PermitRootLogin yes

# passwd root
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

# exit
コンテナのIDを確認してコミットします。
$ docker ps -a
CONTAINER ID        IMAGE                        COMMAND             CREATED             STATUS                     PORTS               NAMES
9cfd78ba8a20        mamor/ubuntu-phpdev:latest   /bin/bash           3 minutes ago       Exited (0) 5 seconds ago                       angry_heisenberg

$ docker commit 9cfd78ba8a20 mamor/ssh
ポート22を指定して、コンテナをバックグラウンドで起動します。
$ docker run -d -p 22 mamor/ssh /usr/sbin/sshd -D
3d6c2249369ad059fd2005c2c0589c9f76b624808ba59d1fde8942eaa42e98f0
コンテナの22番ポートにフォワードされている、CoreOSのポートを確認します。
$ docker port 3d6c2 22
0.0.0.0:49153
Macから接続してみます。パスワードは、Dockerコンテナで指定したパスワードです。尚、当記事のCoreOSは https://github.com/coreos/coreos-vagrant.git で構築されていて、IPアドレスは "172.17.8.101" になっていました。
$ ssh root@172.17.8.101 -p 49153
root@172.17.8.101's password:
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.2.0-58-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@29932f436be8:~#
(これでMacからDockerのUbuntuに対してserverspecの実行できるかな?)

June 18, 2014

Vagrant + CoreOSでUbuntuのDockerイメージを作成して、パッケージを更新した後にコミットするまでのメモ

メモです。

最終的には、Dockerに対する理解を兼ねて、Vagrant + Chefで作った個人的なPHP用の開発環境 https://github.com/mamor/vagrant-phpdev-ubuntu みたいなことをDockerでできたら良いなーと思っています。

まず、Dockerコンテナを動かすための、VagrantなCoreOS環境を用意します。
$ git clone https://github.com/coreos/coreos-vagrant.git
$ cd coreos-vagrant/
$ vagrant up
$ vagrant ssh
以下、CoreOS内で実行します。
# Ubuntuイメージをダウンロードします。
$ docker pull ubuntu

# パッケージを更新します。
$ docker run ubuntu /bin/sh -c "apt-get update && apt-get upgrade -y"

# コンテナのプロセスを確認します。
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS                      PORTS               NAMES
xxxxxxxxxxxx        ubuntu:14.04        /bin/sh -c 'apt-get    About a minute ago   Exited (0) 17 seconds ago                       distracted_torvalds

# 表示された"CONTAINER ID"をコミットします。
$ docker commit xxxxxxxxxxxx mamor/ubuntu-phpdev
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

# コミットされていることを確認します。
$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
mamor/ubuntu-phpdev   latest              yyyyyyyyyyyy        22 seconds ago      315.2 MB
ubuntu                12.10               e314931015bd        13 days ago         172.1 MB
ubuntu                quantal             e314931015bd        13 days ago         172.1 MB
ubuntu                13.10               145762641db9        13 days ago         180.2 MB
ubuntu                saucy               145762641db9        13 days ago         180.2 MB
ubuntu                14.04               ad892dd21d60        13 days ago         275.4 MB
ubuntu                latest              ad892dd21d60        13 days ago         275.4 MB
ubuntu                trusty              ad892dd21d60        13 days ago         275.4 MB
ubuntu                13.04               6b0a59aa7c48        13 days ago         169.4 MB
ubuntu                raring              6b0a59aa7c48        13 days ago         169.4 MB
ubuntu                12.04               ae8682f4ff20        13 days ago         209.9 MB
ubuntu                precise             ae8682f4ff20        13 days ago         209.9 MB
ubuntu                10.04               3db9c44f4520        8 weeks ago         183 MB
ubuntu                lucid               3db9c44f4520        8 weeks ago         183 MB

# コミットしたイメージを起動してみます。
$ docker run -i -t mamor/ubuntu-phpdev /bin/bash
root@4290c049e05c:/# exit

# 不要となったコンテナのプロセスを削除します。
$ docker rm $(docker ps -a -q)
xxxxxxxxxxxx
zzzzzzzzzzzz

# プロセスが削除されたことを確認します。
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

#Docker Hubへプッシュするには"docker login"と"docker push"を使います。
$ docker login
Username: mamor
Password:
Email: mamor@example.com
Login Succeeded
$ docker push mamor/ubuntu-phpdev:latest

June 11, 2014

CentOS6.5でidn_to_ascii()とidn_to_utf8()を使えるようにする

メモです。

ICUライブラリをインストールする (最新版は http://site.icu-project.org/download で確認)
$ wget http://download.icu-project.org/files/icu4c/53.1/icu4c-53_1-src.tgz
$ tar zxvf icu4c-52_1-src.tgz
$ cd icu/source/
$ ./configure --prefix={PREFIX}
$ make
$ make install
PECLのintlパッケージをインストールする
$ sudo pecl install intl
# 略
Specify where ICU libraries and headers can be found [DEFAULT] :{PREFIX}
php.iniに以下を記述
extension=intl.so
確認
$ php -r "var_dump(function_exists('idn_to_ascii'));"
bool(true)

$ php -r "var_dump(function_exists('idn_to_utf8'));"
bool(true)
以下、ICUライブラリに関して参考にさせて頂きました。
http://qiita.com/emegane/items/f8f66eeb6dbac95c662d

June 7, 2014

Piwikでメンテナンスモードにする

config/config.ini.php の "General" 項で "maintenance_mode" に "1" を設定するとメンテナンスモードになりました。
 [General]
maintenance_mode = 1
WEB

API

解析タグからアクセスされる /piwik.php には影響無いようです。

一応、CLIで console core:archive もしてみましたが、問題なく実行できました。

(CLIでメンテナンスモードを有効にできれば良いんだけど。。。)

Piwikの負荷対策とDB容量についてメモ

Piwikの負荷対策とDB容量について、気になったのでメモしておきます。飽くまでPIwik上の内容で、サーバ構成とかではありません。
http://piwik.org/


"システムの構成" > "全般の設定" と進むと、負荷周りの項目が出てきます。

1.
"リポートがブラウザで表示されたときをトリガーとする Piwik アーカイブ処理を許可" を "いいえ" にして、cronジョブ http://piwik.org/docs/setup-auto-archiving/ をセットアップすることが推奨されています。

2.
"今日のリポート(または今日を含む、他の日付範囲)が生成される間隔" を "30分(1800 秒) または 1時間(3600 秒)" に設定することが推奨されています。


次に「'古いビジターのログとリポートを削除' 設定はこちらをクリック」の先の内容です。

3.
"定期的にデータベースから古いビジターのログを削除する" を "はい" にする場合、http://piwik.org/faq/general/#faq_125 に、前述のcronジョブをセットアップすることと "archive.php" を少なくとも一回実行することが強く推奨されています。"archive.php" は "misc/cron" にあるやつだと思います。

が、"archive.php" とcronジョブの違いがよくわかりませんでした。以下のように "archive.php" は推奨されていなく、代わりにcronジョブが指す内容をかわりに使うようなメッセージが出ます。(cronジョブで行うことを、念のため手動で一回はやってくれ。みたいな感じ??)
# URLオプション値は適当です
$ php archive.php --url=http://example.com/

-------------------------------------------------------
Using this 'archive.php' script is no longer recommended.
Please use '/path/to/php /xxx/piwik/console core:archive --url=http://example.com/' instead.
To get help use '/path/to/php /xxx/piwik/console core:archive --help'
See also: http://piwik.org/docs/setup-auto-archiving/
-------------------------------------------------------
"古いビジターのログ" というのは飽くまでログで、正しくアーカイブ処理されていれば定期削除の設定をして問題ないような気がします。(未確認ですが。)

対して "古いリポート" というのは、不用意に削除すると、過去にさかのぼった解析データが見れなくなるような気がします。(これも、未確認ですが。)

やってみないと実際にどういう効果(あるいは問題)が出るかよくわかりませんが、やるなら最初のうちに確認しておいたほうが良いだろうなぁと思います。

May 25, 2014

YouFMがLast.fmと連携可能になりました。

YouFMはYouTube動画を音楽プレーヤーのように再生できるChrome Extentionです。
https://chrome.google.com/webstore/detail/youfm/gbpebippikipomjijplmbepmginobjbj

今回のアップデートでv0.6系となり、Last.fmとの連携が可能になりました。

Last.fmと連携すると、scrobbleが可能になります。(v0.5から。)
ただし、動画名が"{artist name} - {track name}"の動画に限ります。ハイフンの数が1つの場合のみに反応します。

また、Last.fmがあなたのLast.fmアカウントに対してrecommendするアーティストの関連動画を、ラジオモードで連続再生できるようになります。(v0.6から。)

以下、それぞれの設定方法です。

YouFMの右上の歯車マーク、あるいは拡張の管理画面から、オプション画面に移動します。


"Authenticate with Last.fm for scrobble."をクリックします。

Last.fm側で、YouFMからのアクセスを許可します。

Last.fm側での認証が完了したら、試しに一曲再生してみます。前述のとおり、動画名が"{artist name} - {track name}"の動画に限ります。そして、Last.fmにscrobbleできているか確認します。

これで、認証は完了です。

また、オプション画面を開くと"Enable Last.fm Radio Mode:"というチェックボックスが追加されます。

このチェックボックスをONにしておくと、従来のラジオモードの選曲基準が、Last.fmのrecommendするアーティスト基準に変わります。(従来の基準は、単純に、関連動画でした。)

ぜひ使ってみてください :)

May 21, 2014

Ubuntu13.10で Call to undefined function idn_to_ascii() が出た

PHPで idn_to_ascii() と idn_to_utf8() という関数を使うと、日本語ドメイン名をASCII形式に変換したり、戻したりできます。
http://www.php.net/manual/ja/function.idn-to-ascii.php
http://www.php.net/manual/ja/function.idn-to-utf8.php

Ubuntu13.10上で使おうとしたら "Call to undefined function idn_to_ascii()" が出たので、その対応です。PHPは5.5系でした。
$ sudo apt-get install php5-intl
ちなみにWin(と言うかXAMPP)の場合は "/xampp/php/" にある "icu**.dl" 一式を "/xampp/apache/bin/" にコピーしてあげると上手くいきました。
http://stackoverflow.com/questions/1451468/intl-extension-installing-php-intl-dll

(MacユーザなんですがMacでの対応方法はまだ調べていない。。。そもそも日本語ドメイン名の必要性がよくわからない。)

May 20, 2014

iTermで接続先のホスト毎に背景色を変える

同時に複数ホストに接続していると、ステージングのつもりがうっかり商用。とかあり得なくもないので、調べてみました。

すると、既にやっている方がいたので、試してみました。(一応、自分のgistにもフォークしておきました。)
https://gist.github.com/BugRoger/789887

このスクリプトを適当な場所に保存して、実行権限を与えます。
$ cd /usr/local/bin/
$ wget https://gist.githubusercontent.com/BugRoger/789887/raw/e19958a97530f6be7f00f6d0526e56cbe4c42d73/ssh-background
$ chmod +x ssh-background
スクリプトの最後の方にある、ホスト名判定部分やカラーコードを、適当に書き換えます。文字色、背景色、透過度を変えられるみたいですね。

~/.bash_profile にsshコマンドのエイリアスを登録します。
alias ssh=/usr/local/bin/ssh-background
設定を反映します。
$ source ~/.bash_profile
$ alias
alias ssh='/usr/local/bin/ssh-background'
これで、ssh接続して、背景色が変わればOKです。

May 18, 2014

LaravelでOAuth2プロバイダを作ってみる

2014-05-18 追記:
このサンプルでは、OAuth2のプロバイダとクライアントが共存しています。また、飽くまで処理フローを確認するための簡単なサンプルですので、適宜、プロジェクトを分けたり、バックグラウンド処理にするなどの工夫が必要になります。

--

たぶん、初Laravelネタです。

"oauth2-server-laravel"という、OAuth2プロバイダ(クライアントではない)用のパッケージがあったので、試してみました。
https://github.com/lucadegasperi/oauth2-server-laravel

このパッケージは、以下のライブラリをLaravel用にラップしているようです。
https://github.com/thephpleague/oauth2-server

また、今回作成したサンプルプロジェクトを、GitHubにアップしてあります。
https://github.com/mamor/laravel-oauth2-server-sample
このサンプルプロジェクトは、以下の手順で動かせます。

ソースをインストールします。
$ git clone https://github.com/mamor/laravel-oauth2-server-sample.git
$ cd laravel-oauth2-server-sample
$ composer install
次に、app/config/database.phpを正しく設定します。また、app/config/app.phpの"url"をブラウザからアクセスするURLに変更します。後述のシーダと、フォームの初期値で使用している為です。私の手元の環境(掲載しているキャプチャ)では"http://192.168.33.10/oauth2"になっています。

インストールと設定が済んだら、以下の順でマイグレーションを実行します。
$ php artisan migrate --package="lucadegasperi/oauth2-server-laravel"
$ php artisan migrate --seed
トップ画面にアクセスすると、(リダイレクトされて)ログイン画面が表示されます。
以下でログインできます。
email: your-email@example.com
password: password

ログインに成功すると、OAuth2認証用のフォームが表示されます。必要な値はセットされているので、そのまま送信ボタンを押してOKです。

OAuth2認証に成功すると"code"値が付いて"/callback"にリダイレクトされます。この画面は、access_tokenを発行するためのフォームです。この画面でも、必要な値はセットされているので、そのまま送信ボタンを押してOKです。

access_tokenが発行されました。

試しに、各scopeにアクセスしてみます。

/scope1?access_token={発行されたアクセストークン}

/scope2?access_token={発行されたアクセストークン}

/scope3?access_token={発行されたアクセストークン}

scope3だけがエラーになればOKです。前述のリクエストで"scope"値を"scope1,scope2"としている為です。

ソース周りは https://github.com/mamor/laravel-oauth2-server-sample/commits/master をご覧頂ければと思います。

細かな機能はまだ把握できていませんが、OAuth2そのものの仕様と、冒頭に記載したOAuth2ライブラリの説明やソースを確認しながら。という感じでしょうか。

以上です。

April 15, 2014

Chrome ExtensionでGoogleのサービスにOAuth2認証するメモ

https://developer.chrome.com/apps/app_identity#google に書いてある通りですが、忘れそうなのでメモしておきます。

1.
開発で使用しているmanifest.jsonの"permissions"に"identity"を追加して、Chromeウェブストアにアップロードします。(2014/04/17 追記: "identity"の追記は、Chromeウェブストアへのアップロードと同時で無くて良いかもしれません。未確認ですが。)
"permissions": [
    "identity"
]

2.
Chromeウェブストア経由でインストールして、インストールディレクトリに移動します。

Macなら "~/Library/Application\ Support/Google/Chrome/Default/Extensions/" の中です。

インストールしたアプリのmanifest.jsonに"key"が追加されているので、開発で使用しているmanifest.jsonに書き足します。(2014/04/17 追記: どうもこの"key"は、ローカルで一時的に書き足す感じで、レポジトリにコミットするべきではないようです。書き足した状態でChromeウェブストアにアップしようとするとエラーになりました。)


3.
Google APIs Console でプロジェクトを作成します。
https://console.developers.google.com/project

プロジェクトを作成したら、"CREATE NEW CLIENT ID"から"Installed application"を選択、"Chrome Application"を選択、"Application ID"を入力します。

払い出された"Client ID"(最初から表示されている"Client ID"とは異なります。)を開発で使用しているmanifest.jsonに書き足します。必要なスコープも併せて書きます。
"oauth2": {
    "client_id": "xxx.apps.googleusercontent.com",
    "scopes": [
        "https://www.googleapis.com/auth/youtube"
    ]
}
試しに、適当な場所でトークンが取れるか確認してみます。
chrome.identity.getAuthToken({'interactive': true}, function (token) {
    console.log(token);
});
これで、コンソールにトークンが表示されれば、認証はOK(なはず)です。

April 12, 2014

Chrome.fmというChrome Extensionをリリースしました。

2014/04/19 追記:
名前がダメだったらしく、現在ストアから削除されています。後日、"YouFM"という名前にして、再度公開します。

2014/04/23 追記:
YouFMに名称変更して、再公開しました。
https://chrome.google.com/webstore/detail/youfm/gbpebippikipomjijplmbepmginobjbj

--

Chrome.fmという、YouTube動画を再生できるChrome Extensionをリリースしました。音楽プレイヤーのように使えます。

Chrome.fm:
https://chrome.google.com/webstore/detail/chromefm/gbpebippikipomjijplmbepmginobjbj

以下、特徴です。
  • 無料です(きっとずっと)
  • 動画の再生に、ウインドウ(タブ)を必要としません
  • シンプルなUIです
  • (手動で)再生中の動画名をTwitterにシェアできます

以下、近々実装予定の機能です。
  • 似た曲を垂れ流すラジオモード機能 (実装済み)
  • YouTubeプレイリストのインポート機能

尚、Chrome.fm内で発生したエラー情報は、品質向上を目的として、Sentryに送信されることがあります。このエラー情報に個人情報は含まれません。

開発的な部分を少し書いておくと
  • AngularJS
  • Bower
  • gulp.js
  • Jasmine
あたりを使っています。(JSライブラリはその他色々使っていますが。)

良かったら使ってみてください。アイデア等もお気軽に :)

April 5, 2014

AngularUIでsortable

AngularUIのsortableのメモです。
https://github.com/angular-ui/ui-sortable

AngularUIのsortableはjQuery UIのsortableをラッピングしています。
http://api.jqueryui.com/sortable/

以下、簡単なサンプルです。(console.log()結果がたまに間違っている気がするけど。。。)
http://jsfiddle.net/mamor/AWbRj/ このサンプルでは
  • jquery.min.js
  • jquery-ui.min.js
  • angular.min.js
  • angular-ui.min.js
を読み込んでいます。

AngularUIとjQuery UIのsortableドキュメントを見ながら、簡単に実装できそうですね。

April 3, 2014

AngularJSとAngularUIでBootstrapのModalを使う

AngularUIという、AngularJSを使ったUI開発用モジュール群があります。
http://angular-ui.github.io/
https://github.com/angular-ui

その中の"UI Bootstrap"を使うと、BootstrapのModalをAngularJSっぽく簡単に使うことができました。
http://angular-ui.github.io/bootstrap/

ソースは以下のような感じになります。
http://jsfiddle.net/mamor/4KBWj/

上記でAngularJSの他に読み込んでいるcssとjsは

Bootstrap公式のcss
https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css

ui-bootstrap-tpls.min.js
https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap-tpls.min.js

です。また、Modalで使用するHTMLのテンプレートは、インラインではなく外部ファイルにすることもできました。

一つのモーダルを、ng-repeatで繰り返した全データで共用するのとか簡単に出来て楽だなーと思いました。

March 30, 2014

March 29, 2014

AngularJSでGitHubのissuesとStack Overflowの質問を検索するツールを作ってみました

先日作ったツールを改良して、Stack Overflowの質問も検索できるようにしてみました。
http://madroom-project.blogspot.jp/2014/03/angularjsgithubissues.html

TroubleClinic
https://mamor.github.io/TroubleClinic/

今回、GitHubのAPI http://developer.github.com/v3/search/#search-issues を使ってみて、検索の精度が凄く高いなと思いました。

試しに、以前に自分がポストしたFuelPHPのissuesなんかも、それっぽいキーワードを指定したら、しっかりヒットしてくれました。

比べて、Stack OverflowのAPI https://api.stackexchange.com/docs/advanced-search は、まだ自分が慣れていないせいか、GitHubよりは意図した質問をヒットさせるのに苦労します。改良の余地があるかもしれませんね。

規模も小さいですし、(自分向け)AngularJSのサンプルとしても管理していこうかなと思います。

P.S.
PRくれた @hackoh さん、ありがとうございました :)

AngularJSでng-showを使ってローディング画像を表示したり非表示したりするメモ

ngShowが凄く便利だったのでメモしておきます。
http://docs.angularjs.org/api/ng/directive/ngShow

要は、対応する$scopeのプロパティで、表示非表示を切り替えられる感じでしょうか。

例えばHTML側で以下のようにして
<img src="xxx" ng-show="loading" />
JS側で
$scope.loading = true;
とすると表示されます。Ajax開始時にこれを行い、終わったらfalseにすれば非表示に戻りました。

March 26, 2014

AngularJSの独自ディレクティブをJasmineでspyしつつテストしてみた

以下の環境(今回のテストでは、jQueryを追加しています。)で
http://madroom-project.blogspot.jp/2014/03/gulp-karma-jasmine-phantomjs-angularjs.html

昨夜に作った独自ディレクティブをテストしてみました。
http://madroom-project.blogspot.jp/2014/03/angularjs.html
<div my-keypress-enter="enter(1)"></div>
のようなタグがある時にエンターキーが押されたら、対応する $scope.enter() が引数1で呼ばれる。みたいなテストです。

以下、CoffeeScriptですが、テスト(spec)のソースです。
describe 'myKeypressEnterDirective', () ->
  it 'should be bind keypress 13', inject ($rootScope, $compile) ->
    $scope = $rootScope.$new()

    $scope.spyCallback = () ->
    spyOn($scope, 'spyCallback')

    $element = $compile('<div my-keypress-enter="spyCallback(1)"></div>')($scope)

    $element.trigger($.Event('keypress', keyCode: 1))
    expect($scope.spyCallback).not.toHaveBeenCalled()

    $element.trigger($.Event('keypress', keyCode: 13))
    expect($scope.spyCallback).toHaveBeenCalledWith(1)
このやり方で良いのだろうか。(angular-scenario.js http://docs.angularjs.org/misc/downloading も気になる。。。)

AngularJSでGitHubのissuesを検索するサービスを作ってみた

とりあえずGitHubページに置いてあります。
http://mamor.github.io/gh-issues-search/
https://mamor.github.io/TroubleClinic/

レポジトリは以下です。
https://github.com/mamor/gh-issues-search
https://github.com/mamor/TroubleClinic

AngularJSを使ったら、思いついてから小一時間くらいでした。小規模だとBackbone.jsより楽かなーと思いました。

尚、APIのドキュメントは http://developer.github.com/v3/search/#search-issues になります。

細かな検索条件の指定とか、ローカルストレージを使ってレポのブックマークとかできるといーのかなーと思います。(気が向いたらやります。。。PR頂けると凄く嬉しいですw)

March 25, 2014

gulp.js + Karma + Jasmine + PhantomJS で AngularJS をテストしてみた

調べるのに少し時間がかかったので、メモがてら、書いておきます。(これが良いやり方なのかはわかりませんm(_ _)m)

gulpとkarmaをグローバルでインストールします。
$ sudo npm install -g gulp karma
どうもkarmaにパスが通っていないようなので、パスを通しておきます。
$ sudo ln -s /usr/lib/node_modules/karma/bin/karma /usr/local/bin/karma
(2014/05/23 追記: 上記、Ubuntu13.10上です。Macだと "/usr/local/lib/node_modules/karma/bin/karma" でした。)

必要なパッケージをローカルにインストールします。
$ mkdir xxx && cd xxx
$ npm install --save-dev gulp gulp-util gulp-karma karma-jasmine karma-phantomjs-launcher
package.json を自前で用意するなら、以下の様な感じになります。
{
    "name": "gulp-karma",
    "version": "0.1.0",
    "devDependencies": {
        "gulp": "~3.*",
        "gulp-util": "~2.*",
        "gulp-karma": "~0.*",
        "karma-jasmine": "~0.*",
        "karma-phantomjs-launcher": "~0.*"
    }
}
angular.js本体と、angular-mocks.js をダウンロードしておきます。
https://github.com/angular/angular.js/blob/master/src/ngMock/angular-mocks.js

今回は
  • ./vendor/angular.min.js
  • ./vendor/angular-mocks.js
として保存しました。

karmaの設定ファイルを作成します。以下、ブラウザを"Chrome"から"PhantomJS"に変えただけ(カーソルキーの上下で変えられました。)で、他はそのままです。
$ karma init

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes


Config file generated at "xxx/gulp-karma/karma.conf.js".
gulpfile.js を作成します。
var gulp = require('gulp');
var karma = require('gulp-karma');

gulp.task('karma', function () {
    var files = [
        'vendor/angular.min.js',
        'vendor/angular-mocks.js',
        'js/app.js',
        'spec/appSpec.js',
    ];

    gulp.src(files).pipe(karma({configFile: 'karma.conf.js'}));
});
テスト対象のjsファイルと、そのテスト(spec)のjsファイルを作成します。
$ mkdir js && mkdir spec
js/app.js
var myApp = angular.module('myApp', []);

myApp.filter('truncate', [function () {
    return function (text, length, end) {
        if (isNaN(length)) {
            length = 10;
        }

        if (end === void 0) {
            end = '...';
        }

        if (text.length <= length) {
            return text;
        }

        return text.substring(0, length - end.length) + end;
    };
}]);
spec/appSpec.js
describe('spec for popup.js', function() {
    beforeEach(function () {
        module('myApp');
    });

    it('should be truncated.', inject(function(truncateFilter) {
        expect(truncateFilter('1234567890')).toBe('1234567890');
        expect(truncateFilter('12345678901')).toBe('1234567...');
        expect(truncateFilter('1234567890', 4, '---')).toBe('1---');
        expect(truncateFilter('1234567890', 2, '---')).toBe('---');
    }));
});
テストを実行してみます。
$ gulp karma
[gulp] Using gulpfile /share/gulp-karma/gulpfile.js
[gulp] Starting 'karma'...
[gulp] Finished 'karma' after 7.23 ms
[gulp] Starting Karma server...
INFO [karma]: Karma v0.12.1 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Linux)]: Connected on socket 6KRg3iwj9PAR6jMwlCqC with id 13100382
PhantomJS 1.9.7 (Linux): Executed 1 of 1 SUCCESS (0.041 secs / 0.009 secs)
試しに、テストを適当に書き換えて失敗させてみます。
$ gulp karma
[gulp] Using gulpfile /share/gulp-karma/gulpfile.js
[gulp] Starting 'karma'...
[gulp] Finished 'karma' after 8.24 ms
[gulp] Starting Karma server...
INFO [karma]: Karma v0.12.1 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Linux)]: Connected on socket pjHN4RaPeEbzGFrtlZ35 with id 28598103
PhantomJS 1.9.7 (Linux) spec for popup.js should be truncated. FAILED
    Expected '---' to be '1--'.
PhantomJS 1.9.7 (Linux): Executed 1 of 1 (1 FAILED) ERROR (0.042 secs / 0.01 secs)
とりあえず、これでテストできるかなー。という感じにはなった気がします。