ti-enxame.com

JDatabase, modelo de instrução SQL

Existe uma maneira de usar um modelo de instrução, como o Hibernate HQL?

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

Ou, se não estamos falando sobre ORMs, como no DOP?

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

Eu realmente não quero concatenar strings e citar os dados injetados manualmente, o que é tão decepcionante em 2017.

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

ou pior ainda

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

Eu realmente não preciso de ORM neste projeto, o banco de dados será o MySQL para sempre, só preciso de uma maneira de obter conexão, iniciar transações e executar modelos de instrução.

Observe que não quero criar uma nova conexão PDO, gostaria de fazer isso com uma conexão JDatabase. Não quero que o componente saiba nada da conta db.

2
inf3rno

Eu descobri que há um $query->bind, para que seja possível criar uma declaração preparada, ou como quisermos. Mas no meu caso, isso não é tão simples. Eu tenho a versão 3.4.3, não sou o mantenedor do site, por isso não quero alterar a configuração ou migrar para uma versão mais recente. Não quero experimentar no servidor de produção e não tenho um ambiente de desenvolvimento para PHP, apenas o bloco de notas. Isso ocorre porque estou trabalhando apenas em um componente pequeno e não quero comprar IDEs como o PHPStorm apenas para encerrar o projeto no máximo por cinco dias. Também testei em produção. Eu entendo que isso está longe de ser perfeito ...

Na configuração, descobri que $dbtype = 'mysqli'. Até onde sei. o mysqli suporta instruções preparadas também, mas isso não significa necessariamente que o driver mysqli também o suporte.

No início do Joomla 3.x em 2014, apenas o SQLite e o Oracle tinham drivers baseados em DOP, e os outros drivers não suportavam instruções preparadas.

Descobri que minha versão do Joomla é de 2015 e esse recurso foi adicionado ao driver mysqli em 2016 . Eu escrevi uma classe QueryTemplate simples, vou usar isso como solução alternativa. Eu recomendo fortemente que todos os outros migrem para uma nova versão, se possível, e nunca teste seu código no ambiente de produção! : D: D: D

Eu descobri que minha versão suporta transações. Pelo menos está tudo bem. Decidi encerrar a classe joomla, mas isso não é obrigatório, você pode usar o modelo sem isso.

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

}

A conexão é injetada nos meus repositórios e serviços de aplicativo. Portanto, os serviços de aplicativos podem lidar com transações e os repositórios podem enviar consultas sql. Por exemplo:

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

e

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

(Eu sei que não preciso reverter apenas por instruções de seleção, mas copiei e colei essa parte do código, e isso não causa nenhum dano. Seria melhor com a unidade de trabalho, acho, mas estou aprendendo esse padrão mais tarde.)

Tenho certeza de que a API original do construtor de consultas joomla também é boa, mas me senti mais natural ao usar instruções preparadas, porque não precisava aprender a traduzir entre o código do construtor de consultas e o sql resultante. Eu não quero usar, por exemplo, o pgsql mais tarde, então não há problema em escrever sql em vez de gerá-lo. Ofc. se você pode escolher, instale a versão mais recente do joomla, que suporta instruções preparadas, e use-a em vez disso.

1
inf3rno