web-dev-qa-db-ja.com

JDatabase、SQLステートメントテンプレート

Hibernate HQLなどのステートメントテンプレートを使用する方法はありますか?

SQLQuery sql=s.createSQLQuery("SELECT AVG(RATING) as r, COUNT(*) as c FROM RATINGS WHERE ADVENTURE_ID = ?");
sql.setParameter(0, adventureId);

または、ORMについて話していなければ、PDOが好きですか?

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

文字列を連結したり、注入されたデータを手動で引用したりしたくないので、2017年は残念です。

$db = JFactory::getDbo();
$db->setQuery('SELECT params FROM #__extensions WHERE name = ' . $db->quote('com_democompupdate'));

さらに悪いことに

$query->select($db->quoteName(array('user_id', 'profile_key', 'profile_value', 'ordering')));
$query->from($db->quoteName('#__user_profiles'));
$query->where($db->quoteName('profile_key') . ' LIKE '. $db->quote('\'custom.%\''));
$query->order('ordering ASC');

このプロジェクトでは本当にORMは必要ありません。dbは永久にMySQLになります。接続を取得し、トランザクションを開始し、ステートメントテンプレートを実行する方法が必要です。

新しいPDO接続を作成したくないので、JDatabase接続で作成したいことに注意してください。コンポーネントにdbアカウントの情報を知らせたくありません。

2
inf3rno

$query->bindメソッドなので、準備済みステートメント、またはそれを呼び出すものを作成できます。しかし、私の場合、これはそれほど単純ではありません。私はバージョン3.4.3を持っています。私はサイトのメンテナーではないので、構成を変更したり、新しいバージョンに移行したりしたくありません。私は本番サーバーで実験したくないし、PHPの開発環境も持っていない。メモ帳だけだ。これは、私が小さなコンポーネントでのみ作業しており、PHPStormのようなIDEを購入して、これを最大5日間のプロジェクトで終わらせたくないためです。私もプロダクションでテストしています。これは完璧ではないことを理解しています...

設定で私はそれを見つけました$dbtype = 'mysqli'。私の知る限り。 mysqliは準備されたステートメントをサポートします もですが、必ずしもmysqliドライバーがサポートすることを意味するわけではありません。

2014年の初期のJoomla 3.xでは、SQLiteとOracleにのみPDOベースのドライバーがあり、他のドライバーは準備済みステートメントをサポートしていませんでした。

Joomlaのバージョンが2015年のものであることがわかりました。この機能 2016年にmysqliドライバーに追加されました 。簡単なQueryTemplateクラスを作成しました。これを回避策として使用します。可能な場合は新しいバージョンに移行し、本番環境でコードをテストしないでください。 :D:D:D

私のバージョンはトランザクションをサポートしていることがわかりました。少なくともそれは大丈夫です。 joomlaクラスをラップすることにしましたが、これは必須ではありません。テンプレートはそれなしで使用できます。

JoomlaQueryTemplate.php:

namespace Canteen\infrastructure;

use Canteen\infrastructure\iTemplate;
use JFactory;
use Exception;

class JoomlaQueryTemplate implements iTemplate {

    public function __construct($template){
        if (!is_string($template))
            throw new Exception('Invalid SQL template.');
        $this->template = $template;
    }

    public function evaluate($data){
        return preg_replace_callback('/:(\w+)/usD', function ($match) use ($data) {
            $param = $match[1];
            if (!array_key_exists($param, $data))
                throw new Exception('Not given param: '.$param);
            $value = JFactory::getDbo()->quote($data[$param]);
            return $value;
        }, $this->template);
    }

}

JoomlaConnection.php

namespace Canteen\infrastructure;

use Canteen\infrastructure\JoomlaQueryTemplate;
use JFactory;

class JoomlaConnection implements iConnection {

    protected $connection;

    public function __construct(){
        $this->connection = JFactory::getDbo();
    }

    public function execute($template, $data = array()){
        $queryTemplate = new JoomlaQueryTemplate($template);
        $query = $queryTemplate->evaluate($data);
        $this->connection->setQuery($query);
        $this->connection->execute();
    }

    public function getId(){
        return $this->connection->insertid();
    }

    public function query($template, $data = array()){
        $queryTemplate = new JoomlaQueryTemplate($template);
        $query = $queryTemplate->evaluate($data);
        $this->connection->setQuery($query);
        $this->connection->execute();
    }

    public function isEmpty(){
        $rowsCount = $this->connection->getNumRows();
        return $rowsCount == 0;
    }

    public function getMany(){
        return $this->connection->loadObjectList();
    }

    public function getOne(){
        return $this->connection->loadObject();
    }

    public function getValues(){
        return $this->connection->loadColumn();
    }

    public function getValue(){
        return $this->connection->loadResult();
    }

    public function beginTransaction(){
        $this->connection->transactionStart();
    }

    public function commit(){
        $this->connection->transactionCommit();
    }

    public function rollback(){
        $this->connection->transactionRollback();
    }

}

接続は私のリポジトリとアプリサービスに挿入されます。そのため、アプリサービスはトランザクションを処理でき、リポジトリはSQLクエリを送信できます。例えば:

public function readStatistics(){
    $statisticsDTO = new CustomerStatisticsDTO();
    try {
        $this->connection->beginTransaction();
        $statisticsDTO->setTotalCount($this->repository->countCustomers());
        $statisticsDTO->setActiveCount($this->repository->countActiveCustomers());
        $statisticsDTO->setSuspendedCount($this->repository->countSuspendedCustomers());
        $this->connection->commit();
    }
    catch (Exception $exception){
        $this->connection->rollback();
        throw $exception;
    }
    $statisticsDTO->setPassiveCount($statisticsDTO->getTotalCount() - $statisticsDTO->getActiveCount());
    $statisticsDTO->setOrderingCount($statisticsDTO->getActiveCount() - $statisticsDTO->getSuspendedCount());
    return $statisticsDTO;
}

そして

public function countSuspendedCustomers(){
    $today = new DateTime('today');
    $this->connection->query(
        'SELECT COUNT(`#__canteen_customers`.`user_id`) AS `result` '.
        'FROM `#__canteen_customers` '.
        'WHERE '.
            '0 < ('.
                'SELECT count(`#__canteen_suspensions`.`suspension_id`) AS `active_suspension_count` '.
                'FROM `#__canteen_suspensions` '.
                'WHERE '.
                    '`#__canteen_customers`.`user_id` = `#__canteen_suspensions`.`user_id` AND '.
                    '`#__canteen_suspensions`.`suspension_from` <= :date AND '.
                    '(`#__canteen_suspensions`.`suspension_to` IS NULL OR `#__canteen_suspensions`.`suspension_to` >= :date)'.
            ') AND '.
            '`#__canteen_customers`.`customer_active` = TRUE',
        array(
            'date' => $today->format('Y-m-d')
        )
    );

    return (int) $this->connection->getValue();
}

(selectステートメントのみでロールバックする必要がないことはわかっていますが、コードのその部分をコピーして貼り付けただけで、害はありません。作業単位の方が良いと思いますが、私は学習していますそのパターンは後で。)

元のjoomlaクエリビルダーAPIも良いと確信していますが、クエリビルダーコードと結果のSQLとの間の変換方法を学ぶ必要がなかったので、準備されたステートメントを使用するほうが自然に感じました。たとえば、後でpgsqlを使用したくないので、生成する代わりにsqlを記述してもかまいません。よく選択できる場合は、準備済みステートメントをサポートする最新のjoomlaバージョンをインストールし、これの代わりにそれを使用します。

1
inf3rno