web-dev-qa-db-fra.com

JDatabase, modèle d'instruction SQL

Existe-t-il un moyen d'utiliser un modèle de déclaration, comme par Hibernate HQL?

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

Ou si nous ne parlons pas d'ORM, alors comme par PDO?

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

Je ne souhaite vraiment pas concaténer des chaînes et citer manuellement les données injectées, ce qui est décevant en 2017.

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

ou même pire

$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');

Je n'ai pas vraiment besoin d'ORM dans ce projet, la base de données sera MySQL pour toujours, j'ai juste besoin d'un moyen d'obtenir une connexion, de commencer des transactions et d'exécuter des modèles d'instructions.

Notez que je ne veux pas créer une nouvelle connexion PDO, j'aimerais le faire avec une connexion JDatabase. Je ne veux pas que le composant sache rien du compte de la base de données.

2
inf3rno

J'ai trouvé il y a un $query->bind méthode, il est donc possible de créer une instruction préparée, ou ce que nous appelons cela. Mais dans mon cas, ce n'est pas si simple. J'ai la version 3.4.3, je ne suis pas le responsable du site, je ne souhaite donc pas modifier la configuration ni migrer vers une version plus récente. Je ne veux pas expérimenter sur le serveur de production et je n'ai pas d'environnement de développement pour PHP, seulement du bloc-notes. En effet, je ne travaille que sur un petit composant et je ne souhaite pas acheter d’EDI comme PHPStorm pour terminer ce projet de 5 jours tout au plus. Je teste aussi en production. Je comprends que c'est loin d'être parfait ...

Dans la configuration, j'ai trouvé que $dbtype = 'mysqli'. Autant que je sache. mysqli supporte les instructions préparées aussi, mais cela ne signifie pas nécessairement que le pilote mysqli le supporte aussi.

Au début de Joomla 3.x en 2014, seuls SQLite et Oracle disposaient de pilotes basés sur PDO, tandis que les autres pilotes ne prenaient pas en charge les instructions préparées.

J'ai découvert que ma version de Joomla date de 2015 et cette fonctionnalité a été ajoutée au pilote mysqli en 2016 . J'ai écrit une classe QueryTemplate simple, je vais l'utiliser comme solution de contournement. Je recommande fortement à tout le monde de migrer vers une nouvelle version si cela est possible et de ne jamais tester votre code en environnement de production! : D: D: D

J'ai trouvé que ma version prend en charge les transactions. Au moins ça va. J'ai décidé d'emballer la classe joomla, mais ce n'est pas obligatoire, vous pouvez utiliser le modèle sans cela.

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();
    }

}

La connexion est injectée dans mes services de dépôt et d’application. Les services de l’application peuvent donc gérer les transactions et le dépôt peut envoyer des requêtes SQL. Par exemple:

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;
}

et

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();
}

(Je sais que je ne suis pas obligé de revenir en arrière par certaines déclarations uniquement, mais j'ai simplement copié-collé cette partie du code, et cela ne fait aucun mal. Ce serait mieux avec une unité de travail, je suppose, mais j'apprends ce modèle plus tard.)

Je suis à peu près sûr que l’API de constructeur de requêtes joomla d’origine est également bon, mais j’ai eu l’impression plus naturelle d’utiliser des instructions préparées, car je n’avais pas à apprendre à traduire entre le code du générateur de requêtes et le SQL résultant. Je ne veux pas utiliser par exemple pgsql plus tard, alors je peux écrire SQL au lieu de le générer. Ofc. si vous pouvez choisir, installez la dernière version de joomla, qui prend en charge les instructions préparées, et utilisez-la à la place de celle-ci.

1
inf3rno