PHP

MongoDB com PHP Já leu

7 min de leitura

MongoDB com PHP
MongoDB representa uma abordagem radicalmente diferente ao armazenamento de dados. Enquanto bancos relacionais organizam dados em tabelas com linhas e colunas de schema fixo, o MongoDB organiza dados em coleções de docum

MongoDB representa uma abordagem radicalmente diferente ao armazenamento de dados. Enquanto bancos relacionais organizam dados em tabelas com linhas e colunas de schema fixo, o MongoDB organiza dados em coleções de documentos no formato BSON (Binary JSON), sem schema obrigatório. Cada documento pode ter campos diferentes dos demais na mesma coleção.

Isso não significa que o MongoDB é "melhor" que o MySQL — é diferente. Ele brilha em cenários específicos: dados com estrutura variável ou que mudam com frequência, catálogos de produtos com atributos heterogêneos, logs e eventos, e aplicações onde a estrutura de dados evolui rápido. Entender quando usá-lo (e quando não usá-lo) é tão importante quanto saber usá-lo.


Instalação

MongoDB no Linux:

# Instalar MongoDB
sudo apt install mongodb-org

# Instalar a extensão PHP
sudo pecl install mongodb
echo "extension=mongodb.so" | sudo tee -a /etc/php/8.x/cli/php.ini

# Instalar a biblioteca via Composer
composer require mongodb/mongodb

Conceitos fundamentais

Antes de escrever código, alinhe o vocabulário:

Relacional MongoDB
Banco de dados Database
Tabela Collection
Linha Document
Coluna Field
JOIN $lookup (aggregation)
PRIMARY KEY _id (ObjectId automático)
INDEX Index (mesma ideia)
Schema fixo Schema flexível

Conexão e operações básicas

<?php
declare(strict_types=1);

require 'vendor/autoload.php';

use MongoDB\Client;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;

$client = new Client('mongodb://localhost:27017');

// Seleciona o banco e a coleção
// Se não existirem, são criados automaticamente na primeira operação
$db       = $client->loja;
$produtos = $db->produtos;

// INSERT — insertOne() retorna InsertOneResult
$resultado = $produtos->insertOne([
    'nome'       => 'Teclado Mecânico Keychron K2',
    'preco'      => 299.90,
    'estoque'    => 15,
    'ativo'      => true,
    'tags'       => ['periférico', 'teclado', 'mecânico'],
    'fabricante' => [           // documento embutido (embedded document)
        'nome'    => 'Keychron',
        'pais'    => 'China',
        'site'    => 'keychron.com',
    ],
    'variacoes' => [            // array de documentos embutidos
        ['cor' => 'preto',  'sku' => 'K2-BLK', 'estoque' => 8],
        ['cor' => 'cinza',  'sku' => 'K2-GRY', 'estoque' => 7],
    ],
    'criado_em'  => new UTCDateTime(),
]);

$id = $resultado->getInsertedId(); // ObjectId
echo "Inserido com ID: {$id}\n";

// INSERT em lote — insertMany()
$produtos->insertMany([
    ['nome' => 'Mouse Logitech MX Master 3', 'preco' => 499.90, 'tags' => ['periférico', 'mouse']],
    ['nome' => 'Monitor LG 27" 4K',          'preco' => 2899.00, 'tags' => ['monitor', 'display']],
]);

Consultas — find e findOne

<?php

// findOne() retorna um documento ou null
$produto = $produtos->findOne(['nome' => 'Teclado Mecânico Keychron K2']);
echo $produto['nome'] . ": R$ " . $produto['preco'] . "\n";

// Acessando documento embutido
echo "Fabricante: " . $produto['fabricante']['nome'] . "\n";

// find() retorna um Cursor — itere normalmente
$cursor = $produtos->find(['ativo' => true]);
foreach ($cursor as $p) {
    echo "- {$p['nome']}: R$ {$p['preco']}\n";
}

// Operadores de comparação
$caros = $produtos->find([
    'preco' => ['$gte' => 500],   // maior ou igual a 500
    'ativo' => true,
]);

// Múltiplas condições — AND implícito
$cursor = $produtos->find([
    'preco'   => ['$gte' => 100, '$lte' => 1000],
    'estoque' => ['$gt' => 0],
]);

// OR explícito
$cursor = $produtos->find([
    '$or' => [
        ['tags' => 'teclado'],
        ['tags' => 'mouse'],
    ]
]);

// Busca em array — o MongoDB verifica se o array contém o valor
$cursor = $produtos->find(['tags' => 'mecânico']);

// Busca em documento embutido com notação de ponto
$cursor = $produtos->find(['fabricante.nome' => 'Keychron']);

// Projeção — escolher quais campos retornar (1 = incluir, 0 = excluir)
$cursor = $produtos->find(
    filter:  ['ativo' => true],
    options: [
        'projection' => ['nome' => 1, 'preco' => 1, '_id' => 0],
        'sort'       => ['preco' => -1],   // -1 = decrescente
        'limit'      => 10,
        'skip'       => 0,
    ]
);

Atualização e deleção

<?php

// updateOne() — atualiza o primeiro documento que corresponde ao filtro
$produtos->updateOne(
    ['_id' => new ObjectId($id)],
    ['$set' => ['preco' => 279.90, 'atualizado_em' => new UTCDateTime()]]
);

