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)
とりあえず、これでテストできるかなー。という感じにはなった気がします。

AngularJSでエンターキー押下を監視するディレクティブを作ってみた

またまたAngularJS勉強メモです。フィルタに続いてディレクティブです。エンターキーの押下程度なら、コントローラの中でbindしてやっても良いのでしょうが、AngularJSっぽくするにはこんな感じなのかなーと思います。(自信は無いですが。)

http://jsfiddle.net/mamor/Azt3p/

AngularJSで指定文字長オーバー時に後ろを"..."にするフィルタを作ってみた

AngularJSの勉強がてら、独自フィルタを作ってみたので書いておきます。

http://jsfiddle.net/mamor/sB7uJ/1/
ほとんど http://jsfiddle.net/tUyyx/ のまんまですが。。。

AngularJSで、ng-repeatしたデータにマウスオーバー(リーブ)時、ng-showやng-styleを変えるメモ

これがスマートなやり方なのかはわかりませんが、とりあえず以下の方法で出来ました。マウスオーバーすると"age"が表示され、文字色も変わります。 http://jsfiddle.net/mamor/2MNk8/

March 24, 2014

chrome extensionでCDNからファイルを読み込むメモ

メモです。

例えば https://ajax.googleapis.com/ と https://netdna.bootstrapcdn.com/ から読み込む場合は manifest.json に
"content_security_policy": "script-src 'self' 'unsafe-eval' https://ajax.googleapis.com/ https://netdna.bootstrapcdn.com/; object-src 'self'"
と書いたら読み込めました。

March 22, 2014

ブログにソースコードを書く時に使う、Chrome用エスケープツールを(結構前に)作りました

ブログにソースコードを書く時、HTMLタグはそのままは書けませんし、タブや連続する半角スペースは、記事として表示した時にインデントが崩れたりします。

なので、HTMLタグをエスケープしたり、タブや半角スペース x 4 を"&nbsp;&nbsp;&nbsp;&nbsp;"に置換するChrome用エクステンションをこっそり作って使っていますw

前に書いた気もしますが、書いていない気もするので、改めて(?)書いておきます。

一式、以下に置いてあります。ストアには登録してませんm(_ _)m
https://github.com/mamor/escaper


1. git cloneなりzipでDLするなりで、適当な場所に保存します。
2. クロームの拡張機能設定でデベロッパーモードにチェックを入れます。
3. "パッケージ化されていない拡張機能を読み込む..." から、保存したソース一式のルートを指定します。(manifest.jsonがある場所です。)

すると、真っ白な正方形アイコン(手抜き)がクロームの右上に出てきます。真っ白な正方形アイコン(手抜き)をクリックして、ソースコードを貼り付けて "Escape & Copy to Clipboard" を押すと、エスケープしつつクリップボードに貼り付けられます。

良かったら使ってみてください。


オレオレPhalconで依存注入しつつテストしてみる

前回の記事で作ったAjaxFilter#filter()をテストしてみます。
http://madroom-project.blogspot.jp/2014/03/phalcon_1056.html

ディレクトリ構造とかは、適当に作っているオレオレPhalconを御覧ください。
http://madroom-project.blogspot.jp/2014/03/phalcon_21.html
https://github.com/mamor/phalcon-myapp

以下にPhalcon用の便利ツールっぽいものもあるのですが、今回は使わずにやってみます。
https://github.com/phalcon/incubator

まず app/bootstrap/helper.php を作成して、ヘルパ関数を作ってみました。di()は、Phalcon\DI\FactoryDefaultのインスタンスを返します。
<?php

use Phalcon\DI;

/**
 * @return Phalcon\DI\FactoryDefault
 */
function di()
{
    static $di = null;

    if (is_null($di)) {
        $di = (new DI)->getDefault();
    }

    return $di;
}
このファイルは app/bootstrap/bootstrap.php で読み込みます。
require_once __DIR__.'/helper.php';

