[php][emacs] php-completion.el の補完にSPLクラスを追加


とりあえず補完されればいいのでfunctionsに追加。(classesがなさそう)

;; SPLクラスの補完追加
(phpcmp-db-update
 'functions
 '(
   "AppendIterator"
   "ArrayIterator"
   "ArrayObject"
   "BadFunctionCallException"
   "BadMethodCallException"
   "CachingIterator"
   "Countable"
   "DirectoryIterator"
   "DomainException"
   "EmptyIterator"
   "FilterIterator"
   "InfiniteIterator"
   "InvalidArgumentException"
   "IteratorIterator"
   "LengthException"
   "LimitIterator"
   "LogicException"
   "NoRewindIterator"
   "OuterIterator"
   "OutOfBoundsException"
   "OutOfRangeException"
   "OverflowException"
   "ParentIterator"
   "RangeException"
   "RecursiveArrayIterator"
   "RecursiveCachingIterator"
   "RecursiveDirectoryIterator"
   "RecursiveFilterIterator"
   "RecursiveIterator"
   "RecursiveIteratorIterator"
   "RecursiveRegexIterator"
   "RegexIterator"
   "RuntimeException"
   "SeekableIterator"
   "SimpleXMLIterator"
   "SplFileInfo"
   "SplFileObject"
   "SplObjectStorage"
   "SplObserver"
   "SplSubject"
   "SplTempFileObject"
   "UnderflowException"
   "UnexpectedValueException"
   ))

[symfony][propel][mysql] symfony 1.2 の sfPropelPager で MySQL 行カウント SQL_CALC_FOUND_ROWS を使う


http://ossipedia.ipa.go.jp/capacity/EV0603280115/

<?php
$c = new Criteria();
$c->addDescendingOrderByColumn(ArticlePeer::CREATED_AT);

$pager = new myPropelPager('Article', 10);
$pager->setCriteria($c);
$pager->setPage($page);
//$pager->init();         // COUNT
$pager->initFoundRows();  // SQL_CALC_FOUND_ROWS
<?php

class myPropelPager extends sfPropelPager
{
    protected $results;

    public function initFoundRows()
    {
        $hasMaxRecordLimit = ($this->getMaxRecordLimit() !== false);
        $maxRecordLimit = $this->getMaxRecordLimit();

        $c1 = $this->getCriteria();
        if ($c1 instanceof myCriteria) {
            $c = $c1;
        } else {
            $c = new myCriteria();
            $c->fromCriteria($c1);
        }

        $c->addSelectModifier(myCriteria::SQL_CALC_FOUND_ROWS)->
            setOffset(0)->
            setLimit(0);

        $offset = ($this->getPage() - 1) * $this->getMaxPerPage();
        $c->setOffset($offset);

        if ($hasMaxRecordLimit) {
            $maxRecordLimit = $maxRecordLimit - $offset;
            if ($maxRecordLimit > $this->getMaxPerPage()) {
                $c->setLimit($this->getMaxPerPage());
            } else {
                $c->setLimit($maxRecordLimit);
            }
        } else {
            $c->setLimit($this->getMaxPerPage());
        }

        $this->setCriteria($c);
        $this->results = parent::getResults();

        $con = Propel::getConnection(constant($this->getClassPeer() . '::DATABASE_NAME'), Propel::CONNECTION_READ);
        $stmt = $con->query('SELECT FOUND_ROWS()');
        $count = $stmt->fetchColumn();

        $this->setNbResults($hasMaxRecordLimit ? min($count, $maxRecordLimit) : $count);

        if (($this->getPage() == 0 || $this->getMaxPerPage() == 0)) {
            $this->setLastPage(0);
        } else {
            $this->setLastPage(ceil($this->getNbResults() / $this->getMaxPerPage()));
        }
    }

    public function getResults()
    {
        if ($this->results === null) {
            $this->results = parent::getResults();
        }

        return $this->results;
    }
}

class myCriteria extends Criteria
{
    const SQL_CALC_FOUND_ROWS = 'SQL_CALC_FOUND_ROWS';

    protected $_selectModifiers = array();

    public function addSelectModifier($modifier)
    {
        $this->_selectModifiers[] = $modifier;
    }

    public function getSelectModifiers()
    {
        return array_merge(parent::getSelectModifiers(), $this->_selectModifiers);
    }

    public function clear()
    {
        $this->_selectModifiers = array();
        parent::clear();
    }

    public function fromCriteria(Criteria $c)
    {
        array_map(array($this, 'addSelectColumn'), $c->getSelectColumns());
        array_map(array($this, 'addGroupByColumn'), $c->getGroupByColumns());
        array_map(array($this, 'add'), $c->getMap());

        foreach ($c->getAsColumns() as $name => $clause) {
            $this->addAsColumn($name, $clause);
        }

        foreach ($c->getJoins() as $join) {
            $this->addJoin($join->getLeftColumns(), $join->getRightColumns(), $join->getJoinType());
        }

        foreach ($c->getSelectModifiers() as $selectModifier) {
            switch ($selectModifier) {
                case Criteria::DISTINCT:
                    $this->setDistinct();
                    break;
                case Criteria::ALL:
                    $this->setAll();
                    break;
            }
        }

        foreach ($c->getOrderByColumns() as $column) {
            list($name, $order) = explode(' ', $column);
            switch ($order) {
                case Criteria::ASC:
                    $this->addAscendingOrderByColumn($name);
                    break;
                case Criteria::DESC:
                    $this->addDescendingOrderByColumn($name);
                    break;
            }
        }

        if ($having = $c->getHaving()) {
            $this->addHaving($having);
        }

        // TODO addAlias

        $this->setUseTransaction($c->isUseTransaction());
        $this->setDbName($c->getDbName());

        $this->
            setIgnoreCase($c->isIgnoreCase())->
            setSingleRecord($c->isSingleRecord())->
            setOffset($c->getOffset())->
            setLimit($c->getLimit());

        return $this;
    }
}

