What is the difference between ActiveRecord and Repository?

1

I'm reading a POO book in PHP that calls PHP Object Oriented Programming [Pablo Dall'Oglio] and got a bit confused with these two design patterns , especially when it makes use of the Repository using ActiveRecord. It is worth noting that in the examples does not contain use of namespace, prepare (SQL Injection), because in this chapter of the book it has not been dealt with yet.

<?php
abstract class Record
{
protected $data; //Array contendo os dados do objeto

public function __construct($id = NULL)
{
    if ($id) { //Se o ID for informado
        //Carrega o objeto correspondente
        $object = $this->load($id);
        if ($object) {
            $this->fromArray($object->toArray());
        }
    }
}

public function __clone() 
{
    unset($this->data['id']);
}

public function __set($prop, $value)
{
    if (method_exists($this, 'set_'.$prop)) {
        //Executa o método set_<propriedade>
        call_user_func(array($this, 'set_'.$prop), $value);
    } else {
        if ($value === NULL) {
            unset($this->data[$prop]);
        } else {
            $this->data[$prop] = $value;
        }
    }
}

public function __get($prop) 
{
    if (method_exists($this, 'get_'.$prop)) {
        //Executa o método get_<propriedade>
        return call_user_func(array($this, 'get_'.$prop));
    } else {
        if(isset($this->data[$prop])) {
            return $this->data[$prop];
        }
    }
}

public function __isset($prop)
{
    return isset($this->data[$prop]);
}

private function getEntity()
{
    $class = get_class($this); //Obtém o nome da classe
    return constant("{$class}::TABLENAME"); //Retorna a constante de classe TABLENAME
}

public function fromArray($data)
{
    $this->data = $data;
}

public function toArray()
{
    return $this->data;
}

public function store()
{
    $prepared = $this->prepare($this->data);

    //Verifica se tem ID ou se existe na base de dados
    if (empty($this->data['id']) or (!$this->load($this->id))) {
        //Incrementa o ID
        if (empty($this->data['id'])) {
            $this->id = $this->getLast() + 1;
            $prepared['id'] = $this->id;
        }

        //Cria uma instrução de insert
        $sql = "INSERT INTO {$this->getEntity()} " . '(' . implode(', ', array_keys($prepared)) . ')' . " VALUES " . '(' . implode(', ', array_values($prepared)) . ')';
    } else {
        //Monta a string de UPDATE
        $sql = "UPDATE {$this->getEntity()}";
        //Monta os pares: coluna=valor,...
        if ($prepared) {
            foreach($prepared as $column => $value) {
                if ($column !== 'id') {
                    $set[] = "{$column} = {$value}";
                }
            }
        }
        $sql .= " SET " . implode(', ', $set);
        $sql .= 'WHERE id = ' . (int) $this->date['id'];
    }

    //Obtém transação ativa
    if ($conn = Transaction::get()) {
        Transaction::log($sql);
        $result = $conn->exec($sql);
        return $result;
    } else {
        throw new Exception('Não há transação ativa');
    }
}

public function load($id)
{
    //Monta instrução de SELECT
    $sql = "SELECT * FROM {$this->getEntity()}";
    $sql .= ' WHERE id = ' . (int) $id;

    //Obtém transação ativa
    if ($conn = Transaction::get()) {
        //Cria a mensagem de log e executa a consulta
        Transaction::log($sql);
        $result = $conn->query($sql);

        //Se retornou algum dado
        if ($result) {
            //Retorna os dados em forma de objeto
            $object = $result->fetchObject(get_class($this));
        }
        return $object;
    } else {
        throw new Exception('Não há transação ativa!!');
    }
}

public function delete($id = NULL)
{
    //O ID é o parâmetro ou a propriedade ID
    $id = $id ? $id : $this->id;

    //Mostra a string de UPDATE
    $sql = "DELETE FROM {$this->getEntity()}";
    $sql .= ' WHERE id = ' . (int) $this->data['id'];

    //Obtém transação ativa
    if ($conn = Transaction::get()) {
        //Faz o log e executa o SQL
        Transaction::log($sql);
        $result = $conn->exec($sql);
        return $result; //Retorna o resultado
    } else {
        throw new Exception('Não há transação ativa!!');
    }
}

public static function find($id)
{
    $classname = get_called_class();
    $ar = new $classname;
    return $ar->load($id);
}

private function getLast()
{
    if($conn = Transaction::get()) {
        $sql = "SELECT max(id) FROM {$this->getEntity()}";

        //Cria log e executa instrução SQL
        Transaction::log($sql);
        $result = $conn->query($sql);

        //Retorna os dados do banco
        $row = $result->fetch();
        return $row[0];
    } else {
        throw new Exception('Não há transação ativa!!');
    }
}

public function prepare($data)
{
    $prepared = array();
    foreach ($data as $key => $value) {
        if(is_scalar($value)) {
            $prepared[$key] = $this->escape($value);
        }
    }
    return $prepared;
}

public function escape($value)
{
    if (is_string($value) and (!empty($value))) {
        //Adiciona \ em aspas
        $value = addslashes($value);
        return "'$value'";
    } else if (is_bool($value)) {
        return $value ? 'TRUE' : 'FALSE';
    } else if ($value !== '') {
        return $value;
    } else {
        return "NULL";
    }
}
}
?>