次に composer.jsonに "phpunit/phpunit" と "mockery/mockery" と、この後作成する "app/tests/TestCase.php" を追加して composer update します。Mockeryは無くても良いですが、PHPUnitよりも少し簡単にモックが書けます。
 {
+    "require-dev": {
+        "phpunit/phpunit": "4.*",
+        "mockery/mockery": "0.*"
+    },
     "autoload": {
+        "classmap": [
+            "app/tests/TestCase.php"
+        ],
         "psr-0": {
             "Core": "app/classes",
             "App": "app/classes"

app/tests/TestCase.php を作成します。この後に作成するテストクラスの基底クラスになります。setMockToDI()は依存注入用メソッドです。
<?php

/**
 * Class TestCase
 */
abstract class TestCase extends \PHPUnit_Framework_TestCase {

    /**
     * @param  string $name
     * @param  mixed $mock
     * @param  bool $shared
     */
    protected function setMockToDI($name, $mock, $shared = null)
    {
        di()->set($name, function () use ($mock) {
            return $mock;
        }, $shared);
    }
}

プロジェクトルートに phpunit.xml を作成します。 http://docs.phalconphp.com/en/1.2.6/reference/unit-testing.html を基にしました。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./app/tests/bootstrap.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         verbose="true"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="true">
    <testsuite name="Phalcon - Testsuite">
        <directory>./app/tests/</directory>
    </testsuite>
</phpunit>

app/tests/bootstrap.php を作成します。composer の autoload.php と、前述のヘルパファイルを読み込んでいます。
<?php

include __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__.'/../bootstrap/helper.php';

app/tests/ の下に、名前空間とかを加味して、テストクラスを作成します。今回は app/tests/Core/Filter/AjaxFilterTest.php としています。内容の細かいことは最後に書きます。
<?php namespace Core\Filter;

use Mockery as m;

/**
 * Class AjaxFilterTest
 */
class AjaxFilterTest extends \TestCase {

    /**
     * test for filter()
     */
    public function testFilter() {
        $m = m::mock();
        $m->shouldReceive('getServer')->once()->with('X_REQUESTED_WITH')->andReturn('xmlhttprequest');
        $this->setMockToDI('request', $m);

        $this->assertTrue((new AjaxFilter)->filter());
    }

    /**
     * test for filter()
     */
    public function testFilterFailed() {
        $m = m::mock();
        $m->shouldReceive('getServer')->once()->with('X_REQUESTED_WITH')->andReturn(null);
        $this->setMockToDI('request', $m);

        $this->assertFalse((new AjaxFilter)->filter());
    }
}

phpunitを実行してみます。
$ phpunit
PHPUnit 4.0.12 by Sebastian Bergmann.

Configuration read from /share/phalcon_project/phpunit.xml

..

Time: 302 ms, Memory: 6.00Mb

OK (2 tests, 2 assertions)

AjaxFilterTestクラスでは、Mockeryのモックオブジェクトを作成しています。このモックオブジェクトを、基底テストクラスのsetMockToDI()メソッドに渡して依存注入して、AjaxFilter#filter()におけるgetServer()をコントロールしている感じです。

もっと良い方法あれば教えて下さいm(_ _)m (Phalcon\DI\Injectableとか上手く使えないかな。。。)


March 21, 2014

Phalconでルーティングをフィルタする

例えば、このコントローラのこのアクションは、Ajaxでしかアクセスさせたくない。という場合に、ルーティングでチェックできます。
http://docs.phalconphp.com/en/1.2.6/reference/routing.html#match-callbacks

まず、フィルタクラスを作成します。尚、di()という関数が書いてありますが、この後に書くユニットテスト関係になります。(書きました。 http://madroom-project.blogspot.jp/2014/03/phalcon_22.html )
<?php namespace Core\Filter;

/**
 * Class AjaxFilter
 */
class AjaxFilter
{
    /**
     * @var \Phalcon\Http\Request
     */
    protected $request;

    /**
     * constructor
     */
    public function __construct()
    {
        $this->request = di()->get('request');
    }

    /**
     * @return bool
     */
    public function filter()
    {
        return $this->request->getServer('X_REQUESTED_WITH') === 'xmlhttprequest';
    }
}
ルーティングで、以下のようにします。AjaxFilter#filter()の戻り値(bool)で、このルーティングにマッチするかしないかが決まります。beforeMatch()はチェインもできるようです。
$router->add('/', 'Index::index')->beforeMatch([new Core\Filter\AjaxFilter, 'filter']);
要認証の基底コントローラ、要Ajaxの基底コントローラ。とかを作ってコンストラクタで制御する手段もありますが、その場合、要認証かつ要Ajaxとかなってくると、ややこしくなるので、こんな感じが良いのかなと思いました。

追記: Phalcon\Mvc\Controllerのコンストラクタはどういうわけか final になっていました。また、beforeMatch()をチェインした時、一方しか働かないような挙動になりました。(まだ細かくは見ていませんが。)

次はこのフィルタクラスをテストしてみます。

オレオレPhalconを作ってみる (とりあえずルーティング周り)

追記: レポジトリ作りました。 https://github.com/mamor/phalcon-myapp

--

Phalconはディレクトリ構造が凄く自由なので、適当に自分好みな感じに作っていこうと思います。

Phalcon DevTools (1.3.0)で、プロジェクトを作ります。
$ phalcon project phalcon_project
  • app/controllers/ControllerBase.php
  • app/controllers/IndexController.php
は削除します。app/controllers/ が空になるので、一応 .gitkeep を入れておきました。

以下の composer.json を作成して "composer install" します。以後、app/classes/に、App\Xxx\Yyyみたいな名前空間でクラスを作成します。コントローラもその中に作ります。
{
    "autoload": {
        "psr-0": {
            "App": "app/classes"
        }
    }
}

public/index.php に、以下を追記します。services.phpの読み込み後にしました。
/**
 * Bootstrap
 */
include __DIR__ . "/../app/bootstrap/bootstrap.php";

app/bootstrap/bootstrap.php を作成します。composer の autoload.php と、自作ルーティング設定ファイルの読み込みを行っています。
<?php

require_once __DIR__.'/../../vendor/autoload.php';
require_once __DIR__.'/routes.php';

app/bootstrap/routes.php を作成します。自動ルーティングをしないようにして、"/"に対するコントローラ::アクションと、not foundなコントローラ::アクションを登録しています。
<?php

/** Phalcon\DI\FactoryDefault $di */

$di->set('router', function () {
    $router = new Phalcon\Mvc\Router(false);
    $router->setDefaults(['namespace' => 'App\Controller', 'controller' => 'error', 'action' => 'route404']);

    $router->add('/', 'Index::index');

    return $router;
});

app/classes/App/Controller ディレクトリを作成して、以下の3つのコントローラを作成します。

AppController.php
<?php namespace App\Controller;

use Phalcon\Mvc\Controller;

class AppController extends Controller
{
}
ErrorController.php
<?php namespace App\Controller;

class ErrorController extends AppController
{
    public function route404Action()
    {
        $this->response->setStatusCode(404, 'Not Found');
    }
}
IndexController.php
<?php namespace App\Controller;

class IndexController extends AppController
{
    public function indexAction()
    {
    }
}

app/views/ ディレクトリに、各ビューファイル(.volt)を作成 or 編集します。

layouts/default.volt
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>
index/index.volt
{% extends "layouts/default.volt" %}

{% block title %}Index{% endblock %}

{% block content %}
Index
{% endblock %}
error/route404.volt
{% extends "layouts/default.volt" %}

{% block title %}Error{% endblock %}

{% block content %}
Not Found
{% endblock %}

後ほど、GitHubにメモ用なレポつくろうと思います。

March 20, 2014

Phalconでオートコンプリートする

小ネタです。

https://github.com/phalcon/phalcon-devtools/tree/master/ide
で、使っているバージョンのディレクトリを、IDEで外部ライブラリに指定してあげればOKでした。尚、1.3.0は、今日現在"1.3.x"ブランチにあります。

Phalconと、PhalconのデベロッパーツールをUbuntu 13.10にインストールして、プロジェクトを作成してみた

少しハマったりしたので、手順をメモしておきます。

PHPのバージョンは、以下の通りです。
$ php -v
PHP 5.5.9-1+sury.org~saucy+1 (cli) (built: Feb 13 2014 15:58:58)
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.3, Copyright (c) 1999-2014, by Zend Technologies
ドキュメント http://phalconphp.com/ja/download を見た限り、必要なパッケージは揃っていそうだったので、Phalcon本体だけインストールしようとしたら、エラーが出ました。
$ git clone git://github.com/phalcon/cphalcon.git
$ cd cphalcon/
$ git checkout 1.3.0
$ cd build/
$ sudo ./install

# ... 略 ...

/bin/bash /home/vagrant/cphalcon/build/64bits/libtool --mode=compile gcc  -I. -I/home/vagrant/cphalcon/build/64bits -DPHP_ATOM_INC -I/home/vagrant/cphalcon/build/64bits/include -I/home/vagrant/cphalcon/build/64bits/main -I/home/vagrant/cphalcon/build/64bits -I/usr/include/php5 -I/usr/include/php5/main -I/usr/include/php5/TSRM -I/usr/include/php5/Zend -I/usr/include/php5/ext -I/usr/include/php5/ext/date/lib  -DPHALCON_RELEASE -DHAVE_CONFIG_H  -march=native -mtune=native -O2 -finline-functions -fomit-frame-pointer -fvisibility=hidden   -c /home/vagrant/cphalcon/build/64bits/phalcon.c -o phalcon.lo
libtool: compile:  gcc -I. -I/home/vagrant/cphalcon/build/64bits -DPHP_ATOM_INC -I/home/vagrant/cphalcon/build/64bits/include -I/home/vagrant/cphalcon/build/64bits/main -I/home/vagrant/cphalcon/build/64bits -I/usr/include/php5 -I/usr/include/php5/main -I/usr/include/php5/TSRM -I/usr/include/php5/Zend -I/usr/include/php5/ext -I/usr/include/php5/ext/date/lib -DPHALCON_RELEASE -DHAVE_CONFIG_H -march=native -mtune=native -O2 -finline-functions -fomit-frame-pointer -fvisibility=hidden -c /home/vagrant/cphalcon/build/64bits/phalcon.c  -fPIC -DPIC -o .libs/phalcon.o
In file included from /usr/include/php5/ext/spl/spl_iterators.h:27:0,
                 from /home/vagrant/cphalcon/build/64bits/phalcon.c:202:
/usr/include/php5/ext/pcre/php_pcre.h:29:18: fatal error: pcre.h: No such file or directory
 #include "pcre.h"
                  ^
compilation terminated.
make: *** [phalcon.lo] Error 1
README.md https://github.com/phalcon/cphalcon を見ると "libpcre3-dev" が足りないようだったので、インストールしたら解決しました。
$ sudo apt-get install libpcre3-dev
$ sudo ./install

# ... 略 ...

Build complete.
Don't forget to run 'make test'.

Installing shared extensions:     /usr/lib/php5/20121212/

Thanks for compiling Phalcon!
Build succeed: Please restart your web server to complete the installation
インストールが出来たので php.ini に "extension = phalcon.so" を追記して、cliから確認してみると、またエラーが出ました。
$ php -r "print_r(get_loaded_extensions());" | grep phalcon
PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib/php5/20121212/phalcon.so' - /usr/lib/php5/20121212/phalcon.so: undefined symbol: php_pdo_get_dbh_ce in Unknown on line 0
どうもエクステンションの読み込み順序のようで、php.iniではなく
  • /etc/php5/cli/conf.d/phalcon.ini
  • /etc/php5/apache2/conf.d/phalcon.ini
を作成して、それぞれに "extension = phalcon.so" を記述してやると、とりあえず動きました。
$ php -r "print_r(get_loaded_extensions());" | grep phalcon
    [55] => phalcon
次に、適当なディレクトリで composer.json を作ってデベロッパーツールをインストールしました。
{
    "require": {
        "phalcon/devtools": "dev-master"
    }
}
$ composer install
パスを通しておきます。
$ sudo ln -s /xxx/yyy/phalcon-devtools/vendor/bin/phalcon.php /usr/local/bin/phalcon
$ phalcon

Phalcon DevTools (1.3.0)

Available commands:
  commands (alias of: list, enumerate)
  controller (alias of: create-controller)
  model (alias of: create-model)
  all-models (alias of: create-all-models)
  project (alias of: create-project)
  scaffold
  migration
  webtools
さっそく、プロジェクトを作ってみます。
$ phalcon project phalcon_project

Phalcon DevTools (1.3.0)


  Success: Controller "index" was successfully created.


  Success: Project 'phalcon_project' was successfully created.
symlinkを貼るなどして、ブラウザからアクセスしてみます。
$ sudo ln -s /zzz/phalcon_project/public /var/www/phalcon
(凄く素っ気ない画面ですがw)とりあえず、これでOKかなー。

March 18, 2014

PHPで動的にZIPを作ってダウンロードさせるメモ

メモです。
<?php

$zip = new ZipArchive;

$zipName = 'test.zip';

if ($zip->open($zipName, ZipArchive::CREATE) === true) {
    $zip->addFromString('test1.txt', 'test1');
    $zip->addFromString('test2.txt', 'test2');
    $zip->close();

    header('Content-Type: application/zip');
    readfile($zipName);

    unlink($zipName);
}

PredictionIOをvagrantで起動してみる

PredictionIOという、機械学習サーバー(?)が気になったので、とりあえずvagrantで起動してみました。
http://prediction.io/

手順は http://docs.prediction.io/current/installation/install-predictionio-with-virtualbox-vagrant.html の通りですが、自分メモです。

PredictionIO指定のboxを追加します。
$ vagrant box add precise64 http://files.vagrantup.com/precise64.box
PredictionIOが公開しているVagrantfile等を取得します。
$ mkdir predictionio
$ cd predictionio/
$ git clone https://github.com/PredictionIO/PredictionIO-Vagrant.git
$ cd PredictionIO-Vagrant/
$ git checkout v0.6.8 # 今日現在の最新バージョンです。
起動します。
$ vagrant up
ユーザを作成します。
$ vagrant ssh

# 以下、仮想マシンの中です。
$ /opt/PredictionIO/bin/users

PredictionIO CLI User Management

1 - Add a new user
2 - Update email of an existing user
3 - Change password of an existing user
Please enter a choice (1-3): 1
Adding a new user
Email: mamor@example.com
Password: *****
Retype password: *****
First name: Mamoru
Last name: Otsuka
User added
http://localhost:9000/ にアクセスして、作成したユーザでログインしてみます。


各言語のSDKも提供されているので、細かな機能は、気が向いた時にちゃんと見てみようと思います。。。
https://github.com/PredictionIO

March 16, 2014

Ubuntuでフォントのインストールメモ

グローバルにインストールする場合
$ sudo apt-get install -y fontconfig
$ sudo apt-get install -y ttf-sazanami-gothic ttf-sazanami-mincho
ローカルにインストールする場合(~/.fonts/以下にインストールする)
~/.fonts$ ll
total 67440
drwxrwxr-x  2 vagrant vagrant     4096 Mar 16 18:00 ./
drwxr-xr-x 18 vagrant vagrant     4096 Mar 16 18:01 ../
-rw-r--r--  1 vagrant vagrant 20104092 Dec  8 00:41 HanaMinA.ttf
-rw-r--r--  1 vagrant vagrant 24169832 Dec  8 08:58 HanaMinB.ttf
-rwxrwxrwx  1 vagrant vagrant 24737240 Mar 16 18:00 hanazono-20131208.zip*
-rw-r--r--  1 vagrant vagrant     5852 Dec  8 12:33 LICENSE.txt
-rw-r--r--  1 vagrant vagrant    17304 Dec  8 12:41 README.txt
-rw-r--r--  1 vagrant vagrant     1444 Dec  8 12:32 THANKS.txt

Chart.jsのチャートをPNGで保存するライブラリを作ってみました

追記: Google Chartsもサポートしました。

--

レポジトリは
https://github.com/mamor/phantom-chart-to-png
です。

使い方は "Usage" の通りですが、ポイントを簡単に書いておきます。

  • 要PhantomJSです。
  • 引数の"data"と"opts"は、Chart.jsのチャートに渡すdataとoptionsです。

Chart.jsのdataとoptionsについては、公式のドキュメントを御覧ください。
http://www.chartjs.org/docs/

作った経緯なんですが、チャートをPDFに埋め込みたく、画像出力できるチャートライブラリを探していました。

PHPだとpChart 2が有名そうですが、ライセンスをよく理解していなかったり、その他のライブラリでピンとくるものも無かったりでした。そこで、JSのライブラリは豊富そうだったので、PhantomJSを使えばできるかなーと思い立って試してみた感じです。

このスクリプトをPHPから叩くサンプルも用意しよう。

Chart.jsのグラフを画像化するメモ

メモです。

Chart.js
http://www.chartjs.org/
https://github.com/nnnick/Chart.js/

サンプル

K-9 Mail UnreadCountに新ウィジェットを実装しました。

K-9 Mail UnreadCount
https://play.google.com/store/apps/details?id=net.madroom.k9uc

外人さんからアイデア頂き(なんとフォトショでイメージ付き!)、良いアイデアだと思ったので、実装してみました。


デザインは質素です。すみませんm(_ _)m

2x1のサイズで、最大4アカウントの未読数を個別に表示します。
5アカウント目以降含め、全アカウントの未読数は上部に表示されます。

実はもうK-9 Mailは使っていないので、活発な更新はしていません。
なので、MITライセンスにしたので誰か引き継いで下さいw
https://github.com/mp-android/K9MailUnreadCount

(結構前に作ったので幼稚な書き方とかもありますが、最低限のリファクタリングはするので。。。w)

March 11, 2014

PHP製SSHツールのenvoyでプロジェクトをデプロイしてみる

以前、Rocketeerについて調べました。
https://github.com/Anahkiasen/rocketeer
http://madroom-project.blogspot.jp/search/label/rocketeer

今回は、PHP製SSHツールのenvoyを使って、プロジェクトをデプロイしてみます。
https://github.com/laravel/envoy

使い方は、非常にシンプルです。


envoy.phar をダウンロードします。
$ wget https://github.com/laravel/envoy/raw/master/envoy.phar
設定ファイルを生成します。尚、この例の192.168.33.10には鍵認証ができる前提です。
$ php envoy.phar init 192.168.33.10
Envoy.blade.phpというファイルが作成されるので、cdするディレクトリをサーバのプロジェクトルートに変更します。
@servers(['web' => '192.168.33.10'])

@task('deploy')
    cd /xxx/yyy/my_project
    git pull origin master
@endtask
デプロイを実行します。(実行結果のurlは適当に書き換えています。)
$ php envoy.phar run deploy
[vagrant]: From https://github.com/mamor/my_project
 * branch            master     -> FETCH_HEAD
[vagrant]: Already up-to-date.
簡単ですね。初回のclone等は、別途対応する必要がありますが。README.mdを見ると、1タスクをNサーバに対して実行することもできるみたいです。

envoyと比べると、Rocketeerは設定が多いですが、例えばPHPUnitの実行結果によりデプロイの続行/中止を制御してくれたりするので、一概にどちらが優れているとは言えないと思います。envoyでそれをやろうとすると、symlinkの貼替えとか、かえって面倒そうですし。プロジェクトの性質によって使い分けていきたいなと思います。

March 8, 2014

PHPUnitでprotectedなメソッドをテストするメモ

以前 "ReflectionClassでpublicではないプロパティやメソッドにアクセスするメモ" という記事を書いたんですが、PHPUnitでの使い方をメモしておきます。
<?php

require_once('vendor/autoload.php');

class Example
{
    protected function protectedMethod($value)
    {
        return $value;
    }
}

class ExampleTest extends PHPUnit_Framework_TestCase
{
    protected function getMethod($object, $methodName)
    {
        $reflection = new ReflectionClass($object);
        $method = $reflection->getMethod($methodName);
        $method->setAccessible(true);

        return $method;
    }

    public function testProtectedMethod()
    {
        $example = new Example;
        $method = $this->getMethod($example, 'protectedMethod');

        $this->assertTrue($method->invokeArgs($example, ['xxx']) === 'xxx');
    }
}
$ phpunit ExampleTest.php
PHPUnit 4.0.3 by Sebastian Bergmann.

.

Time: 38 ms, Memory: 4.75Mb

OK (1 test, 1 assertion)
実際には、上記のExampleTest#getMethod()は、各テストケースの基底クラスに書く感じになるはずです。

Rocketeer の rocketeer.phar を自分でコンパイルしてみる

以下の方法ですんなりできました。OS X Mavericks(PHP 5.4.24)で確認しています。(デプロイ実行の確認まではしていませんが。。。)
$ git clone https://github.com/Anahkiasen/rocketeer.git
$ cd rocketeer/
$ composer install
$ chmod +x ./bin/compile
$ ./bin/compile
$ php ./bin/rocketeer.phar

Rocketeer version 1.1.2

Current state
  application     {application_name}
  configuration   /xxx/rocketeer/.rocketeer
  tasks           /xxx/rocketeer/.rocketeer/tasks
  events          /xxx/rocketeer/.rocketeer/events
  logs            /xxx/rocketeer/.rocketeer/logs

Usage:
  [options] command [arguments]

Options:
  --help           -h Display this help message.
  --quiet          -q Do not output any message.
  --verbose        -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  --version        -V Display this application version.
  --ansi              Force ANSI output.
  --no-ansi           Disable ANSI output.
  --no-interaction -n Do not ask any interactive question.
  --env               The environment the command should run under.

Available commands:
  check      Check if the server is ready to receive the application
  cleanup    Clean up old releases from the server.
  current    Display what the current release is
  deploy     Deploy the website.
  flush      Flushes Rocketeer's cache of credentials
  help       Displays help for a command
  ignite     Creates Rocketeer's configuration
  list       Lists commands
  rollback   Rollback to the previous release, or to a specific one
  setup      Set up the remote server for deployment
  teardown   Remove the remote applications and existing caches
  test       Run the tests on the server and displays the output
  update     Update the remote server without doing a new release.

March 3, 2014

sort()とnatsort()の違いメモ

sort()は文字列に含まれる部分を辞書順に、natsort()は数値順にソートします。キーを維持する/しないも異なりました。また、natcasesort()は、文字の大文字小文字を区別しません。

http://jp2.php.net/natsort
http://jp2.php.net/natcasesort

<?php

$array = ['a1', 'a2', 'a10'];

sort($array);

print_r($array);
/*
Array
(
    [0] => a1
    [1] => a10
    [2] => a2
)
*/

natsort($array);

print_r($array);
/*
Array
(
    [0] => a1
    [2] => a2
    [1] => a10
)
*/

$array = ['a1' ,'A2', 'a10'];

natsort($array);

print_r($array);
/*
Array
(
    [1] => A2
    [0] => a1
    [2] => a10
)
*/

natcasesort($array);

print_r($array);
/*
Array
(
    [0] => a1
    [1] => A2
    [2] => a10
)
*/

array_reduce()を使ってみる

マニュアルには"コールバック関数を用いて配列を普通の値に変更することにより、配列を再帰的に減らす"とあります。
http://php.net/array_reduce

よくわからないんですが、とりあえず 1,2,3...100 の総和を計算してみました。
$ret = array_reduce(range(1, 100), function ($x, $y) {
        return $x + $y;
});

echo $ret; // 5050
array_sum()でも同じことができました。
echo array_sum(range(1, 100)); // 5050
array_reduce()ってどんな時に使うと便利なのかなー。

usort(),uasort(),uksort()の挙動を確認してみた

標準のソート関数であるsort()やksort()等の他に、ユーザ定義な基準でソートが出来るusort(),uasort(),uksort()があります。たまに使ったことはある気がしますが、ちゃんと覚えていないので、挙動の確認をしてみました。

http://www.php.net/manual/ja/function.usort.php
http://www.php.net/manual/ja/function.uasort.php
http://www.php.net/manual/ja/function.uksort.php

それぞれ、戻り値(数値)の昇順にソートされます。
<?php

$origin = $array = [
    'x' => 'XXXX',
    'xxx' => 'XX',
    'xx' => 'XXX',
    'xxxx' => 'X',
];

// $arrayを、文字数の少ない値順でソートする。キーは0から振り直される
usort($array, function ($x, $y) {
        return strlen($x) - strlen($y);
});

print_r($array);
/*
Array
(
    [0] => X
    [1] => XX
    [2] => XXX
    [3] => XXXX
)
*/

// $arrayを元に戻す
$array = $origin;

// $arrayを、文字数の少ない値順でソートする。キーは維持される
uasort($array, function ($x, $y) {
        return strlen($x) - strlen($y);
});

print_r($array);
/*
Array
(
    [xxxx] => X
    [xxx] => XX
    [xx] => XXX
    [x] => XXXX
)
*/

// $arrayを、文字数の少ないキー順でソートする
uksort($array, function ($x, $y) {
        return strlen($x) - strlen($y);
});

print_r($array);
/*
Array
(
    [x] => XXXX
    [xx] => XXX
    [xxx] => XX
    [xxxx] => X
)
*/

PHPのイテレータに関するメモ

PHPの配列は、foreach文やfor文で処理することが多いですが、イテレータを用いて、1つずつ進めながら処理することもできます。
http://php.net/manual/ja/class.iterator.php

以下、簡単な例です。
<?php

$array = ['a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'];

reset($array); // おまじない

// current()は現在地の値、key()は現在地のキーを返す
echo current($array); // A
echo key($array); // a

// next()は、現在地を一つ進めて、その値を返す
echo next($array); // B
echo key($array); // b

// prev()は、現在地を一つ戻して、その値を返す
echo prev($array); // A
echo key($array); // a

// end()は、現在地を最後まで進めて、その値を返す
echo end($array); // D
echo key($array); // d

// reset()は、現在地を最初に戻して、その値を返す
echo reset($array); // A
echo key($array); // a

// each()は、現在地の要素を返して、現在地を一つ進める
list($k, $v) = each($array);
echo $k, $v; // aA

list($k, $v) = each($array);
echo $k, $v; // bB

extract()とcompact()のメモ

extract()で配列から変数を作れます。逆に、compact()で変数から配列を作れます。
http://www.php.net/extract
http://www.php.net/compact
// extract()
$array = ['k' => 'v', 'k2' => 'v2'];
extract($array); // 第二、第三引数次第で、接頭辞の指定もできます
echo $k, $k2; // vv2

// compact()
$x = 'X';
$y = 'Y';

print_r(compact('x', 'y'));
/*
Array
(
    [x] => X
    [y] => Y
)
*/

print_r(compact(['x', 'y']));
/*
Array
(
    [x] => X
    [y] => Y
)
*/

March 2, 2014

strspn()とstrcspn()のメモ

strspn()は、指定したマスク文字から成る、最初のセグメントの長さを返します。
http://www.php.net/strcspn
$subject = '12345 67890';
$mask = '1234';

// $subjectの"1234"の部分は$maskに含まれるので、"4"
echo strspn($subject, $mask);
strcspn()は逆に、指定したマスク文字以外から成る、最初のセグメントの長さを返します。
http://www.php.net/strcspn
$subject = '561234 7890';
$mask = '1234';

// $subjectの"56"の部分は$maskに含まれないので、"2"
echo strcspn($subject, $mask);
というわけで、本日の文字列系関数の記事は、以上です。。。

strpos()やstrstr()や、それに似た関数のメモ

メモです。。。
<?php

$haystack = 'IamMamor';
$needle = 'am';

// strpos()は、最初に見つかった位置を返す
// http://www.php.net/manual/ja/function.strpos.php
echo strpos($haystack, $needle).PHP_EOL; // 1

// strrpos()は、最後に見つかった位置を返す
// http://www.php.net/manual/ja/function.strrpos.php
echo strrpos($haystack, $needle).PHP_EOL; // 4

// strstr()は、最初に見つかった位置以降の文字列を返す
// strchr()は、strstrのエイリアス
// http://www.php.net/manual/ja/function.strstr.php
// http://www.php.net/manual/ja/function.strchr.php
echo strstr($haystack, $needle).PHP_EOL; // amMamor

// strrchr()は、最後に見つかった位置以降の文字列を返す
// http://www.php.net/manual/ja/function.strrchr.php
echo strrchr($haystack, $needle); // amor

// stristr()は、大文字小文字を区別しない
// http://www.php.net/manual/ja/function.stristr.php
$haystack = 'IAMMAMOR';
$needle = 'am';
echo strstr($haystack, $needle).PHP_EOL; // false
echo stristr($haystack, $needle).PHP_EOL; // AMMAMOR

sscanf()のメモ

sscanf関数を使うと、文字列をフォーマットで分解できます。
http://php.net/manual/ja/function.sscanf.php

例えば、'yyyy-mm-dd hh:ii:ss'形式の文字列を一発で分解できました。
list($yyyy, $mm, $dd, $hh, $ii, $ss) = sscanf(date('Y-m-d H:i:s'), '%d-%d-%d %d:%d:%d');

echo $yyyy.PHP_EOL;
echo $mm.PHP_EOL;
echo $dd.PHP_EOL;
echo $hh.PHP_EOL;
echo $ii.PHP_EOL;
echo $ss.PHP_EOL;

/* 出力
2014
3
2
19
44
25
*/
使ったこと無い関数たくさんあるな。。。

strtok()のメモ

strtok関数は、第一引数に与えられた文字列を、第二引数で与えられたトークンで分割します。二回目以降の呼び出しの際は、トークンのみを引数に取ります。初期化したい場合は、改めて、二つの引数を指定します。
http://www.php.net/manual/ja/function.strtok.php

<?php
$str = 'My name is|mamor';
$token = ' |';

$tok = strtok($str, $token);
while ($tok !== false) {
    echo $tok.PHP_EOL;
    $tok = strtok($token);
}

/* 出力結果
My
name
is
mamor
*/
explode()でも似たようなことはできますが、巨大文字列に対して処理をする時とか、strtok()の方がメモリ節約になる。みたいなことが、以下に書かれていました。
http://stackoverflow.com/questions/2528168/whats-the-use-of-function-strtokin-php-how-is-better-than-other-string-functi

strcmp()や、それに似た関数のメモ

メモです。。。

<?php

// strcmp(string $str1, string $str2) は文字列を辞書順に比較する
// http://www.php.net/manual/ja/function.strncmp.php

// 等しい場合
$str1 = 'abc';
$str2 = 'abc';
echo strcmp($str1, $str2); // 0
echo PHP_EOL;

// $str1 > $str2
$str1 = 'abd';
$str2 = 'abc';
echo strcmp($str1, $str2); // 1
echo PHP_EOL;

// $str1 > $str2
$str1 = 'acx';
$str2 = 'abc';
echo strcmp($str1, $str2); // 1
echo PHP_EOL;

// $str2 > $str1
$str1 = 'abc';
$str2 = 'abd';
echo strcmp($str1, $str2); // -1
echo PHP_EOL;

// $str2 > $str1
$str1 = 'abc';
$str2 = 'acx';
echo strcmp($str1, $str2); // -1
echo PHP_EOL;

// strcasecmp(string $str1, string $str2) は大文字小文字を区別しない
// http://www.php.net/manual/ja/function.strcasecmp.php

$str1 = 'abc';
$str2 = 'ABC';
echo strcmp($str1, $str2); // 32
echo PHP_EOL;
echo strcasecmp($str1, $str2); // 0
echo PHP_EOL;

// strncmp(string $str1, string $str2, int $len) は指定した位置までを比較する
// http://www.php.net/manual/ja/function.strncmp.php
// strncasecmp(string $str1, string $str2, int $len) については割愛

$str1 = 'abd';
$str2 = 'abc';
echo strncmp($str1, $str2, 3); // 1
echo PHP_EOL;
echo strncmp($str1, $str2, 2); // 0
echo PHP_EOL;

// strnatcmp(string $str1, string $str2) は文字列を自然順に比較する
// http://www.php.net/manual/ja/function.strnatcmp.php
// strnatcasecmp(string $str1, string $str2, int $len) については割愛

$str1 = 'ab3';
$str2 = 'ab10';
echo strcmp($str1, $str2); // 2
echo PHP_EOL;
echo strnatcmp($str1, $str2); // -1
echo PHP_EOL;