‘PHP’ タグのついている投稿

PhalconのコマンドラインでError: This command should be invoked inside a Phalcon project directory

phalcon create-modelをしようとして、上記エラーが発生しました。

プロジェクトディレクトリで実行しろということなのですが、プロジェクトディレクトリには既にいるはず…

Error: This command should be invoked inside a Phalcon project directory – Discussion – Phalcon Framework

上記サイトでは.phalconファイルを作成すれば良いという話。が、正解は.phalconディレクトリを作る必要があります。

結局のところ、プロジェクトを自分で作った人ではなく、リポジトリからダウンロードしてきた場合に多く発生します。

というのも、自分のところはMercurialですが、中身が空のディレクトリがリポジトリに登録できないという仕様なので、.phalconディレクトリをリポジトリにコミットし損ねていたということですね。

$ mkdir ./.phalcon
$ touch ./.phalcon/empty

などとして、ファイルを追加してコミットしておけば解決です。


UbuntuでApache 2.4.6(Event MPM)+PHP 5.4(FastCGI)を使う

環境はAmazon EC2のUbuntu 12.10です。Apache 2.4.6を使ってみます。Apache 2.4系列を使うからには、やっぱりEvent MPMを使いたいところです。

ところがPHPのマニュアルにはPreforkを使えと書いてあります。

PHP: インストール – Manual
http://www.php.net/manual/ja/faq.installation.php#faq.installation.apache2

マルチスレッドMPMを使いたい場合はFastCGIを使うようにも書いてあるので、PHPをFastCGIで動かすようにします。

(さらに…)


Symfony 1.4でHybridAuthを使ってtwitter/facebookログインを実装する

schema.ymlにUserモデルを以下のように作っておきます。

User:
  actAs: { Timestampable: ~ }
  tableName: users
  options:
    type: MyISAM
    collate: utf8_unicode_ci
    charset: utf8
    comment: 'ユーザー'
  columns:
    id:
      type: integer(4)
      unsigned: false
      primary: true
      autoincrement: true
      comment: 'ID'
    name:
      type: string(255)
      fixed: false
      notnull: true
      default: 'no name'
      comment: 'ユーザー名'
    image:
      type: blob
      notnull: false
      comment: 'アイコン画像'
    hybridauth_provider_name:
      type: string(20)
      fixed: false
      notnull: true
      comment: 'HybridAuthプロバイダ'
    hybridauth_provider_uid:
      type: string(255)
      fixed: false
      notnull: true
      comment: 'HybridAuthプロバイダUID'

HybridAuthをダウンロードします。

Download HybridAuth

ちなみにSymfony2用にはHybridAuthのバンドルがあります。1.4用にも欲しい…

$ cd lib/vendor
$ wget http://jaist.dl.sourceforge.net/project/hybridauth/hybridauth-2.0.11.zip
$ unzip ./hybridauth-2.0.11.zip
$ rm ./README.html
$ rm -R ./examples/
$ rm ./hybridauth-2.0.11.zip
$ cd ../..
$ mkdir lib/config

lib/configにsfEnvironmentYamlConfigHandler.class.phpを作成します。sfSimpleYamlConfigHandlerを継承して、環境設定が反映されるように変更します。

<?php

class sfEnvironmentYamlConfigHandler extends sfSimpleYamlConfigHandler
{
    public function execute($configFiles)
    {
        $config = self::getConfiguration($configFiles);
        
        // compile data
        $retval = "<?php\n".
                  "// auto-generated by %s\n".
                  "// date: %s\nreturn %s;\n";
        $retval = sprintf($retval, __CLASS__, date('Y/m/d H:i:s'), var_export($config, true));
        
        return $retval;
    }

    static public function getConfiguration(array $configFiles)
    {
        return self::replaceConstants(self::flattenConfigurationWithEnvironment(self::parseYamls($configFiles)));
    }
}

configにconfig_handlers.ymlを作成します。設定ファイルの処理の仕方をここで指定しています。

config/hybrid_auth.yml:
  class: sfEnvironmentYamlConfigHandler

で、同じくconfigにhybrid_auth.ymlファイルを作成します。

