---
title: 'Laravel, простенький backup/restore моделей'
url: 'https://graker.ru/news/2017/08/15/laravel-simple-model-backup-restore'
language: ru
type: post
author: 'Роман Грачев'
published: '2017-08-15T11:25:00+03:00'
updated: '2017-08-15T11:36:44+03:00'
categories:
  - Laravel
  - Программирование
---

# Laravel, простенький backup/restore моделей

На днях понадобилось немного изменить структуру БД в проекте, так чтобы уже созданные модели не пропали. То есть скинуть все данные моделей куда-нибудь, потом сделать изменения, потом данные восстановить. Стал, в общем, писать небольшой бэкапчик, без особых изысков, просто чтоб работал.

В Ларавеле с моделями работать удобно и получить их данные в целом довольно просто.

```
$user = User::find(1);
$data = $user->toArray();
```

И все, у нас в массиве есть данные модели, можно их склеить в строку, например, и скинуть в файл. Но есть немного нюансов.

Например, в массиве не будут содержаться поля, которые перечислены в `$hidden`. Для модели пользователя это поле пароля. А мы бы хотели, конечно, сохранить хэши паролей в бэкапе. Чтобы скрытые поля появились, нужно их специально показать:

```
$user = User::find(1);
$user->makeVisible(['password']);
$data = $user->toArray();
```

Кроме того, у модели могут быть отношения, перечисленные в свойстве `$with`. Эти отношения всегда будут загружаться вместе с моделью и тоже попадут в `$data` (как вложенные массивы). Но отношения мы будем бэкапить отдельно, поэтому хотелось бы их исключить. Для этого с помощью фасада `Schema` получим список тех полей, которые действительно хранятся в таблице моделей.

```
$table = $user->getTable();
$fields = Schema::getColumnListing($table);
```

Теперь можно просто убрать лишнее из `$data`:

```
$user = User::find(1);
$user->makeVisible(['password']);
$data = $user->toArray();
$table = $user->getTable();
$fields = Schema::getColumnListing($table);
$filtered = [];
// filter out relationships
foreach ($fields as $key => $field) {
    if (!in_array($key, $fields)) {
        // relationship
        continue;
    } else {
        $filtered[] = $field;
    }
}
```

То, что осталось в массиве `$filtered` — можно склеить в строку и вывести куда-то. Осталось только забэкапить все модели и сделать код независимым от класса модели (чтобы не повторяться):

```
function exportAllModels($model_class, $make_visible = []) {
  $fields = getFieldNames($model_class);
  foreach ($model_class::all() as $model) {
    $data = exportModel($model, $make_visible);
    // данные в массиве $data можно склеить и куда-нибудь вывести
  }
}

function getFieldNames($model_class) {
    $table = (new $model_class)->getTable();
    return Schema::getColumnListing($table);
}

function exportModel($model, $fields, $make_visible = []) {
    if (!empty($make_visible) {
        $model->makeVisible($make_visible);
    }
    $filtered = [];
    // filter out relationships
    foreach ($fields as $key => $field) {
        if (!in_array($key, $fields)) {
            // relationship
            continue;
        } else {
            $filtered[] = $field;
        }
    }
    return $filtered;
}
```

В `$make_visible` будем передавать массив скрытых полей, которые должны попасть в экспорт (например, `['password']`). Если данных очень много, можно еще `::all()` заменить на `::chunk()`. Пример вызова:

```
exportAllModels(App\User::class, ['password']);
```

Теперь про обратную операцию. Мы вытащили данные из файла, разобрали, и у нас снова есть массив `$data` с полями модели. Создать модель можно вот так:

```
User::create($data);
```

Но так не удастся сохранить, например, `id` моделей, потому что Laravel будет учитывать только поля, перечисленные в свойстве `$fillable` класса модели. А сохранить id хочется — например, чтобы не заморачиваться с зависимыми от модели отношениями и восстановить их с теми же id родителей. Для этого можно временно отключить `$fillable`, вот так:

```
User::unguard();
User::create($data);
User::reguard();
```

Есть и еще одна проблема. Допустим, у модели `User` есть отношение: одному пользователю положено иметь одну модель `UserSettings` с какими-нибудь настройками. И мы решили (почему-то), что надо сразу с созданием пользователя по соответствующему событию автоматически создавать и эту модель. Тогда при восстановлении пользователей из бэкапов через `User::create()`, зависимые модели будут созданы автоматически, и для них уже `UserSettings::create()` не будет работать, т.к. модели-то уже есть, причем с неправильными id.

Можно еще раз почистить таблицы этих зависимых моделей, прежде чем восстанавливаться из бэкапа. Ну или можно восстанавливать модели не через `::create()` а с помощью простой вставки в базу данных через фасад `DB`. Тогда независимый от класса модели код восстановления будет выглядеть вот так:

```
function createModel($model_class, $data) {
    $table = (new $model_class)->getTable();
    $fields = getFieldNames($model_class);
    $model = array_combine($fields, $data);
    DB::table($table)->insert($model);
}
```

В принципе, все довольно просто. В рассмотренном примере не хватает только бэкапа промежуточных таблиц для отношений «много-много», но их можно пустить через обычные `DB::select()` и `DB::insert()`.