// $inc — incrementa um valor
$produtos->updateOne(
    ['_id' => new ObjectId($id)],
    ['$inc' => ['estoque' => -1]]   // decrementa estoque em 1
);

// $push — adiciona ao array
$produtos->updateOne(
    ['_id' => new ObjectId($id)],
    ['$push' => ['tags' => 'bluetooth']]
);

// $pull — remove do array
$produtos->updateOne(
    ['_id' => new ObjectId($id)],
    ['$pull' => ['tags' => 'bluetooth']]
);

// updateMany() — atualiza todos que correspondem
$produtos->updateMany(
    ['estoque' => 0],
    ['$set' => ['ativo' => false]]
);

// findOneAndUpdate() — atualiza e retorna o documento
$atualizado = $produtos->findOneAndUpdate(
    ['sku' => 'K2-BLK'],
    ['$inc' => ['estoque' => -1]],
    ['returnDocument' => \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER]
);

// deleteOne() e deleteMany()
$produtos->deleteOne(['_id' => new ObjectId($id)]);
$produtos->deleteMany(['ativo' => false, 'estoque' => 0]);

Aggregation Pipeline — o poder do MongoDB

O framework de agregação do MongoDB é equivalente a GROUP BY, JOIN e funções de janela do SQL, mas com uma sintaxe de pipeline:

<?php

$pipeline = [
    // $match — filtra documentos (como WHERE)
    ['$match' => ['ativo' => true]],

    // $unwind — "explode" um array em documentos separados
    ['$unwind' => '$tags'],

    // $group — agrupa e acumula (como GROUP BY)
    ['$group' => [
        '_id'           => '$tags',
        'total_produtos'=> ['$sum' => 1],
        'preco_medio'   => ['$avg' => '$preco'],
        'preco_min'     => ['$min' => '$preco'],
        'preco_max'     => ['$max' => '$preco'],
    ]],

    // $sort — ordena o resultado
    ['$sort' => ['total_produtos' => -1]],

    // $limit
    ['$limit' => 10],
];

$resultado = $produtos->aggregate($pipeline);
foreach ($resultado as $item) {
    echo "Tag: {$item['_id']}, Produtos: {$item['total_produtos']}, "
       . "Preço médio: R$ " . number_format($item['preco_medio'], 2, ',', '.') . "\n";
}

Índices

<?php

// Índice simples
$produtos->createIndex(['nome' => 1]);

// Índice composto
$produtos->createIndex(['ativo' => 1, 'preco' => -1]);

// Índice de texto para busca full-text
$produtos->createIndex(['nome' => 'text', 'descricao' => 'text']);

// Busca textual
$cursor = $produtos->find(['$text' => ['$search' => 'teclado mecânico']]);

// Índice em campo de documento embutido
$produtos->createIndex(['fabricante.nome' => 1]);

// TTL index — documentos expiram automaticamente (ótimo para logs/sessões)
$db->sessoes->createIndex(
    ['criado_em' => 1],
    ['expireAfterSeconds' => 3600]  // expira após 1 hora
);

Quando usar MongoDB vs banco relacional

Use MongoDB quando... Use banco relacional quando...
Schema varia entre documentos Schema é bem definido e estável
Dados hierárquicos naturais Dados com muitos relacionamentos
Escrita e leitura em alta escala Consistência ACID é crítica
Prototipagem rápida Relatórios complexos e JOINs
Catálogos com atributos variáveis Transações financeiras

Exercício da semana

  1. Crie uma coleção artigos de um blog: título, conteúdo, autor (documento embutido com nome e email), tags (array), publicado_em, rascunho (boolean). Insira 5 artigos.

  2. Escreva queries para: buscar todos os artigos publicados de um autor específico, buscar artigos por tag, buscar artigos com mais de 500 palavras no conteúdo (usando $where com $expr).

  3. Crie um pipeline de agregação que retorne: por autor, o número de artigos publicados, a data do artigo mais recente e as 3 tags mais usadas.

  4. Implemente um TTL index na coleção tokens_recuperacao onde cada token expira em 2 horas (expireAfterSeconds: 7200).

  5. Desafio: implemente um ArticleRepository com interface idêntica ao ArticleRepositoryPDO que usa bancos relacionais. Ambos devem implementar ArticleRepositoryInterface. Escreva testes que rodem contra ambas as implementações.


Referências

  • Documentação oficial MongoDB: https://www.mongodb.com/docs/
  • Driver PHP oficial: https://www.mongodb.com/docs/drivers/php/
  • Biblioteca PHP MongoDB no Packagist: https://packagist.org/packages/mongodb/mongodb
  • MongoDB University (cursos gratuitos): https://learn.mongodb.com/
Comentários

Mais em PHP

Variáveis, Tipos de Dados e Operadores
Variáveis, Tipos de Dados e Operadores

Todo programa de computador, em sua ess&ecirc;ncia, faz tr&ecirc;s coisas: re...

Configurando o Ambiente de Desenvolvimento
Configurando o Ambiente de Desenvolvimento

Antes de escrever qualquer programa, voc&ecirc; precisa de um ambiente onde o...

Namespaces, Autoloading, PSR-4 e Composer
Namespaces, Autoloading, PSR-4 e Composer

At&eacute; agora todos os exemplos couberam em um &uacute;nico arquivo. Na pr...