Product Class

<?php
class Produto extends Record {
const TABLENAME = 'produto';
}
?>

Expression Class

<?php
abstract class Expression 
{
//Operadores lógicos
const AND_OPERATOR = 'AND ';
const OR_OPERATOR = 'OR ';

//Metodo que retornara uma expressão em string
abstract public function dump();
}
?>

Criteria Class

<?php
class Criteria extends Expression 
{
private $expressions; //Armazena a lista de expressões
private $operators; //Armazena a lista de operadores
private $properties; //Propriedades do critério (ORDER BY, LIMIT, ...)

public function __construct()
{
    $this->expressions = array();
    $this->operators = array();
}

public function add(Expression $expression, $operator = self::AND_OPERATOR)
{ 
    //Na primeira vez, não precisamos concatenar
    if (empty($this->expressions)) {
        $operator = NULL;
    }

    //Agrega o resultado da expressão para a lista de expressões
    $this->expressions[] = $expression;
    $this->operators[] = $operator;
}

public function dump()
{
    //Concatena a lista de expressões
    if (is_array($this->expressions)) {
        if (count($this->expressions) > 0) {
            $result = '';
            foreach ($this->expressions as $i => $expression) {
                $operator = $this->operators[$i];
                //Concatena o operador com a respectiva expressão
                $result .= $operator . $expression->dump() . ' ';
            }
            $result = trim($result);
            return "({$result})";
        }
    }
}

public function setProperty($property, $value)
{
    if (isset($value)) {
        $this->properties[$property] = $value;
    } else {
        $this->properties[$property] = NULL;
    }
}

public function getProperty($property)
{
    if (isset($this->properties[$property])) {
        return $this->properties[$property];
    }
}
}
?>

Filter Class

<?php
class Filter extends Expression
{
public $variable;
public $operator;
public $value;

public function __construct($variable, $operator, $value) 
{
    $this->variable = $variable;
    $this->operator = $operator;

    //Transforma o valor de acordo com certas regras de tipo
    $this->value = $this->transform($value);
}

private function transform($value)
{
    //Caso seja um array
    if (is_array($value)) {
        foreach ($value as $x) {
            if (is_integer($x)) {
                $foo[] = $x;
            } else if (is_string($x)) {
                $foo[] = "'$x'";
            }
        }
        //Converte o array em string separada por ","
        $result = '(' . implode(',', $foo) . ')';
    } else if (is_string($value)) {
        $result = "'$value'";
    } else if (is_null($value)) {
        $result = 'NULL';
    } else if (is_bool($value)) {
        $result = $value ? 'TRUE' : 'FALSE';
    } else {
        $result = $value;
    }
    //Retorna o valor
    return $result;
}

public function dump()
{
    //Concatena a expressão
    return "{$this->variable} {$this->operator} {$this->value}";
}
}  
?>

Repository Class