all:
  base_url: http://localhost/login/endpoint
  providers:
    OpenID:
      enabled: true

    Yahoo:
      enabled: true

    AOL:
      enabled: true

    Google:
      enabled: true
      keys:
        id: ""
        secret: ""
      scope: ""

    Facebook:
      enabled: true
      keys:
        id: ""
        secret: ""

      # A comma-separated list of permissions you want to request from the user.
      # See the Facebook docs for a full list of available permissions:
      # http://developers.facebook.com/docs/reference/api/permissions.
      scope: ""

      # The display context to show the authentication page.
      # Options are: page, popup, iframe, touch and wap.
      # Read the Facebook docs for more details:
      # http://developers.facebook.com/docs/reference/dialogs#display.
      # Default: page
      display: ""

    Twitter:
      enabled: true
      keys:
        key: ""
        secret: ""

    # windows live
    Live:
      enabled: true
      keys:
        id: ""
        secret: ""

    MySpace:
      enabled: true
      keys:
        key: ""
        secret: ""

    LinkedIn:
      enabled: true
      keys:
        key: ""
        secret: ""

    Foursquare:
      enabled: true
      keys:
        id: ""
        secret: ""

  # if you want to enable logging, set 'debug_mode' to true
  # then provide a writable file by the web server on "debug_file"
  debug_mode: false
  debug_file: ""

TwitterとFacebookのKeyとSecretにそれぞれOAuthのConsumer KeyとConsumer Secretを入れておきます。TwitterはCallback URLをダミーでも構わないので入れておかないといけないので注意。

Twitter https://dev.twitter.com/apps

Facebook https://developers.facebook.com/apps/

設定内容はHybridAuthのconfig.phpと内容的には同じものです。YAMLに書き換えたのと、devやprodなど環境ごとに設定を切り替えられるようになっています。デフォルトはall以下に書けばOKです。

lib/vendor以下はオートロードされないので、configにautoload.ymlファイルを作成します。

autoload:
  hybridauth:
    name:           hybridauth
    path:           %SF_LIB_DIR%/vendor/hybridauth
    recursive:      true

次にアクション側を実装します。

authモジュールを作ります。

$ ./symfony generate:module frontend auth

routing.ymlにログインとエンドポイントを追加します。

login_endpoint:
  url:   /login/endpoint
  param: { module: auth, action: endPoint }

login:
  url:   /login/:provider
  param: { module: auth, action: index }
  requirements:
    provider: Facebook|Google|Twitter

apps/frontend/lib/myUser.class.phpを編集してIDとユーザー名を保持できるようにします。

<?php

class myUser extends sfBasicSecurityUser
{

    const ATTRIBUTE_NAMESPACE = 'localhost/user/myUser/attributes';

    public function setName($name)
    {
        $this->setAttribute('name', $name, self::ATTRIBUTE_NAMESPACE);
    }

