W naszym projekcie MVC zrobiliśmy model z prawdziwego zdarzenia, działający jak w Laravelu, z modelami statycznymi oraz metodą save i kreatywnym wykorzystaniem magicznych metod get i set. Teraz poznamy wzorzec Repository.
Nie jest on aż tak przydatny, ale warto znać. Najpierw jednak połączenie z bazą danych:
$host = '127.0.0.1';
$db = 'mydb12345';
$user = 'root';
$pass = 'pass';
$port = "3306";
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset;port=$port";
$pdo = new PDO($dsn, $user, $pass);
$q = $pdo->query('SELECT COUNT(*) FROM PEOPLE');
print_r($q->fetch());
//Array ( [COUNT(*)] => 14 [0] => 14 )
Ok, teraz przypominam, jak wyglądał nasz PersonModel:
class PersonModel extends Model {
protected static $tablename = 'people';
protected static $keys = ['name', 'age'];
}
Klasa bazowa Model robiła za nas wszystko, toż to prawie ORM ta klasa. Jedyne co ustawialiśmy to nazwy tabeli i kolumn.
Cóż, tutaj nie będzie aż tak różowo:
class PersonEntity {
public function __construct(public $id, public $name, public $age)
{
}
}
Teraz Repository:
class PersonRepository {
public function __construct(private PDO $pdo) {}
private function arrayToModel(array $entry): PersonEntity {
return new PersonEntity(
$entry['ID'],
$entry['name'],
$entry['age']
);
}
}
Metoda arrayToModel robi za nas wszystko. Dalej już tylko nasza umiejętność korzystania z PDO:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function fetchById(int $id): ?PersonEntity {
$stmt = $this->pdo->prepare('SELECT * FROM `people` WHERE `ID` = :id');
$stmt->bindValue(':id', $id);
$stmt->execute();
$entry = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($entry))
return $this->arrayToModel($entry);
return null;
}
$repo = new PersonRepository($pdo);
$jim = $repo->fetchById(14);
print_r($jim);
//PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
Teraz fetch:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function fetch(): array {
$stmt = $this->pdo->prepare('SELECT *
FROM `people` ');
$stmt->execute();
$models = [];
$entries = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($entries AS $entry) {
$models[] = $this->arrayToModel($entry);
}
return $models;
}
}
$repo = new PersonRepository($pdo);
$jim = $repo->fetchById(14);
print_r($jim);
//PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
$models = $repo->fetch();
print_r($models[13]);
//PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
Jako że przepis na paginację znamy, paginate również nie powinno nas dziwić:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function paginate(int $page, int $perPage = 5): array {
$page = max(1, $page);
$stmt = $this->pdo->prepare('SELECT *
FROM `people`
ORDER BY `ID` ASC
LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', ($page - 1) * $perPage, PDO::PARAM_INT);
$stmt->execute();
$models = [];
$entries = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($entries AS $entry) {
$models[] = $this->arrayToModel($entry);
}
return $models;
}
}
//(...)
$paginated_models = $repo->paginate(2, 3);
print_r($paginated_models);
// Array (
// [0] => PersonEntity Object ( [id] => 4 [name] => Yolo [age] => 22 )
// [1] => PersonEntity Object ( [id] => 5 [name] => Abc [age] => 22 )
// [2] => PersonEntity Object ( [id] => 6 [name] => Jane [age] => 22 )
// )
Count to łatwizna:
//(...)
public function count(): int {
$stmt = $this->pdo->prepare('SELECT COUNT(*) AS `count` FROM `people`');
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['count'];
}
}
//(...)
echo $repo->count();
// 14
Teraz update:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function update(int $id, array $properties): PersonEntity {
$stmt = $this->pdo->prepare('UPDATE `people`
SET
`name` = :name,
`age` = :age
WHERE `id` = :id');
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $properties['name']);
$stmt->bindValue(':age', $properties['age']);
$stmt->execute();
return $this->fetchById($id);
}
}
//(...)
$yolo_man = $repo->update(4, ['name' => 'YoloMan', 'age' => 35]);
print_r($yolo_man);
//PersonEntity Object ( [id] => 4 [name] => YoloMan [age] => 35 )
Jedyny problem, jaki tu widzę (dostosowałem wzorzec do naszych potrzeb bazując na przykładzie z internetu) to paginacja:
$repo = new PersonRepository($pdo);
$paginated_models = $repo->paginate(20, 3);
print_r($paginated_models);
// Array ()
Znaczy – można przekazać stronę większą niż ostatnia strona. Naprawmy to:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function count(): int {
$stmt = $this->pdo->prepare('SELECT COUNT(*) AS `count` FROM `people`');
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['count'];
}
public function lastPage($perPage): int {
$count = $this->count();
$lastPage = ceil($count / $perPage);
return $lastPage;
}
}
//(...)
$repo = new PersonRepository($pdo);
echo $repo->lastPage(3);
//5
print_r($repo->paginate(5, 3));
// Array (
// [0] => PersonEntity Object ( [id] => 13 [name] => Tim [age] => 22 )
// [1] => PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
// )
print_r($repo->paginate(6, 3));
// Array ()
Teraz możemy zastosować zabezpieczenie w paginate:
class PersonRepository {
public function __construct(private PDO $pdo) {}
//(...)
public function paginate(int $page, int $perPage = 5): array {
$page = max(1, $page);
$lastPage = $this->lastPage($perPage);
$page = min($page, $lastPage);
$stmt = $this->pdo->prepare('SELECT *
FROM `people`
ORDER BY `ID` ASC
LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', ($page - 1) * $perPage, PDO::PARAM_INT);
$stmt->execute();
$models = [];
$entries = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($entries AS $entry) {
$models[] = $this->arrayToModel($entry);
}
return $models;
}
}
//(...)
$repo = new PersonRepository($pdo);
echo $repo->lastPage(3);
//5
print_r($repo->paginate(5, 3));
// Array (
// [0] => PersonEntity Object ( [id] => 13 [name] => Tim [age] => 22 )
// [1] => PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
// )
print_r($repo->paginate(6, 3));
// Array (
// [0] => PersonEntity Object ( [id] => 13 [name] => Tim [age] => 22 )
// [1] => PersonEntity Object ( [id] => 14 [name] => Jim [age] => 29 )
// )