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

0
Перекладено ШІ
Оригінал: 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 на прикладі програми соціальної мережі. Бажаю успіхів у навчанні!

Популярні

Logomark Logotype

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

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

Logomark Logotype

Що нового в PHP 8.5

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

Logomark Logotype

Оптимізація запитів до бази даних за допомогою скорочених методів Laravel

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