    public function getName()
    {
        return $this->getAttribute('name', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function setId($id)
    {
        $this->setAttribute('id', $id, self::ATTRIBUTE_NAMESPACE);
    }

    public function getId()
    {
        return $this->getAttribute('id', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function setImage($image)
    {
        $this->setAttribute('image', $image, self::ATTRIBUTE_NAMESPACE);
    }

    public function getImage()
    {
        return $this->getAttribute('image', null, self::ATTRIBUTE_NAMESPACE);
    }

    public function getImageBase64()
    {
        return base64_encode($this->getImage());
    }

}

lib/model/doctrine/UserTable.class.phpにプロバイダとUIDでユーザー名を検索するメソッドを実装します。

    public function getUserByProviderAndUid($providerName, $uid)
    {
        return $this->createQuery('u')
            ->where('u.hybridauth_provider_name = ?', $providerName)
            ->andWhere('u.hybridauth_provider_uid = ?', $uid)
            ->fetchOne();
    }

apps/frontend/modules/auth/actions/actions.class.phpを編集します。

<?php

class authActions extends sfActions
{

    public function executeIndex(sfWebRequest $request)
    {
        
        $config = sfContext::getInstance()->getConfigCache()->checkConfig(sfConfig::get('sf_config_dir').'/hybrid_auth.yml');
        
        try{
            $hybridauth = new Hybrid_Auth($config);
            
            $adapter = $hybridauth->authenticate($request->getParameter('provider'));
            
            $user_profile = $adapter->getUserProfile();
            
            $user = Doctrine::getTable('User')->getUserByProviderAndUid($adapter->id, $user_profile->identifier);
            
            if (!$user) {
                // 新規作成
                $user = new User();
                $user->set('name', $user_profile->displayName);
                $user->set('image', file_get_contents($user_profile->photoURL));
                $user->set('hybridauth_provider_name', $adapter->id);
                $user->set('hybridauth_provider_uid', $user_profile->identifier);
                $user->save();
            }
            
            $this->getUser()->setAuthenticated(true);
            $this->getUser()->setId($user->get('id'));
            $this->getUser()->setName($user->get('name'));
            $this->getUser()->setImage($user->get('image'));
            
            $this->redirect('@homepage');
            
        }
        catch( Exception $e ){  
            switch( $e->getCode() ){ 
            case 0 : echo "Unspecified error."; break;
            case 1 : echo "Hybriauth configuration error."; break;
            case 2 : echo "Provider not properly configured."; break;
            case 3 : echo "Unknown or disabled provider."; break;
            case 4 : echo "Missing provider application credentials."; break;
            case 5 : echo "Authentification failed. " 
            . "The user has canceled the authentication or the provider refused the connection."; 
            break;
            case 6 : echo "User profile request failed. Most likely the user is not connected "
            . "to the provider and he should authenticate again."; 
            $twitter->logout(); 
            break;
            case 7 : echo "User not connected to the provider."; 
            $twitter->logout(); 
            break;
            case 8 : echo "Provider does not support this feature."; break;
            } 
            
            // well, basically your should not display this to the end user, just give him a hint and move on..
            echo "<br /><br /><b>Original error message:</b> " . $e->getMessage();  
        }
        
        return sfView::NONE;
    }

    public function executeEndPoint(sfWebRequest $request)
    {
        Hybrid_Endpoint::process();
        
        return sfView::NONE;
    }

}

これでhttp://localhost/login/Twitterにアクセスすれば、Twitterの認証ページへジャンプしてログインができるようになります。

ログイン後のユーザー表示とかは、テンプレートでこんな感じに。

<img src="<?php echo 'data:;base64,' . $sf_user->getImageBase64(); ?>" alt="<?php echo $sf_user->getName(); ?>">&nbsp;
<?php echo $sf_user->getName(); ?>

後でどうせキャッシュしてしまうのでbase64_encodeのオーバーヘッドは無視です。


Windows版PHP 5.4.0でAPCを使う

まずはAPCをダウンロードします。

downloads.php.net/pierre/

php_apc-3.1.10-5.4-vc9-x86.zip (2012-04-11 23:46 -0700)
をダウンロードしました。

展開すると、ntsとtsのフォルダが作成されます。使っているPHPがNon Thread Safe版かThread Safe版かによって使う方を決めてください。

それぞれのフォルダの中に、php_apc.dllがあるので、phpがインストールされているフォルダのextフォルダにコピーします。

その後、php.iniを開いて
extension=php_apc.dll
を追加し、Apacheを再起動すればOKです。

ちなみに、dllのバージョンを見ると、5.4.1RC1-devと書かれていました。

phpinfo()ではこのようになります。特に問題なく使えているようです。

WS000000


PHPからarpテーブルを参照する

PHPからarpコマンドを実行してLAN内のMACアドレスとIPアドレスの変換テーブルを参照できるようにしてみました。単純に結果を正規表現にかけているだけです。

ちなみにひとつのMACが複数のIPを持つ場合には対応していません。それと、arpの性質上、頻繁にキャッシュクリアされるので、用途によっては注意が必要です。

<?php

$table = array();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    exec('arp -a', $output); // for Windows
}
else {
    exec('/usr/sbin/arp -a -n', $output); // for Linux(debian)
}
foreach ($output as $line) {
    if (preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*([0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2})/i', $line, $matches)) {
        $ip = $matches[1];
        $mac = strtoupper(str_replace(array('-', ':'), '', $matches[2]));
        $table[$mac] = $ip;
    }
}

var_dump($table);

結果

array(2) {
  ["123456789ABC"]=>
  string(13) "192.168.0.1"
  ["0003FFFFFFFF"]=>
  string(15) "192.168.131.254"
}

Linuxのarpコマンドがarp -aで実行するとすごく遅いので、原因を調べていたら、デフォルトではIPアドレスからDNSの逆引きをしているということでした。-nオプションを付けることで、DNSリクエストを送らなくなるので一瞬で表示できます。


タスクスケジューラとPHPのコンソールアプリケーション

PHP5.3+Silexを使って、Windows上で定期的に処理をするコンソールアプリケーションを作っていたのですが、タスクスケジューラが起動するタイミングで、デスクトップにコマンドプロンプトのウィンドウ(DOS窓)が表示されてしまう現象に悩んでいました。

ウィンドウを表示させない方法として、ショートカットを作成してウィンドウ最小化するという手で試してみたのですが、taskeng.exeというタイトルのウィンドウが出たままになって止まってしまう状況でした。(多分PHPファイルを引数として認識していない)

結論としては簡単で、php.exeの代わりにphp-win.exeを使うだけです。

PHP: CLI と CGI – Manual

php-win.exeはコンソールを表示させないので、これを実行ファイルとしてタスクスケジューラに登録すればOKです。

ついでにタスクスケジューラに登録する方法も書いておきます。5分おきにPHPを実行し続けるタスクをコマンドプロンプトから登録するコマンドです。最後の/Fは確認メッセージの抑制。

schtasks /create /tn "Test PHP" /tr "C:\php\php-win.exe C:\hoge\console.php args" /sc minute /mo 5 /it /F

削除はこちら。

schtasks /delete /tn "Test PHP" /f


Silexで外部APIにHTTP Requestする

最近Silex始めました。

まずは外部のWebServerにリクエストを投げる方法です。軽量なWebClientであるBuzzを使います。gitでBuzz本体とProviderをダウンロードします。

$ cd vendor/
$ git clone https://github.com/kriswallsmith/Buzz.git Buzz
$ cd Buzz/
$ git checkout v0.5
$ git submodule update --init
$ cd ..
$ git clone https://github.com/marcw/silex-buzz-extension.git silex-buzz-extension

src/bootstrap.phpに追加します。

$app['autoloader']->registerNamespace('MarcW', __DIR__ . '/../vendor/silex-buzz-extension/lib');

$app->register(new MarcW\Silex\Provider\BuzzServiceProvider(), array(
    'buzz.options' => array(
    ),
    'buzz.class_path' => __DIR__ . '/../vendor/Buzz/lib',
));

これで準備完了です。あとはアプリケーションから

$app['buzz']->getClient()->setTimeout(30);
$response = $app['buzz']->get('http://www.google.co.jp');

echo $app['buzz']->getLastRequest()."\n";
echo $response;

として呼び出すことができます。Buzzのデフォルトのタイムアウトが5秒で、早すぎたので30秒に変更しておきました。


sfGuardFormSigninのメッセージをカスタマイズする

Symfony1.4でsfDoctrineGuardPluginを使っていて、ユーザ名かパスワードが違うときに、

The username and/or password is invalid.

と表示されてしまうので、これを変更したいと思います。それと、ラベルも日本語にしておきたいと思います。

sfGuardFormSigninを継承したフォームクラスをひとつ作ります。今回は/app/front/modules/sfGuardAuth/lib/form/mySigninForm.class.phpに作りました。

<?php

class mySigninForm extends sfGuardFormSignin
{
    public function configure()
    {
        parent::configure();
        
        $this->widgetSchema['username']->setLabel('ユーザID');
        $this->widgetSchema['password']->setLabel('パスワード');
        
        $this->validatorSchema['username']->setMessage('required', '入力して下さい');
        // $this->validatorSchema['username']->setMessage('invalid', 'ユーザIDかパスワードが違います'); // 効かない
        $this->validatorSchema['password']->setMessage('required', '入力して下さい');
        
        $validator = $this->getValidatorSchema()->getPostValidator();
        $validator->setMessage('invalid', 'ユーザIDかパスワードが違います');
    }
}

PostValidatorを取得してくるのが肝です。前述のメッセージはsfGuardValidatorUserで定義されているのですが、このバリデーターはBasesfGuardFormSigninでsetPostValidator(new sfGuardValidatorUser())として呼ばれているためです。

ちなみに、mySigninFormを有効にするには、/app/front/config/app.ymlに

all:
  sf_guard_plugin:
    signin_form: mySigninForm

を追加して、デフォルトのフォームクラスを変更する必要があります。


DoctrineMongoDBBundleでAbstractDoctrineExtensionのエラー

PHP Fatal error:  Class ‘Symfony\\Bridge\\Doctrine\\DependencyInjection\\AbstractDoctrineExtension’ not found in /home/path/to/Symfony/vendor/bundles/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php on line 31

php – DoctrineMongoDBBundle getting a fatal error in Symfony2 – Stack Overflow

masterリポジトリが書き換えられていて、最新バージョンでは動かないのでdepsファイルにDoctrineMongoDBBundleのレポジトリを指定するときに、バージョン番号を指定する必要があるということでした。
DoctrineMongoDBBundleセクションにversion=v2.0.0を追加すればOKです。

[doctrine-mongodb]
    git=http://github.com/doctrine/mongodb.git

[doctrine-mongodb-odm]
    git=http://github.com/doctrine/mongodb-odm.git

[DoctrineMongoDBBundle]
    git=http://github.com/symfony/DoctrineMongoDBBundle.git
    target=/bundles/Symfony/Bundle/DoctrineMongoDBBundle
    version=v2.0.0


Windows版のPHP5.3がc0000005で強制終了する

Symfony 1.4.16をWindowsのPHP 5.3.9で実行したときに、特定の条件で強制終了する現象に悩まされていました。
主にSymfonyタスクでdoctrine:data-dumpなどを実行するときに落ちていました。

色々調べた結果、Symfonyに問題があるわけではなく、/lib/vendor/symfony/lib/config/sfDefineEnvironmentConfigHandler.class.phpがきっちり4096バイトだったということが原因でした。

つまり、PHP5.3.9では、4096バイトのファイルをrequireもしくはincludeするとc0000005(Access Violation)で強制終了します。CLIに限らず、Apache経由でも同じです。

試しに4096バイトの一切コードが書かれていないテキストファイルを作ってrequireしてみると、再現しました。

強制終了時のウィンドウと、内容は以下のとおり。

image

ちなみにモジュール名はnts(Non Thread Safe)版ならphp5.dll、ts(Thread Safe)版ならphp5ts.dllです。
どちらも確認しましたが、どちらも同じ現象です。モジュールは入っていても入っていなくても同じです。

問題の署名:
  問題イベント名:    APPCRASH
  アプリケーション名:    php.exe
  アプリケーションのバージョン:    5.3.9.0
  アプリケーションのタイムスタンプ:    4f0c5cd0
  障害モジュールの名前:    php5.dll
  障害モジュールのバージョン:    5.3.9.0
  障害モジュールのタイムスタンプ:    4f0c5dfb
  例外コード:    c0000005
  例外オフセット:    00090f5b
  OS バージョン:    6.1.7601.2.1.0.256.48
  ロケール ID:    1041
  追加情報 1:    0a9e
  追加情報 2:    0a9e372d3b4ad19135b953a78882e789
  追加情報 3:    0a9e
  追加情報 4:    0a9e372d3b4ad19135b953a78882e789

多分現象はこのバグ報告と同じだと思います。
PHP :: Bug #60758 :: require() crashes Apache

5.4.0RC5でも解決してないみたいですね。報告とほぼ同じですが、これで再現できます。

<?php
file_put_contents('test.txt', str_pad('', 4096));
include('./test.txt');

とりあえず対処法としては改行1文字でも増やしておけばいいってことですか…