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

Перекладено ШІ 1 Laravel News 02 червня, 2026

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

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

  • Зрозумієте структуру документів BSON та MongoDB.
  • Виконаєте основні CRUD-операції з документами.
  • Оберете між вбудовуванням та посиланнями.
  • Ефективно змоделюєте зв’язки один до одного, один до багатьох та багато до багатьох.

Для цього курсу необхідні базові знання 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,
],
...

Переваги

  • Один запит для отримання злагоджених даних
  • Одне атомарне оновлення для всього блоку

Недоліки

  • Великі документи можуть перевищити ліміт у 16 МБ (великі масиви або вбудовані документи можуть перевищити цей ліміт).
  • Можливе дублювання, коли одні й ті ж дані зустрічаються в інших місцях.

# Посилання

Посилання з’єднує документи з різних колекцій за допомогою їхніх 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');
}

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

Переваги

  • Уникає дублювання в різних колекціях
  • Зберігає документи малими, підтримує незалежні запити

Недоліки

  • Вимагає виконання кількох запитів або $lookup з’єднань для агрегацій

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

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

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

// 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": "..." }
  ]
...
}
  • Один до багатьох (обмежений): Вбудовування, якщо елементи обмежені та отримуються разом з батьком; приклад: відповіді, обмежені 50 на коментар.
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();

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

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

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

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

  • Колекції: продукти, теги, product_tags
  • Метадані зв'язку: хто додав тег, коли, та необов'язковий бал релевантності
// продукти
{ _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 на прикладі програми соціальної мережі. Бажаю успіхів у навчанні!

Популярні

Інше, що варто прочитати

17 Оновлено 01 червня, 2026

Налаштування Xdebug з Docker та PHP 8.4 всього за одну хвилину

Встановлення Xdebug може бути складним завданням, але в цій статті ми розкриємо, як швидко та просто налаштувати його за допомогою Docker на прикладі Laravel. Дочитайте до кінця, щоб дізнатися, як за кілька хвилин зробити Xdebug вашим надійним помічником у розробці

41 Оновлено 01 червня, 2026

Що нового в PHP 8.5

PHP 8.5 обіцяє безліч нових можливостей, таких як оператор Pipe, функції `array_first()` та `array_last()`, а також нове розширення URI. Чи готові ви дізнатися, як ці функції можуть спростити вашу розробку? Читайте далі, щоб дізнатися більше про ці захоплюючі нововведення

20 Оновлено 01 червня, 2026

Створення MCP-серверів на PHP

Модельний контекстний протокол (MCP) відкриває нові горизонти в інтеграції AI-додатків з PHP. Дізнайтеся, як легко створити сервер, що відповідає MCP, та які можливості відкриваються для вашого проєкту