Một trong những điều thú vị về ORM (Object-Relational Mapping) là tính năng thực thi mô hình Active Record, giống như Eloquent. Nó rất dễ sử dụng và trực quan. Với mô hình Active Record, bạn chỉ đơn giản là tương tác và lưu trữ một đối tượng. Thay vì phải quan tâm đến logic về phía cơ sở dữ liệu, việc quản lý logic liên quan đến kinh doanh trở nên dễ dàng hơn rất nhiều.
Thực thể chỉ là đối tượng PHP đơn thuần
Điểm khác biệt lớn nhất giữa Doctrine 2 và Eloquent là cách xử lý thực thể. Trong Doctrine 2, thực thể chỉ đơn thuần là đối tượng PHP, không kế thừa bất kỳ logic nào từ ORM. Trong khi đó, Eloquent thừa kế toàn bộ logic bền vững của ORM. Ví dụ:
class User extends Eloquent {
}
$user = User::find(123);
$user->name = 'Philip Brown';
$user->save();
Trong ví dụ trên, khi tương tác với thực thể User, bạn có sẵn tất cả các phương thức của Eloquent. Bạn có thể lưu thực thể bất kỳ ở bất kỳ điểm nào trong mã của bạn bằng cách sử dụng phương thức save()
.
Trong Doctrine, thực thể của bạn chỉ đơn giản là các đối tượng PHP, không kế thừa bất kỳ logic nào khi bạn mở rộng một lớp ORM ở đầu tập tin:
<?php
use DoctrineORMMapping AS ORM;
/**
* @ORMEntity
* @ORMTable(name="users")
*/
class User {
/**
* @ORMId
* @ORMGeneratedValue
* @ORMColumn(type="integer")
*/
private $id;
/**
* @ORMColumn(type="string")
*/
private $name;
}
Như bạn thấy, lớp User chỉ đơn giản là một lớp PHP. Vì vậy, nó rất nhẹ nhàng vì không có bất kỳ khối lượng công việc nào liên quan đến thực thể ORM.
Ghi chú trong thực thể
Khác với Eloquent, Doctrine 2 sử dụng các ghi chú để mô tả các thực thể. Điều này có vẻ phức tạp ở lần đầu tiên, nhưng thực tế chỉ đơn giản là cung cấp thông tin về dữ liệu của thực thể của Doctrine. Trong ví dụ trên, tôi đã liệt kê chi tiết về cách thực thể được lưu trữ như một bảng và kiểu dữ liệu của các cột được sử dụng. Trong Laravel, chúng ta sử dụng các tệp migration để tạo schema cơ sở dữ liệu. Trong Doctrine 2, cơ sở dữ liệu được tạo tự động từ các dữ liệu meta trong các ghi chú (annotations) của thực thể.
Entity Manager là ông chủ
Thực thể của Doctrine 2 chỉ đơn giản là các đối tượng PHP. Điều đó có nghĩa là chúng ta không thể gọi phương thức `save()` để lưu dữ liệu vào cơ sở dữ liệu. Doctrine 2 có một lớp dịch vụ được gọi là Entity Manager. Entity Manager chịu trách nhiệm duy nhất về việc xử lý tất cả các logic về bền vững (persistence). Vì vậy, khi bạn muốn lưu dữ liệu vào cơ sở dữ liệu cho một đối tượng nào đó, bạn phải sử dụng Entity Manager với các phương thức `persist()` và `flush()`:
$user = new User;
$user->setName('Philip Brown');
EntityManager::persist($user);
EntityManager::flush();
Doctrine 2 cập nhật dữ liệu thông qua các giao dịch, chứ không phải những câu lệnh truy vấn đơn lẻ. Điều đó được gọi là Unit of Work (đơn vị công việc).
Unit of Work là gì?
Một khái niệm quan trọng cần hiểu khi sử dụng Doctrine 2 là Unit of Work. Chú ý rằng khi chúng ta muốn tương tác với cơ sở dữ liệu, chúng ta cần sử dụng `persist($user)` và `flush()`. Vậy tại sao lại cần gọi `persist()` trước và sau đó mới gọi `flush()` mà không gọi `save()` luôn? Doctrine 2 sử dụng chiến lược ghi chú ghi lại sau khi ghi vào cơ sở dữ liệu. Điều này có nghĩa là Doctrine 2 sẽ trì hoãn việc tương tác với cơ sở dữ liệu cho đến khi `flush()` được gọi.
Khi bạn gọi persist($user)
trên Entity Manager, Doctrine sẽ theo dõi điều này và bất kỳ đối tượng nào khác đang tồn tại. Khi phương thức flush()
được gọi, Doctrine 2 sẽ tự động tạo ra câu lệnh SQL để cập nhật dữ liệu lên cơ sở dữ liệu thông qua một giao dịch (transaction).
The Identity Map
Một tính năng tốt của Doctrine 2 là Identity Map. Đoạn mã sau minh họa tính năng này:
$user = EntityManager::find('CribbbEntitiesUser', 1);
$user->setUsername('philipbrown');
$user = EntityManager::find('CribbbEntitiesUser', 1);
echo $user->getUsername();
Trong đoạn mã trên, chúng ta truy vấn một người dùng trong cơ sở dữ liệu và gán giá trị cho thuộc tính username
. Tiếp theo, chúng ta lại truy vấn người dùng đó ra khỏi cơ sở dữ liệu và lấy giá trị thuộc tính username
. Thông thường, điều này sẽ dẫn đến nhiều câu lệnh SQL với cơ sở dữ liệu, vì đối tượng đã được truy vấn và sau đó đã bị loại bỏ. Doctrine 2 quản lý các tình huống này thông qua Identity Map. Doctrine 2 làm quản lý đối tượng và tạo ra duy nhất một phiên bản của nó trong suốt thời gian yêu cầu PHP, có nghĩa là bạn có thể truy vấn cùng một đối tượng bằng nhiều cách khác nhau mà không cần truy vấn cơ sở dữ liệu.
Lấy thực thể từ cơ sở dữ liệu
Khi bạn muốn lấy một thực thể từ cơ sở dữ liệu sử dụng mô hình Active Record, bạn chỉ cần gọi phương thức `find()` từ mô hình:
$user = User::find(1);
Bạn cũng có thể sử dụng Entity Manager thay vì mô hình:
$user = EntityManager::find('CribbbEntitiesUser', 1);
Repository thật sự là một repository
Bạn có thể sử dụng Repository Pattern để trả về một đối tượng PHP tiêu chuẩn thay vì một instance của mô hình Eloquent:
$user = EntityManager::getRepository('CribbbEntitiesUser')->find($id);
Phương thức getRepository()
sẽ trả về một repository cho phép bạn truy vấn cơ sở dữ liệu. Đối tượng repository này có một số phương thức hữu ích để truy vấn cơ sở dữ liệu:
$users = EntityManager::getRepository('CribbbEntitiesUser')->findBy(['location' => 'UK']);
$users = EntityManager::getRepository('CribbbEntitiesUser')->findOneBy(['username' => 'philipbrown']);
Mỗi phương thức này đều chấp nhận một mảng các điều kiện.
Repository tùy chỉnh
Bên cạnh việc sử dụng các phương thức có sẵn của repository, bạn cũng có thể tùy chỉnh hoặc thêm các phương thức mà bạn tự định nghĩa. Ví dụ:
class UserRepository extends EntityRepository {
public function getAllSuperUsers() {
// logic của bạn
}
}
// Sử dụng
$user = UserRepository->getAllSuperUsers();
Doctrine Query Language
Trong trường hợp muốn thực hiện các truy vấn phức tạp, bạn có thể sử dụng trực tiếp truy vấn SQL với Doctrine 2:
$query = EntityManager::createQuery("select u from CribbbEntitiesUser u where u.karma >= 10 and u.karma <= 100");
$users = $query->getResult();