PHPackages                             flowstore/lookup - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. flowstore/lookup

ActiveLibrary

flowstore/lookup
================

Agnostic lookup layer for external channels (ecommerce/ERP/marketplace) mapping to domain DTOs.

v1.3.0(7mo ago)013MITPHPPHP ^8.1|^8.2|^8.3|^8.4CI passing

Since Sep 23Pushed 7mo agoCompare

[ Source](https://github.com/lucasgio/lookup-http-client)[ Packagist](https://packagist.org/packages/flowstore/lookup)[ Docs](https://github.com/flowstore/lookup)[ RSS](/packages/flowstore-lookup/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)Dependencies (9)Versions (10)Used By (0)

Lookup (Paquete Laravel)
------------------------

[](#lookup-paquete-laravel)

**Qué es**: Capa agnóstica para consultar APIs externas por "canal" (ecommerce/ERP/marketplace) y convertir respuestas en DTOs de dominio unificados. Se usa para iniciar integraciones (probar conexión, traer datos base, etc.). No persiste ni configura integraciones.

### Instalación

[](#instalación)

- Requerimientos: PHP ^8.1+, Laravel ^9|^10|^11|^12.
- Instala vía Composer:

```
composer require flowstore/lookup
```

- Publica la configuración:

```
php artisan vendor:publish --provider="Flowstore\\Lookup\\LookupServiceProvider" --tag=config
```

### Configuración

[](#configuración)

Archivo `config/lookup.php`:

- **providers**: mapa `channel_key => ClaseProvider`.
- **mappers**: mapa `channel_key => [entity => ClaseMapper]`.
- **conventions**: namespaces por defecto para resolución por convención.
- **http**: timeouts y retries por defecto para el cliente HTTP.

### Contratos principales

[](#contratos-principales)

- `LookupProviderInterface`: `resources()`, `testConnection(IntegrationContext)`, `lookup(IntegrationContext, entity, params)`.
- `EntityMapperInterface`: `entity()`, `map(payload, IntegrationContext): TDomain`.
- `IntegrationContext`: DTO con `channelKey` y `credentials`.
- `IntegrationContextResolver`: contrato que la app host implementa para resolver un contexto desde un `integrationId`.

### Resolución y orquestación

[](#resolución-y-orquestación)

- `LookupProviderResolver`: resuelve Provider por `channel_key` usando `config('lookup.providers')` o la convención `App\\Lookup\\Providers\\{Canal}LookupProvider`.
- `EntityMapperResolver`: resuelve Mapper por `channel_key + entity` usando `config('lookup.mappers')` o la convención `App\\Lookup\\Mappers\\{Canal}\\{Entidad}Mapper`.
- `PerformLookupAction`: invoca `provider->lookup(...)` y mapea con `mapper->map(...)`.
- `LookupService`: resuelve contexto (si se usa `IntegrationContextResolver`) y devuelve el DTO de dominio.

### Resolución de contexto (configurable)

[](#resolución-de-contexto-configurable)

El paquete es agnóstico de tu modelo. Define cómo construir `IntegrationContext` desde el request:

```
// config/lookup.php
'context' => [
	// Nombre del parámetro de entrada con el id
	'id_param' => 'integration_id',

	// (opcional) Clase que implementa IntegrationContextResolver
	// 'resolver' => App\Resolvers\MyContextResolver::class,
	'resolver' => null,

	// (opcional) Resolver genérico basado en Eloquent
	'eloquent' => [
		// 'model' => App\\Models\\IntegrationTenant::class,
		// 'channel_column' => 'channel_key',
		// 'credentials_column' => 'credentials',
	],
],
```

Si usas el resolver genérico Eloquent con `IntegrationTenant`:

```
// config/lookup.php
'context' => [
	'id_param' => 'integration_id',
	'resolver' => null,
	'eloquent' => [
		'model' => App\Models\IntegrationTenant::class,
		'channel_column' => 'channel_key',
		'credentials_column' => 'credentials',
	],
],
```

Modelo sugerido:

```
// app/Models/IntegrationTenant.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class IntegrationTenant extends Model
{
	protected $casts = [
		'credentials' => 'array',
	];
}
```

El controlador del paquete leerá el id desde `config('lookup.context.id_param')` (por defecto `integration_id`) y resolverá el contexto usando el `resolver` configurado o el resolver Eloquent si se define `context.eloquent.model`.

### HTTP (LookupController)

[](#http-lookupcontroller)

- La ruta se habilita por defecto al instalar el paquete.
- Endpoint por defecto: `POST /tenant-integrations/lookup` (middleware `api`).
- Puedes deshabilitar u overrides en `config/lookup.php`:

```
'routes' => [
	'enabled' => true,          // pon false para deshabilitar
	'path' => '/tenant-integrations/lookup',
	'prefix' => null,
	'middleware' => ['api'],
],
```

Ejemplo de request (POST JSON):

```
{
  "integration_id": 123,
  "entity": "seller",
  "params": { "limit": 1 }
}
```

Ejemplo de respuesta:

```
{
  "data": { /* DTO de dominio mapeado */ }
}
```

Nota: Debes implementar y bindear `IntegrationContextResolver` para traducir `integration_id` a `IntegrationContext`.

### Test de conexión (TestConnectionController)

[](#test-de-conexión-testconnectioncontroller)

- Endpoint por defecto: `POST /tenant-integrations/test-connection`
- Body (JSON):

```
{ "channel_key": "shopify", "integration_id": 123 }
```

- Respuesta:

```
{ "success": true }
```

- Requiere un `IntegrationContextResolver` configurado (propio con `context.resolver` o genérico con `context.eloquent.model`).
- Ajustes en `config/lookup.php`:

```
'routes' => [
	'enabled' => true,
	'path' => '/tenant-integrations/lookup',
	'test_path' => '/tenant-integrations/test-connection',
	'prefix' => null,          // p.ej. 'api'
	'middleware' => ['api'],
],
'context' => [
	'id_param' => 'integration_id', // p.ej. 'tenant_id'
],
```

### Puntos de extensión

[](#puntos-de-extensión)

- **Agregar un canal**: crear `App\\Lookup\\Providers\\{Canal}LookupProvider` y registrarlo en `config/lookup.php` (o seguir la convención).
- **Agregar una entidad**: crear `App\\Lookup\\Mappers\\{Canal}\\{Entidad}Mapper` y registrarlo (o usar la convención).
- `testConnection(...)` permite "ping" de credenciales antes de usar `lookup`.

### Comando de scaffolding

[](#comando-de-scaffolding)

```
php artisan make:lookup {channel} {entity} {--provider}
```

### Contribuir

[](#contribuir)

- Issues y PRs en `https://github.com/flowstore/lookup`.
- Ejecuta tests y static analysis antes de commitear:

```
composer test
composer stan
```

Crea stubs para Provider y/o Mapper en tu app (`app/Lookup/...`).

### Buenas prácticas

[](#buenas-prácticas)

- Evita dependencia dura a modelos: usa `IntegrationContext` o implementa `IntegrationContextResolver` en tu app.
- Usa el Http Client de Laravel con timeouts/retries; el paquete provee un `AbstractLookupProvider` de ayuda.
- Mantén el retorno como DTO de dominio consistente y documentado por entidad.

### Uso en la app host

[](#uso-en-la-app-host)

```
$context = new IntegrationContext(channelKey: 'shopify', credentials: ['token' => '...']);
$dto = app(\Flowstore\Lookup\Services\LookupService::class)
    ->lookup($context, 'seller', ['limit' => 1]);
```

O vía HTTP con el `LookupController` opcional.

### IntegrationContext (qué es y cómo personalizar)

[](#integrationcontext-qué-es-y-cómo-personalizar)

`IntegrationContext` es un DTO inmutable que describe el contexto de una integración:

- `channelKey` (string): identifica el canal (p.ej. `shopify`, `mercadoLibre`).
- `credentials` (array&lt;string, mixed&gt;): credenciales y datos necesarios para llamar al canal (token, apiKey, sellerId, etc.).

Se utiliza en `LookupProviderInterface::testConnection(...)` y `lookup(...)`, y también en `EntityMapperInterface::map(...)` para proveer contexto al mapeo.

Personalización:

- Cambiar el parámetro de ID de entrada (nombre del campo en el request):

```
// config/lookup.php
'context' => [
	'id_param' => 'tenant_id', // en vez de integration_id
	'resolver' => null,
	'eloquent' => [ /* ... opcional ... */ ],
],
```

En este caso, el controlador leerá `tenant_id` en el body.

- Proveer tu propio resolver (sin Eloquent genérico):

```
// app/Resolvers/MyContextResolver.php
namespace App\Resolvers;

use App\Models\IntegrationTenant;
use Flowstore\Lookup\Contracts\IntegrationContextResolver;
use Flowstore\Lookup\DTO\IntegrationContext;

final class MyContextResolver implements IntegrationContextResolver
{
	public function resolve($integrationId): IntegrationContext
	{
		$tenant = IntegrationTenant::findOrFail($integrationId);
		return new IntegrationContext(
			channelKey: (string) $tenant->channel_key,
			credentials: (array) $tenant->credentials,
		);
	}
}
```

Regístralo por configuración (no hace falta bind manual):

```
// config/lookup.php
'context' => [
	'id_param' => 'integration_id',
	'resolver' => App\Resolvers\MyContextResolver::class,
],
```

- Resolver genérico con Eloquent (sin escribir una clase):

```
// config/lookup.php
'context' => [
	'id_param' => 'integration_id',
	'resolver' => null,
	'eloquent' => [
		'model' => App\Models\IntegrationTenant::class,
		'channel_column' => 'channel_key',
		'credentials_column' => 'credentials',
	],****
],
```

Sugerencia de modelo:

```
class IntegrationTenant extends \Illuminate\Database\Eloquent\Model
{
	protected $casts = [
		'credentials' => 'array',
	];
}
```

Ejemplo de request con `tenant_id`:

```
POST /tenant-integrations/lookup
Content-Type: application/json

{ "tenant_id": 42, "entity": "seller", "params": { "limit": 1 } }
```

### Persistencia desde providers (helpers)

[](#persistencia-desde-providers-helpers)

Para facilitar inserciones/actualizaciones en tus modelos Eloquent desde un provider custom, `AbstractLookupProvider` expone métodos protegidos que usan `ModelWriter` internamente:

- `persistCreate(string $modelClass, array $attributes): Model`
- `persistUpdateOrCreate(string $modelClass, array $where, array $attributes): Model`
- `persistUpsert(string $modelClass, array $rows, array $uniqueBy, array $update): int`

Ejemplos:

```
use Flowstore\Lookup\DTO\IntegrationContext;
use Flowstore\Lookup\Support\AbstractLookupProvider;

final class ShopifyLookupProvider extends AbstractLookupProvider
{
    public function resources(): array { return ['product']; }
    public function testConnection(IntegrationContext $context): void {}

    public function lookup(IntegrationContext $context, string $entity, array $params = [])
    {
        // ... obtén $payload remoto y mapea los campos de tu modelo

        // Crear o actualizar un registro único por external_id
        $product = $this->persistUpdateOrCreate(
            \App\Models\Product::class,
            ['external_id' => $payload['id']],
            [
                'name' => $payload['title'],
                'price' => $payload['price'],
            ]
        );

        // Upsert masivo
        $this->persistUpsert(
            \App\Models\Product::class,
            $rows /* [[ 'external_id' => '...', 'name' => '...' ], ...] */,
            ['external_id'],
            ['name','price']
        );

        return $product; // o devuelve el payload para que lo mapee el mapper
    }
}
```

Notas:

- `modelClass` es el FQCN del modelo (`App\Models\...`).
- Asegúrate de que tu modelo tenga fillable/casts adecuados para los `attributes`.
- `persistUpsert` sigue la firma de `Eloquent\Builder::upsert($rows, $uniqueBy, $update)`.

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance62

Regular maintenance activity

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

9

Last Release

234d ago

Major Versions

v0.3.0-alpha.3 → v1.0.02025-09-24

PHP version history (2 changes)v0.1.0-alpha.1PHP ^8.2

v0.2.0-alpha.1PHP ^8.1|^8.2|^8.3|^8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/622679d5659254e8d4410310b9ae83d02dac4a2450fd1d285527c756ec79f3dd?d=identicon)[giolabs](/maintainers/giolabs)

---

Top Contributors

[![giodevuy](https://avatars.githubusercontent.com/u/259621443?v=4)](https://github.com/giodevuy "giodevuy (13 commits)")

---

Tags

laravelprovidermapperecommerceintegrationERPlookupmarketplace

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/flowstore-lookup/health.svg)

```
[![Health](https://phpackages.com/badges/flowstore-lookup/health.svg)](https://phpackages.com/packages/flowstore-lookup)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[laravel/passport

Laravel Passport provides OAuth2 server support to Laravel.

3.4k85.0M532](/packages/laravel-passport)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k25.9M107](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k12.1M99](/packages/laravel-pulse)[laravel/reverb

Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.

1.6k9.4M48](/packages/laravel-reverb)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

74310.9M66](/packages/laravel-mcp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
