Структура документів у MongoDB та моделювання даних

Перекладено ШІ
Оригінал: Laravel News
Оновлено: 04 грудня, 2025
Згадуючи MongoDB та Laravel, ви знайдете безліч можливостей для роботи з даними. У нашій статті ви дізнаєтеся про основи моделювання даних у MongoDB, включаючи різницю між вбудовуванням та посиланням — ключові концепції для оптимізації вашої роботи з базами даних! Чи готові ви покращити свої навички програмування та зрозуміти, як ефективно структурувати свої документи? Читайте далі!

# Чого ви навчитеся

Для цього курсу необхідні базові знання Laravel.

# Що таке BSON?

Binary JSON (BSON) — це бінарне кодування документів, подібних до JSON, в MongoDB. Воно містить чітку інформацію про типи та довжину, що забезпечує швидке проходження та ефективне зберігання порівняно з простим JSON.

BSON є форматом, що використовується на диску та в мережі, тоді як у коді зазвичай працюють зі структурами, схожими на JSON.

BSON – це матеріал успіху MongoDB.

Приклад BSON

# Типи даних BSON

BSON розширює JSON додатковими типами даних, такими як дата, ObjectId та бінарні дані.

| Тип даних | Опис | Приклад |
|-----------|-------------|---------|
| String | UTF-8 рядок | `"John Doe"` |
| Integer | Ціле число 32 або 64 біта | `28` |
| Double | Число з плаваючою комою 64 біти | `3.14` |
| Boolean | true/false | `true` |
| Date | Дата у форматі UTC | `ISODate("2025-01-15")` |
| ObjectId | Унікальний 12-байтовий ідентифікатор | `ObjectId("507f...")` |
| Array | Список значень | `["tag1", "tag2"]` |
| Object | Вбудований документ | `{"city": "NY"}` |
| Null | Порожнє значення | `null` |

# Анатомія документа

Ось приклад документа BSON з наведеними вище полями даних.

{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "John Doe",
  "email": "john@example.com",
  "age": 28,
  "created_at": ISODate("2025-01-15T10:30:00Z"),
  "profile": {
    "bio": "Software developer",
    "avatar": "avatar.jpg"
  },
  "tags": ["developer", "laravel", "mongodb"]
}

# Що таке колекція?

Колекція — це група документів. На відміну від SQL-таблиць, документи в одній колекції не повинні мати ідентичну схему.

Порівняння термінів MongoDB та SQL:

MongoDB SQL
Колекція Таблиця
Документ Рядок
Поле Стовпець

# Основи моделювання даних

MongoDB пропонує два основних підходи до зв’язків між даними: вбудовування та посилання.

# Вбудовування

Вбудовування зберігає пов'язані дані в одному документі.

Основне правило: Дані, що отримуються разом, слід зберігати разом.

Приклад — інформація профілю, вбудована в документ користувача:

{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "username": "john.doe123",
  "email": "john@example.com",
  "profile": {
    "full_name": "John Doe",
    "bio": "Software developer and tech enthusiast",
    "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=123e4567",
    "website": "https://example.com",
    "location": "San Francisco, USA"
  }
}
// app/Models/User.php
// У цьому прикладі використовуються фабрики бази даних Laravel разом із Faker для генерації тестових даних.
...
'profile' => [
    'full_name' => $firstName . ' ' . $lastName,
    'bio' => fake()->sentence(10),
    'avatar' => 'https://api.dicebear.com/7.x/avataaars/svg?seed=' . fake()->uuid(),
    'website' => fake()->url(),
    'location' => fake()->city() . ', ' . fake()->country(),
],
...
 
// app/Models/UserController.php
...public function updateProfile(Request $request)
{
    $validated = $request->validate([
        'full_name' => 'sometimes|string|max:100',
        'bio' => 'sometimes|string|max:500',
        'website' => 'sometimes|url',
        'location' => 'sometimes|string|max:100',
    ]);
 
    $user = auth()->user();
    $user->update([
        'profile' => array_merge($user->profile, $validated),
    ]);
}

Ще один приклад вбудовування — статистика користувача:

{
 "_id": ObjectId("507f1f77bcf86cd799439012"),
 "username": "jane.smith456",
 "stats": {
   "posts_count": 42,
   "followers_count": 1250,
   "following_count": 87
 }
}
// app/Models/User.php
...
'stats' => [
    'posts_count' => 0,
    'followers_count' => 0,
    'following_count' => 0,
],
...

Переваги

Недоліки

# Посилання

Посилання з’єднує документи з різних колекцій за допомогою їхніх ID. Це відповідає іноземним ключам у реляційних базах даних.

Приклад зв’язку користувачів у контакті та доступу

Приклад: пости посилаються на користувачів. Пости можуть постійно зростати, тому посилання зберігає документи користувачів малими та незалежними.

{
  "_id": ObjectId("507f1f77bcf86cd799439013"),
  "user_id": ObjectId("507f1f77bcf86cd799439011"),
  "content": "My awesome post!",
  "stats": {
    "likes_count": 342,
    "comments_count": 28
  }
}
// app/Models/Post.php
// Опис моделі
protected $fillable = [
    'user_id',  // Посилання на користувача
    'content',
    'media',
    'stats',
    'tags',
];
 
// Відношення
public function user()
{
    return $this->belongsTo(User::class, 'user_id');
}
// UserController.php
...public function show(User $user)
{
    $user->load(['posts' => function($q) {
        $q->orderBy('created_at', 'desc')->limit(10);
    }]);
 
    $isFollowing = in_array(
        $user->_id,
        auth()->user()->following_ids ?? []
    );
 
    return view('users.show', compact('user', 'isFollowing'));
}

Коментарі посилаються як на пост, так і на користувача:

{
  "_id": ObjectId("507f1f77bcf86cd799439014"),
  "post_id": ObjectId("507f1f77bcf86cd799439013"),
  "user_id": ObjectId("507f1f77bcf86cd799439012"),
  "parent_id": null,
  "content": "Great post! I totally agree.",
  "likes_count": 15,
  "created_at": ISODate("2024-11-03T10:30:00Z"),
  "updated_at": ISODate("2024-11-03T10:30:00Z")
}
// app/Models/Post.php
// Опис моделі
protected $fillable = [
    'post_id',    // Посилання на пост
    'user_id',    // Посилання на користувача
    'parent_id',  // Посилання на батьківський коментар
    'content',
    'likes_count',
    'created_at',
    'updated_at',
];
 
// Відносини
public function post()
{
    return $this->belongsTo(Post::class, 'post_id');
}
 
public function user()
{
    return $this->belongsTo(User::class, 'user_id');
}

Загалом, посилання часто використовуються у відносинах багато до багатьох.

Переваги

Недоліки

# Взаємозв'язки моделей

# Один до одного

Якщо дані завжди отримуються разом, використовуйте вбудовування.

// app/Models/Post.php
'stats' => [
    'likes_count' => fake()->numberBetween(0, 1000),
    'comments_count' => fake()->numberBetween(0, 50),
    'shares_count' => fake()->numberBetween(0, 20),
],
 
// Приклад документа
{
  "_id": ObjectId("507f1f77bcf86cd799439013"),
  "content": "My awesome post!",
  "stats": {
    "likes_count": 342,
    "comments_count": 28,
    "shares_count": 5
  }
}

# Один до багатьох

Варіанти кардинальності один до багатьох

{
  "media": [
    { "type": "image", "url": "..." },
    { "type": "image", "url": "..." },
    { "type": "video", "url": "..." }
  ]
...
}
Comment::where('parent_id', $commentId)->get()
// User.php
public function comments()
{
    return $this->hasMany(Comment::class, 'user_id');
}
 
// Comment.php
public function user()
{
    return $this->belongsTo(User::class, 'user_id');
}
{
  "_id": ObjectId("507f1f77bcf86cd799439015"),
  "user_id": ObjectId("507f1f77bcf86cd799439012"),
  "likeable_type": "App\\Models\\Post",
  "likeable_id": ObjectId("507f1f77bcf86cd799439013")
}
// Запит на лайки: Пагіновані лайки на пост
Like::where('likeable_type', Post::class)
    ->where('likeable_id', $postId)
    ->paginate(20);

# Багато до багатьох

Це зазвичай вирішується через посилання; приклад: взаємини з підписками.

class User extends Model
{
    protected $connection = 'mongodb';
 
    protected $fillable = ['name', 'email', 'following_ids'];
 
    protected $casts = [
        'following_ids' => 'array'
    ];
}
 
// Підписатися на користувача
$user->push('following_ids', $targetUserId);
 
// Відписатися
$user->pull('following_ids', $targetUserId);
 
// Отримати користувачів, на яких підписано
$following = User::whereIn('_id', $user->following_ids)->get();

Зверніть увагу: багато до багатьох можна також змоделювати за допомогою окремої колекції при необхідності зберігати метадані, такі як часові позначки або статуси.

Додатковий приклад (поза кейсом):

Продукти ↔ Теги з колекцією-з’єднанням

Коли вам потрібні метадані про саму взаємозв'язок, створіть окрему колекцію-з’єднання.

// продукти
{ _id: ObjectId("6560..."), name: "UltraSoft Hoodie", sku: "HD-001" }
 
// теги
{ _id: ObjectId("6561..."), name: "winter" }
{ _id: ObjectId("6562..."), name: "sale" }
 
// product_tags (колекція-з’єднання)
{
  _id: ObjectId("6570..."),
  product_id: ObjectId("6560..."),
  tag_id: ObjectId("6561..."),
  added_by: ObjectId("user123..."),
  added_at: ISODate("2025-10-15T09:12:00Z"),
  relevance: 0.87
}
 
// app/Models/ProductTag.php
class ProductTag extends Model
{
    protected $connection = 'mongodb';
    protected $fillable = ['product_id', 'tag_id', 'added_by', 'added_at', 'relevance'];
    protected $casts = [
        'added_at' => 'datetime',
        'relevance' => 'float',
    ];
}
 
// Прикріплення тега з метаданими
ProductTag::create([
    'product_id' => $productId,
    'tag_id'     => $tagId,
    'added_by'   => auth()->id(),
    'added_at'   => now(),
    'relevance'  => 0.87,
]);

Цей шаблон узагальнюється на інші сфери, як-от:

# Вбудовування проти посилання

Фактор Вбудовування Посилання
Можливості зростання Невелика кількість Нескінченні
Шаблон доступу Разом Незалежно
Розмір документа Залишатися малим Ставати великим
Частота оновлення Рідко Часто

# Висновок

Вибір між вбудовуванням і посиланнями залежить від кардинальності, зростання, шаблонів доступу та частоти оновлення.

Почніть з аналізу того, як ваша програма зчитує дані. Якщо дані завжди отримуються разом і мають обмеження, використовуйте вбудовування. Якщо зростання необмежене або елементи запитуються незалежно, використовуйте посилання.

Моделюйте відповідно до вашого робочого навантаження, перевіряйте на реальних запитах і вдосконалюйте процес, як тільки ваша програма розвивається.

# Додаткові ресурси

Проект соціальної мережі, що використовує наведені вище концепції (весь вихідний код для цього уроку)

# Відео-демонстрація:


Сподіваюсь, цей урок допоможе вам зрозуміти основи MongoDB на прикладі програми соціальної мережі. Бажаю успіхів у навчанні!