PHP Design Patterns: Active Record

Antonio Silva - Sep 25 '23 - - Dev Community

What is Active Record?

Active Record is a design pattern that features a class that has both persistence methods and business methods.From the point of view of Object Orientation, this class ends up mixing many responsibilities, thus breaking the principle of single responsibility.

Class Active Record

So one way to solve this “problem” is to create a parent class that implements the persistence methods in a generic way, finally having a separation between the business methods and the persistence methods, this design pattern is known as Layer Supertype.

Example

Next we will have an example of how Active Record works.

Step 1 - Directory System:

📦Active_Record
 ┣ 📂class
 ┃ ┗ 📜Product.php
 ┣ 📂config
 ┃ ┗ 📜config.ini
 ┣ 📂database
 ┃ ┗ 📜product.db
 ┗ 📜index.php
Enter fullscreen mode Exit fullscreen mode

Step 2 - Database Config File:

host = 
name = database/product.db
user = 
pass = 
type = sqlite
Enter fullscreen mode Exit fullscreen mode

Step 3 - Database:

CREATE TABLE product(
  id INTEGER PRIMARY KEY NOT NULL,
  description TEXT,
  stock FLOAT,
  cost_price FLOAT,
  sale_price FLOAT,
  bar_code TEXT,
  date_register DATE,
  origin CHAR(1)
);
Enter fullscreen mode Exit fullscreen mode

Step 5 - Product Class:

class Product
{
    private static $conn;
    private mixed $data;
}
Enter fullscreen mode Exit fullscreen mode

The conn property is static to maintain the value, so we don't need to open the same connection more than once.
The data variable is defined as mixed, as it can take on several types.

  • setConnection method
public static function setConnection(PDO $conn)
{  
    self::$conn = $conn;
}
Enter fullscreen mode Exit fullscreen mode

setConnection receives a PDO connection as a parameter and stores it in the static attribute conn.

  • __get and __set methods
public function __get(String $prop): mixed
{  
    return $this->data[$prop];
}

public function __set(String $prop, mixed $value)
{
    $this->data[prop] = $value;
}
Enter fullscreen mode Exit fullscreen mode

__get is a magic method which is invoked when reading the value of a non-existent or inaccessible property, we receive the name of the property and pass it to the data attribute as the key.
__set is a magic method which is invoked when writing a value to a non-existing or inaccessible property, we receive the name and value of the property respectively

  • find method
public static function find(int $id): Product
{
    // Store the SQL in a variable
    $sql = "SELECT * FROM product WHERE id = :id";
    // Print the SQL
    print "{$sql} <br>";
    // perform the prepare method
    $result = self::$conn->prepare($sql);
    // Binds a parameter to the specified variable name
    // PDO::PARAM_INT Represents the SQL INTEGER data type
    $result->bindParam(':id', $id, PDO::PARAM_INT);
    // execute sql statement
    $result->execute();
    // Fetches the next row and returns it as an object
    // __CLASS__ receives the name of the created class
    return $result->fetchObject(__CLASS__);
}
Enter fullscreen mode Exit fullscreen mode

The find method receives an id as a parameter and returns an object of the Product class as a value

  • all method
public static function all(String $filter = null): array
{
    $sql = "SELECT * FROM product";

    // checks if there is a filter
    if($filter)
    {
      // concatenate the filter to sql statement
      $sql .= " WHERE $filter";
    }

    print "{$sql} <br>";
    $result = self::$conn->prepare($sql);
    $result->execute();
    // Sets the remaining rows of a result set
    // PDO::FETCH_CLASS Returns a new instance of the requested class
    return $result->fetchALL(PDO::FETCH_CLASS, __CLASS__);

}
Enter fullscreen mode Exit fullscreen mode

the all method can receive a filter and return a array of objects as a value

  • delete method
public function delete(int $id, String $operator = '=')
{  
    $sql = "DELETE FROM product WHERE id $operator :id";
    print "{$sql} <br>";
    $result = self::$conn->prepare($sql);             
    $result->bindParam(':id', $id, PDO::PARAM_INT);

    if($result->execute())
    {
      // returns an array indexed by column name as returned in your result set
      return $result->fetch(PDO::FETCH_ASSOC);
    }

}
Enter fullscreen mode Exit fullscreen mode
  • save method
public function save()
{

    if(empty($this->data['id']))
    {
      $sql = "INSERT INTO product (description, stock, cost_price, sale_price, bar_code, date_register, origin)
              VALUES (:description, :stock, :cost_price, :sale_price, :bar_code, :date_register, :origin)";
    }
    else
    {
      $sql = "UPDATE product SET description = :description, stock = : stock, cost_price = :cost_price, sale_price = :sale_price,
              bar_code = :bar_code, date_register = :date_register, origin = :origin WHERE id = :id";
    }

    print $sql;
    $result = self::$conn->prepare($sql);
    return $result->execute($this->data);

}
Enter fullscreen mode Exit fullscreen mode

The save method performs both the save and update.

Testing

<?php

  require_once 'class/Produto.php';

  try
  {
    $ini = parse_ini_file('config/config.ini');
    $name = $ini['name'];

    $conn = new PDO('sqlite:' . $name);
    $conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);

    Product::setConnection($conn);

    $product = new Product;

  }
  catch(Exception $e)
  {
    print $e->getMessage();
  }
Enter fullscreen mode Exit fullscreen mode
  • INSERT
  $product->description = 'Juice';
  $product->stock = 8;
  $product->cost_price = 12;
  $product->sale_price = 18;
  $product->bar_code = '123123123';
  $product->origin = 'S';
  $product->date_register = date('Y-m-d');
  $product->save();
Enter fullscreen mode Exit fullscreen mode
  • UPDATE
  $update = Product::find(1);
  $update->description = "Grape Juice";
  $update->save();
Enter fullscreen mode Exit fullscreen mode
  • ALL
  foreach(Product::all() as $product)
  {
    print $product->description . ' ';
    print $product->cost_price . ' ';
    print $product->sale_price . "<br>";
  }
Enter fullscreen mode Exit fullscreen mode

If you have any questions, just leave a comment below.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .