В прошлой статье мы говорили о построителе запросов к базе данных, Query Builder, и узнали о реализации кода Fluent Api, предоставляемого Query Builder, для построения и генерации операторов SQL. В этой статье мы изучим еще одну важную часть базы данных Laravel: Eloquent Model.
Eloquent Model абстрагирует атрибуты и ассоциации таблицы данных в каждый класс Model, поэтому класс Model является абстракцией таблицы данных, а объект Model — абстракцией одной записи в таблице. Основанный на упомянутом выше Query Builder, Eloquent Model предоставляет Eloquent Builder для взаимодействия с базой данных, а также обеспечивает ассоциацию моделей для элегантного решения отношений между несколькими таблицами данных.
Загрузить Eloquent Builder
Eloquent Builder реализован на базе упомянутого выше Query Builder, рассмотрим его на конкретных примерах.
DB::table('user')->where('name', 'James')->where('age', 27)->get();
После перезаписи его для использования модели он становится
User::where('name', 'James')->where('age', 27)->get();
В файле класса Model мы не нашлиwhere
,find
,first
Эти часто используемые методы запросов, мы все знаем, что PHP будет запускать магические методы при вызове несуществующего метода класса.__callStatic
, вызов несуществующего метода экземпляра вызовет__call
, Нетрудно догадаться, что вышеуказанные методы вызываются динамически через эти два магических метода.Давайте посмотрим на исходный код.
namespace Illuminate\Database\Eloquent;
abstract class Model implements ...
{
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
// new Eloquent Builder
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}
public function newQueryWithoutScopes()
{
$builder = $this->newEloquentBuilder($this->newBaseQueryBuilder());
//设置builder的Model实例,这样在构建和执行query时就能使用model中的信息了
return $builder->setModel($this)
->with($this->with)
->withCount($this->withCount);
}
//创建数据库连接的QueryBuilder
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
}
Запрос модели
Из приведенных выше кодов мы можем видеть, что методы, связанные с запросами, вызываемые в модели, в конечном итоге будут проходить__call
Вместо этого вызовите эти методы экземпляра Eloquent Builder. Взаимодействие между Eloquent Builder и базовой базой данных достигается за счет использования Query Builder. Мы видим, что при создании экземпляра Eloquent Builder объект QueryBuilder соединения с базой данных передается методу его конструктора. , давайте взглянем на исходный код Eloquent Builder.
namespace Illuminate\Database\Eloquent;
class Builder
{
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$query = $this->model->newQueryWithoutScopes();
$column($query);
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
public function get($columns = ['*'])
{
$builder = $this->applyScopes();
//如果获取到了model还会load要预加载的模型关联,避免运行n+1次查询
if (count($models = $builder->getModels($columns)) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $builder->getModel()->newCollection($models);
}
public function getModels($columns = ['*'])
{
return $this->model->hydrate(
$this->query->get($columns)->all()
)->all();
}
//将查询出来的结果转换成Model对象组成的Collection
public function hydrate(array $items)
{
//新建一个model实例
$instance = $this->newModelInstance();
return $instance->newCollection(array_map(function ($item) use ($instance) {
return $instance->newFromBuilder($item);
}, $items));
}
//first 方法就是应用limit 1,get返回的集合后用Arr::first()从集合中取出model对象
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
}
//newModelInstance newFromBuilder 定义在\Illuminate\Database\EloquentModel类文件里
public function newFromBuilder($attributes = [], $connection = null)
{
//新建实例,并且把它的exists属性设成true, save时会根据这个属性判断是insert还是update
$model = $this->newInstance([], true);
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
Метод where Eloquent Builder в коде напрямую передает запрос в Query Builder после получения запроса на вызовwhere
метод, а затем метод get также сначала пропускается через Query Builderget
Метод выполняет запрос для получения массива результатов, а затем передаетnewFromBuilder
Этот метод преобразует результирующий массив в коллекцию объектов Model и другой, более часто используемый метод.first
Также вget
Реализован на основе метода применить ограничение 1 к запросу, а затем начать сget
Коллекция, возвращаемая методом, используетсяArr::first()
Выньте объект модели и верните его вызывающей стороне.
Обновление модели
Прочитав реализацию запроса модели, давайте взглянем на реализацию обновления, создания и удаления или продолжим расширение примера запроса в начале:
$user = User::where('name', 'James')->where('age', 27)->first();
Теперь через запрос модели мы получаем экземпляр модели пользователя, теперь мы хотим изменить возраст этого пользователя на 28 лет:
$user->age = 28;
$user->save();
Мы знаем, что атрибуты модели соответствуют полям таблицы данных.Когда приведенный выше метод get возвращает коллекцию экземпляров модели, мы видели, что поля и значения полей записей данных назначаются атрибутам $ атрибут экземпляра модели, и экземпляр модели доступен и установлен.Свойства, соответствующие этим полям, передаются через__get
а также__set
Магические методы получают и устанавливают значения этих свойств динамически.
abstract class Model implements ...
{
public function __get($key)
{
return $this->getAttribute($key);
}
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
public function getAttribute($key)
{
if (! $key) {
return;
}
//如果attributes数组的index里有$key或者$key对应一个属性访问器`'get' . $key . 'Attribute'` 则从这里取出$key对应的值
//否则就尝试去获取模型关联的值
if (array_key_exists($key, $this->attributes) ||
$this->hasGetMutator($key)) {
return $this->getAttributeValue($key);
}
if (method_exists(self::class, $key)) {
return;
}
//获取模型关联的值
return $this->getRelationValue($key);
}
public function getAttributeValue($key)
{
$value = $this->getAttributeFromArray($key);
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
if ($this->hasCast($key)) {
return $this->castAttribute($key, $value);
}
if (in_array($key, $this->getDates()) &&
! is_null($value)) {
return $this->asDateTime($value);
}
return $value;
}
protected function getAttributeFromArray($key)
{
if (isset($this->attributes[$key])) {
return $this->attributes[$key];
}
}
public function setAttribute($key, $value)
{
//如果$key存在属性修改器则去调用$key地属性修改器`'set' . $key . 'Attribute'` 比如`setNameAttribute`
if ($this->hasSetMutator($key)) {
$method = 'set'.Str::studly($key).'Attribute';
return $this->{$method}($value);
}
elseif ($value && $this->isDateAttribute($key)) {
$value = $this->fromDateTime($value);
}
if ($this->isJsonCastable($key) && ! is_null($value)) {
$value = $this->castAttributeAsJson($key, $value);
}
if (Str::contains($key, '->')) {
return $this->fillJsonAttribute($key, $value);
}
$this->attributes[$key] = $value;
return $this;
}
}
Если Модель определяет модификатор свойства, модификатор будет выполняться при установке свойства В нашем примере модификатор свойства не используется. при исполнении$user->age = 28
, свойство $attributes в экземпляре модели пользователя станет
protected $attributes = [
...
'age' => 28,
...
]
После установки нового значения атрибута выполнение метода сохранения модели Eloquent обновит соответствующую запись в базе данных.Давайте посмотрим на логику в методе сохранения:
abstract class Model implements ...
{
public function save(array $options = [])
{
$query = $this->newQueryWithoutScopes();
if ($this->fireModelEvent('saving') === false) {
return false;
}
//查询出来的Model实例的exists属性都是true
if ($this->exists) {
$saved = $this->isDirty() ?
$this->performUpdate($query) : true;
}
else {
$saved = $this->performInsert($query);
if (! $this->getConnectionName() &&
$connection = $query->getConnection()) {
$this->setConnection($connection->getName());
}
}
if ($saved) {
$this->finishSave($options);
}
return $saved;
}
//判断对字段是否有更改
public function isDirty($attributes = null)
{
return $this->hasChanges(
$this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
);
}
//数据表字段会保存在$attributes和$original两个属性里,update前通过比对两个数组里各字段的值找出被更改的字段
public function getDirty()
{
$dirty = [];
foreach ($this->getAttributes() as $key => $value) {
if (! $this->originalIsEquivalent($key, $value)) {
$dirty[$key] = $value;
}
}
return $dirty;
}
protected function performUpdate(Builder $query)
{
if ($this->fireModelEvent('updating') === false) {
return false;
}
if ($this->usesTimestamps()) {
$this->updateTimestamps();
}
$dirty = $this->getDirty();
if (count($dirty) > 0) {
$this->setKeysForSaveQuery($query)->update($dirty);
$this->fireModelEvent('updated', false);
$this->syncChanges();
}
return true;
}
//为查询设置where primary key = xxx
protected function setKeysForSaveQuery(Builder $query)
{
$query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery());
return $query;
}
}
При сохранении он будет основан на экземпляре модели.exists
атрибут, чтобы определить, выполнять ли обновление или вставку, пример, который мы используем здесь, это обновление, и программа выполняет сравнение во время обновления$attributes
а также$original
Значение поля каждого поля в двух атрибутах массива находит измененное поле (при получении объекта Model поле таблицы данных будет сохранено в$attributes
а также$original
два атрибута), если нет измененных полей, то на этом обновление заканчивается, а если есть изменения, то продолжаем выполнениеperformUpdate
метод,performUpdate
Метод будет выполнять метод обновления Eloquent Builder, а Eloquent Builder зависит от экземпляра Query Builder подключения к базе данных для последнего выполненного обновления базы данных.
МодельWrite
Я просто сказал, что когда модель получена через Eloquent Model (вnewFromBuilder
метод) преобразует экземпляр моделиexists
Для этого свойства установлено значение true, тогда значение этого свойства для вновь созданного экземпляра модели равно false.save
метод будет выполненperformInsert
метод
protected function performInsert(Builder $query)
{
if ($this->fireModelEvent('creating') === false) {
return false;
}
//设置created_at和updated_at属性
if ($this->usesTimestamps()) {
$this->updateTimestamps();
}
$attributes = $this->attributes;
//如果表的主键自增insert数据并把新记录的id设置到属性里
if ($this->getIncrementing()) {
$this->insertAndSetId($query, $attributes);
}
//否则直接简单的insert
else {
if (empty($attributes)) {
return true;
}
$query->insert($attributes);
}
// 把exists设置成true, 下次在save就会去执行update了
$this->exists = true;
$this->wasRecentlyCreated = true;
//触发created事件
$this->fireModelEvent('created', false);
return true;
}
performInsert
Если таблица автоматически инкрементируется по первичному ключу, то после вставки значение идентификатора первичного ключа новой записи будет установлено в атрибут экземпляра Модели, а также это поможет нам сохранить поле времени иexists
Атрибуты.
Удаление модели
Операция удаления модели Eloquent такая же.Метод удаления в построителе запросов подключения к базе данных выполняется через Eloquent Builder для удаления записи базы данных:
//Eloquent Model
public function delete()
{
if (is_null($this->getKeyName())) {
throw new Exception('No primary key defined on model.');
}
if (! $this->exists) {
return;
}
if ($this->fireModelEvent('deleting') === false) {
return false;
}
$this->touchOwners();
$this->performDeleteOnModel();
$this->fireModelEvent('deleted', false);
return true;
}
protected function performDeleteOnModel()
{
$this->setKeysForSaveQuery($this->newQueryWithoutScopes())->delete();
$this->exists = false;
}
//Eloquent Builder
public function delete()
{
if (isset($this->onDelete)) {
return call_user_func($this->onDelete, $this);
}
return $this->toBase()->delete();
}
//Query Builder
public function delete($id = null)
{
if (! is_null($id)) {
$this->where($this->from.'.id', '=', $id);
}
return $this->connection->delete(
$this->grammar->compileDelete($this), $this->cleanBindings(
$this->grammar->prepareBindingsForDelete($this->bindings)
)
);
}
Мы уже упоминали детали реализации Query Builder в предыдущей статье и не будем повторять их здесь.Если вам интересно, как Query Builder выполняет операции SQL, вы можете вернуться и прочитать предыдущую статью.
Суммировать
В этой статье мы подробно рассмотрели, как Eloquent Model выполняет CRUD.Как упоминалось в начале, Eloquent Model выполняет операции с базой данных через Eloquent Builder, а Eloquent Builder дополнительно инкапсулируется на основе Query Builder.Eloquent Builder поместит эти CRUD в вызов метода передается соответствующему методу в построителе запросов для завершения операции, поэтому методы, которые можно использовать в построителе запросов, также можно использовать в модели Eloquent.
Помимо абстракции таблиц данных и базового CRUD, еще одной важной особенностью модели является ассоциация моделей, которая помогает нам элегантно решать отношения между таблицами данных. Мы подробно рассмотрим реализацию части ассоциации модели в следующей статье.
Эта статья вошла в серию статейИзучение основного кода Laravel, добро пожаловать в гости и читать.