Для цього курсу необхідні базові знання Laravel.
Binary JSON (BSON) — це бінарне кодування документів, подібних до JSON, в MongoDB. Воно містить чітку інформацію про типи та довжину, що забезпечує швидке проходження та ефективне зберігання порівняно з простим JSON.
BSON є форматом, що використовується на диску та в мережі, тоді як у коді зазвичай працюють зі структурами, схожими на JSON.
BSON – це матеріал успіху MongoDB.
Приклад 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 на прикладі програми соціальної мережі. Бажаю успіхів у навчанні!