Search by

heliomarpm / linq-php

heliomarpm

Manipulação fluida de arrays em PHP inspirada no LINQ: filtros, joins relacionais, agrupamentos e projeções com alta performance e zero dependências.

v2.0.1 2026-10-15 03:19 UTC

This package is auto-updated.

Last update: 2026-10-16 18:21:00 UTC


README

LinqPHP

CI Status CodeFactor Packagist Version Packagist Downloads

GitHub Sponsors PayPal Ko-fi Liberapay

LinqPHP é uma biblioteca leve, de alta performance e sem dependências externas para manipulação de coleções de dados (arrays) em PHP, inspirada na sintaxe do LINQ (Language Integrated Query).

Permite filtrar, agrupar, ordenar e unir arrays multidimensionais de forma fluida e elegante, sendo ideal para processamento de relatórios, formatação de dados para APIs e refatoração de sistemas legados.

Demo interativo

Benchmark

Benchmark

Requisitos

  • PHP 8.1 ou superior.

Instalação

Você pode instalar o pacote via Composer:

composer require heliomarpm/linq-php

Como usar

A classe LinqPHP utiliza uma interface fluida (method chaining). Todas as operações retornam a própria instância (exceto os métodos de saída toArray e toObject), permitindo encadear múltiplas transformações.

Inicialização

require 'vendor/autoload.php';

use HeliomarPM\LinqPHP\LinqPHP;

$data = [
  ['id' => 1, 'name' => 'John', 'age' => 25, 'status' => 'active', 'city' => 'London'],
  ['id' => 2, 'name' => 'Jane', 'age' => 30, 'status' => 'pending', 'city' => 'New York'],
  ['id' => 3, 'name' => 'John', 'age' => 35, 'status' => 'inactive', 'city' => 'Paris'],
];

$linq = LinqPHP::from($data);

where() - Filtragem de Dados

Aceita arrays de condições simples ou funções (closures) para filtros complexos.

// Filtro simples
$result = LinqPHP::from($data)
    ->where(['age', '>', 25])
    ->toArray();

// Múltiplos filtros e operadores (startswith, endswith, contains, in)
$result = LinqPHP::from($data)
    ->where([
        ['age', '>=', 25],
        ['status', 'in', ['active', 'pending']],
        ['name', 'startswith', 'J']
    ])
    ->toArray();

// Usando Closures (funções anônimas)
$result = LinqPHP::from($data)
    ->where(fn($item) => $item['id'] % 2 !== 0)
    ->toArray();

select() e distinct() - Projeção de Dados

Selecione apenas as colunas desejadas, renomeie campos utilizando aliases ou misture ambos com a projeção híbrida.

// 1. Seleção simples de colunas
$result = LinqPHP::from($data)
    ->select(['name', 'status'])
    ->toArray();

// 2. Seleção com Aliases (renomeação de colunas)
$result = LinqPHP::from($data)
    ->select([
        'id'   => 'codigo',
        'name' => 'nome_completo',
        'city' => 'cidade'
    ])
    ->toArray();

// 3. Projeção Híbrida (mantém alguns campos originais e renomeia outros)
$result = LinqPHP::from($data)
    ->select([
        'id',                      // mantém como 'id'
        'name' => 'nome_completo', // renomeia para 'nome_completo'
        'status'                   // mantém como 'status'
    ])
    ->toArray();

// 4. Retorna apenas os registros únicos
$result = LinqPHP::from($data)
    ->select(['name'])
    ->distinct()
    ->toArray();

Tip

Performance e Modo Estrito (strict):

  • Zero Performance Penalty: Em seleções tradicionais indexadas numericamente (['name', 'status']), o LinqPHP utiliza internamente array_is_list() nativo do PHP 8.1+ e executa o caminho direto em C com array_intersect_key, garantindo zero degradação de performance.
  • Preservação de Ordem: As colunas no array de saída seguem rigorosamente a ordem em que foram declaradas no select().
  • Modo Estrito: Por padrão (strict: false), campos inexistentes na fonte são preenchidos com null. Ao usar select(['campo1', 'campo2'], strict: true), uma InvalidArgumentException será lançada caso alguma chave não exista no registro.

join() - União Relacional

Faça uniões relacionais entre arrays multidimensionais como se estivesse em um banco de dados relacional. Suporta os 4 tipos de join via enum nativo JoinType:

  • JoinType::INNER: Retorna apenas os registros com correspondência em ambas as coleções.
  • JoinType::LEFT: Retorna todos os registros da esquerda e os correspondentes da direita (ou null).
  • JoinType::RIGHT: Retorna todos os registros da direita e os correspondentes da esquerda (ou null).
  • JoinType::FULL: Retorna todos os registros de ambos os lados, preenchendo lacunas com null.
use HeliomarPM\LinqPHP\JoinType;

$users = [
    ['id' => 1, 'name' => 'John', 'course_id' => 10],
    ['id' => 2, 'name' => 'Jane', 'course_id' => 20],
];

$courses = [
    ['id_course' => 10, 'course_name' => 'PHP 8'],
    ['id_course' => 20, 'course_name' => 'Clean Code'],
];

// Inner Join simples
$result = LinqPHP::from($users)
    ->join($courses, JoinType::INNER, ['course_id' => 'id_course'])
    ->toArray();

// Joins Múltiplos encadeados (Join Triplo)
$result = LinqPHP::from($vendas)
    ->join($produtos, JoinType::INNER, ['produto_id' => 'id_produto'])
    ->join($clientes, JoinType::INNER, ['cliente_id' => 'id_cliente'])
    ->toArray();

unionAll() - Concatenação de Coleções