<?php
class Repository 
{
private $activeRecord;

public function __construct($class)
{
    $this->activeRecord = $class;
}

public function load(Criteria $criteria = NULL)
{
    $sql = "SELECT * FROM " . constant($this->activeRecord.'::TABLENAME');
    if ($criteria) {
        $expression = $criteria->dump();
        if ($expression) {
            $sql .= ' WHERE ' . $expression;
        }
        $order = $criteria->getProperty('ORDER');
        $limit = $criteria->getProperty('LIMIT');
        $offset = $criteria->getProperty('OFFSET');
        if ($order) {
            $sql .= ' ORDER ' . $order;
        }
        if ($limit) {
            $sql .= ' LIMIT ' . $limit;
        }
        if ($offset) {
            $sql .= ' OFFSET ' . $offset;
        }
    }
    if ($conn = Transaction::get()) {
        Transaction::log($sql);
        $result = $conn->query($sql);
        $results = array();
        if ($result) {
            while ($row = $result->fetchObject($this->activeRecord)) {
                $results[] = $row;
            }
        }
        return $results;
    } else {
        throw new Exception('Não há transição ativa!!');
    }
}

public function delete(Criteria $criteria = NULL)
{
    $sql = "DELETE FROM " . constant($this->activeRecord.'::TABLENAME');
    if ($criteria) {
        $expression = $criteria->dump();
        if ($expression) {
            $sql .= ' WHERE ' . $expression;
        }
    }
    if ($conn = Transaction::get()) {
        Transaction::log($sql);
        $result = $conn->exec($sql);
        return $result;
    } else {
        throw new Exception('Não há transição ativa!!');
    }
}

public function count(Criteria $criteria = NULL)
{
    $sql = "SELECT count(*) FROM " . constant($this->activeRecord.'::TABLENAME');
    if ($criteria) {
        $expression = $criteria->dump();
        if ($expression) {
            $sql .= ' WHERE ' . $expression;
        }
    }
    if ($conn = Transaction::get()) {
        Transaction::log($sql);
        $result = $conn->query($sql);
        if ($result) {
            $row = $result->fetch();
        }
        return $row[0];
    } else {
        throw new Exception('Não há transição ativa!!!');
    }
}
}
?>

Finally the usage example

<?php
require_once 'classes/api/Transaction.php';
require_once 'classes/api/Connection.php';
require_once 'classes/api/Expression.php';
require_once 'classes/api/Criteria.php';
require_once 'classes/api/Repository.php';
require_once 'classes/api/Record.php';
require_once 'classes/api/Filter.php';
require_once 'classes/api/Logger.php';
require_once 'classes/api/LoggerTXT.php';
require_once 'classes/model/Produto.php';

try {
//Inicia a transação com a base de dados
Transaction::open('estoque');

//Define o arquivo de LOG
Transaction::setLogger(new LoggerTXT('tmp/log_collection_update.txt'));

//Define o criterio de seleção
$criteria = new Criteria;
$criteria->add(new Filter('preco_venda', '<=', 35));
$criteria->add(new Filter('origem', '=', 'N'));

//Cria o repositório
$repository = new Repository('Produto');
//Carrega os objetos conforme o critério
$produtos = $repository->load($criteria);
if ($produtos) {
    //Percorre todos os objetos
    foreach ($produtos as $produto) {
        $produto->preco_venda *= 1.3;
        $produto->store(); //Método da classe Record
    }
}
Transaction::close();

} catch (Exception $e) {
Transaction::rollback();
print $e->getMessage();
}
    
asked by anonymous 10.03.2017 / 04:00

1 answer

1
Active Record is a simpler standard that basically encapsulates a row from a database or other persistence mechanism for the consume application including business rules and the persistence mechanism. It is practically a buffer with some control over its update.

The Repository is a more complex infrastructure taking care of the entire data manipulation process. The access mechanism is separate from the business rule. It is said that he is ignorant of persistence, that is, persistence can occur in different ways as needed.

So the exact shape is different, but the goal is basically the same.

Today the repository pattern is much more used because of its ease of persistence exchange and testing. You have a question about it .

If one is used along with the other it seems to me that nonsense is being done.

If the question improves, I improve the answer

    
10.03.2017 / 05:17