symfony 1.2のsfTesterResponseクラスを日本語に対応させる

<?php
$browser->check('/', 'テスト')->
    with('response')->matches('/\w+の日記/u');
<?php

class myTesterResponse extends sfTesterResponse
{
    public function contains($text)
    {
        $this->tester->like(
            $this->getContent(),
            '/' . preg_quote($text, '/') . '/u',
            sprintf('response contains "%s"', mb_substr($text, 0, 40))
        );

        return $this->getObjectToReturn();
    }

    public function matches($regex)
    {
        if ($regex{0} == '!') {
            $this->tester->unlike(
                $this->getContent(),
                mb_substr($regex, 1),
                sprintf('response content does not match regex "%s"', mb_substr($regex, 1))
            );
        } else {
            $this->tester->like(
                $this->getContent(),
                $regex,
                sprintf('response content matches regex "%s"', $regex)
            );
        }

        return $this->getObjectToReturn();
    }

    protected function getContent()
    {
        $charset = $this->response->getCharset();
        $content = $this->response->getContent();

        if ($charset !== null && strtolower($charset) !== 'utf-8') {
            $content = mb_convert_encoding($content, 'UTF-8', $charset);
        }

        return $content;
    }
}

symfony 1.2で携帯用にSJISで出力する際の注意点

フィルタでSJISに変換して出力している場合、
view.yml で以下のように設定して

default:
  http_metas:
    content-type: application/xhtml+xml; charset=Shift_JIS

テンプレート内で以下のようにするとタイトルが空になる。

<?php $sf_response-setTitle('テスト') ?>


sfWebResponse::fixContentType() で charset設定が上書きされてしまい、UTF-8の文字列がSJISエスケープされるのが原因。
対処法としては以下の2つがありそう。


1. view.ymlではcontent-typeを設定しないでlayout.phpに直接記述する。

<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=Shift_JIS" />
<?php include_title() ?>

<?php include_metas() ?>


2. 動的にタイトルを変更したい場合はアクション内で行う。

<?php
$this->getResponse()-setTitle($var);

Net_UserAgent_Mobileでモバイル版Yahoo!検索のSoftBank用ロボットのユーザエージェントの解析が失敗する


エラー処理をしていなかった場合、知らないところでfatalエラーが発生していてモバイル版Yahoo!検索の結果が悲惨なことに。
うごくひと2ランキング上位20サイトのうち4サイトで何かしらのエラーが発生。

NG

Vodafone/1.0/V705SH (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)

OK

DoCoMo/1.0/N505i/c20/TB/W20H10 (compatible; Googlebot-Mobile/2.1; +http://www.google.com/bot.html)
DoCoMo/2.0 SH902i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)
DoCoMo/2.0/SO502i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)
KDDI-CA33 UP.Browser/6.2.0.10.4 (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)
KDDI-CA34 UP.Browser/6.2.0.10.2.2 (GUI) MMP/2.0 (compatible; KDDI-Googlebot-Mobile/2.1; +http://www.google.com/bot.html)
J-PHONE/2.0/J-SH03 (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)

flymake で yaml のシンタックスチェック

#!/usr/bin/env php
<?php

require_once(dirname(__FILE__) . '/yaml/sfYamlParser.php');

$yaml = new sfYamlParser();

try {
    $yaml->parse(file_get_contents($_SERVER['argv'][1]));
} catch (InvalidArgumentException $e) {
    echo "Unable to parse the YAML string: " . $e->getMessage() . "\n";
}
(defun flymake-yaml-init ()
  (let* ((temp-file   (flymake-init-create-temp-buffer-copy
                       'flymake-create-temp-inplace))
         (local-file  (file-relative-name
                       temp-file
                       (file-name-directory buffer-file-name))))
    (list "flymake-yaml" (list local-file))))
(add-to-list 'flymake-allowed-file-name-masks
             '("\\.ya?ml$" flymake-yaml-init))
(add-to-list 'flymake-err-line-patterns
             '("Unable to parse the YAML string: Unable to parse line \\([0-9]+\\) \\(.*\\)"
               nil 1 nil 2))


Yaml component (Symfony Components)

symfony のキャッシュをcronで削除するときの注意点

拡張子 .php のキャッシュは中途半端に削除されるとエラーになる可能性が高いので除外する。
*.cache のファイルだけ削除するのが無難。

0 4 * * * find DIR/cache -name "*.cache" -mtime +7 -type f -print0 | xargs -0 rm -f