What is the difference between Activerecord and Repository?

Asked

Viewed 558 times

1

I’m reading a POO book in PHP that calls PHP Object Oriented Programming [Pablo Dall'Oglio] and I was a little confused with these two Patterns design, especially when it makes use of Repository using Activerecord. It is worth mentioning that in the examples there is no use of namespace, prepare (SQL Injection), because this chapter of the book 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();
}
?>

Class Criteria

<?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 example of use

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

    Improve your question by putting the definitions that the book brings and, if there are examples of codes, put them as well. Don’t forget to reference the book and author.

1 answer

2


The Active Record is a simpler design pattern that basically encapsulates a row of a database or other persistence mechanism for the consuming application including the business rules and the persistence mechanism. He’s practically a buffer with some control over your upgrade.

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 often said that he is ignorant of persistence, that is, persistence can occur in different ways as needed. Curious not to have an article on Wikipedia, makes you think.

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

Today the repository standard is much more used due to its ease of persistence switching and testing. It has a question about it.

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

  • I apologize for some mistakes, my first time asked here. So in Pablo Dall'Oglio’s PHP Object Oriented Programming book he does a sequence of steps. First it explains Activerecord and builds a Record (Layer Supertype) class that contains methods to load, delete, update, and insert objects into the database. After that he builds the Product class that extends the Record class. Then he explains the concept of Query Object, Composite and Repository, in which the Repository uses the Product class to manipulate its object.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.