Consolida múltiplos arrays em uma única coleção, preenchendo automaticamente com null colunas ausentes em registros heterogêneos.

$produtosRegulares = [
    ['id' => 1, 'nome' => 'Filamento PLA', 'preco' => 90.0]
];

$produtosOutlet = [
    ['id' => 2, 'nome' => 'Resina 3D', 'desconto' => 0.2] // coluna 'preco' ausente
];

$result = LinqPHP::from($produtosRegulares)
    ->unionAll($produtosOutlet)
    ->toArray();

groupBy() - Agrupamento e Agregação

Agrupe dados e realize cálculos automáticos (sum, max, min, count, avg).

$vendas = [
    ['vendedor' => 'João', 'valor' => 100],
    ['vendedor' => 'Maria', 'valor' => 200],
    ['vendedor' => 'João', 'valor' => 150],
];

// Agrupa por vendedor e soma os valores
$result = LinqPHP::from($vendas)
    ->groupBy(['vendedor'], ['sum' => ['valor']])
    ->toArray();

orderBy() e orderByKey() - Ordenação

Ordene por uma chave simples (orderByKey) ou por múltiplos critérios com direções independentes (orderBy).

// Ordena por uma chave simples
$result = LinqPHP::from($data)
    ->orderByKey('age', 'desc')
    ->toArray();

// Ordenação múltipla com direções independentes
$result = LinqPHP::from($data)
    ->orderBy(['name' => 'asc', 'age' => 'desc'])
    ->toArray();

Important

Ciclo de Vida em Memória e Encadeamento com select() e Aliases:

O LinqPHP opera por mutação in-memory a cada chamada da cadeia fluente. Quando você utiliza o select() renomeando colunas para aliases, os nomes originais são substituídos nos dados internos:

  1. Boa Prática — Ordenar antes da projeção: Ordene seus dados pela chave física de origem antes de renomeá-los para a saída:

    $result = LinqPHP::from($vendas)
        ->where(['status', '=', 'concluido'])
        ->orderByKey('id_venda', 'asc') // ✅ Ordena pela chave original
        ->select(['id_venda' => 'Código Venda', 'valor' => 'Total'])
        ->toArray();
  2. Ordenando após a projeção: Se chamar o orderBy após o select(), você deve referenciar o alias gerado, pois o nome antigo não existe mais no registro:

    $result = LinqPHP::from($vendas)
        ->select(['id_venda' => 'Código Venda', 'valor' => 'Total'])
        ->orderByKey('Código Venda', 'asc') // ✅ Ordena pelo alias criado no select
        ->toArray();

Caso tente ordenar por uma chave inexistente (como tentar usar 'id_venda' após ele ter sido transformado em 'Código Venda'), o LinqPHP disparará uma InvalidArgumentException("This field does not exist for sorting.").

Extração de Resultados (toArray e toObject)

No final da cadeia fluente, invoque um método de saída terminal para extrair os dados processados.

$linq = LinqPHP::from($data)->where(['age', '>', 20]);

// Retorna o array tradicional processado
$array = $linq->toArray();

// Retorna um stdClass contendo os dados e métricas de telemetria embutida
$obj = $linq->toObject();
echo $obj->count;       // Quantidade de registros resultantes
echo $obj->elapsedTime; // Tempo total gasto na operação em segundos
echo $obj->memoryUsed;  // Consumo de memória formatado (ex: 18.00 MB)
print_r($obj->data);    // Coleção de dados processados

🧪 Laboratório Interativo (Demo)

O projeto inclui uma interface web completa para experimentação e simulação de queries LINQ em tempo real, com dados de e-commerce e editor interativo de código:

composer demo
# Acesse http://localhost:8000 no seu navegador

Na demo interativa você pode simular cenários avançados como Join Triplo, Union All, agregações com GroupBy e testar expressões customizadas com telemetria instantânea de memória e tempo de execução.

🛠️ Desenvolvimento e Testes

O código-fonte principal encontra-se na pasta src/. O projeto utiliza ferramentas de qualidade estrita (PHPStan e Laravel Pint) e suíte completa de testes unitários com PHPUnit 13.

Comandos disponíveis via Composer:

# Executa a suíte de testes unitários
composer test

# Executa testes com saída detalhada
composer test:v

# Executa benchmarks de performance (até 200.000 registros)
composer bench

# Executa análise estática de tipos (PHPStan nível 5)
composer lint

# Valida conformidade com PSR-12 (Laravel Pint)
composer format:test

# Corrige automaticamente estilo de código
composer format:fix

# Executa verificação completa de QA (Testes + Benchmarks + PHPStan)
composer qa

🤝 Contribuições e suporte

Existem várias formas de contribuir:

  • ⭐ Marcar o repositório com uma estrela
  • 🐞 Relatar bugs
  • 💡 Sugerir funcionalidades ou novas stacks
  • 🧾 Melhorar a documentação
  • 📢 Compartilhar o projeto

Para propor uma melhoria ou contribuição, veja o Guia de Contribuição.
Se precisar de ajuda ou encontrou um problema, consulte o Suporte ou abra uma Issue. Leia também o Código de Conduta.

Obrigado a todos que já contribuíram:

Contribuidores

Criado com contrib.nn

💵 Apoie o Projeto

Se você gosta do projeto, considere fazer uma doação ao desenvolvedor via GitHub Sponsors, Ko-fi, PayPal ou Liberapay — a escolha é sua. 😉

GitHub Sponsors PayPal Ko-fi Liberapay

📝 Licença

Distribuído sob a licença MIT © Heliomar P. Marques.
Sinta-se livre para utilizar, customizar e estender estas habilidades nos seus ambientes de desenvolvimento. 🔝