flowstore/lookup
最新稳定版本:v1.3.0
Composer 安装命令:
composer require flowstore/lookup
包简介
Agnostic lookup layer for external channels (ecommerce/ERP/marketplace) mapping to domain DTOs.
README 文档
README
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
- 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
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
LookupProviderInterface:resources(),testConnection(IntegrationContext),lookup(IntegrationContext, entity, params).EntityMapperInterface<TDomain>:entity(),map(payload, IntegrationContext): TDomain.IntegrationContext: DTO conchannelKeyycredentials.IntegrationContextResolver: contrato que la app host implementa para resolver un contexto desde unintegrationId.
Resolución y orquestación
LookupProviderResolver: resuelve Provider porchannel_keyusandoconfig('lookup.providers')o la convenciónApp\\Lookup\\Providers\\{Canal}LookupProvider.EntityMapperResolver: resuelve Mapper porchannel_key + entityusandoconfig('lookup.mappers')o la convenciónApp\\Lookup\\Mappers\\{Canal}\\{Entidad}Mapper.PerformLookupAction: invocaprovider->lookup(...)y mapea conmapper->map(...).LookupService: resuelve contexto (si se usaIntegrationContextResolver) y devuelve el DTO de dominio.
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)
- La ruta se habilita por defecto al instalar el paquete.
- Endpoint por defecto:
POST /tenant-integrations/lookup(middlewareapi). - 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)
- Endpoint por defecto:
POST /tenant-integrations/test-connection - Body (JSON):
{ "channel_key": "shopify", "integration_id": 123 }
- Respuesta:
{ "success": true }
- Requiere un
IntegrationContextResolverconfigurado (propio concontext.resolvero genérico concontext.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
- Agregar un canal: crear
App\\Lookup\\Providers\\{Canal}LookupProvidery registrarlo enconfig/lookup.php(o seguir la convención). - Agregar una entidad: crear
App\\Lookup\\Mappers\\{Canal}\\{Entidad}Mappery registrarlo (o usar la convención). testConnection(...)permite "ping" de credenciales antes de usarlookup.
Comando de scaffolding
php artisan make:lookup {channel} {entity} {--provider}
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
- Evita dependencia dura a modelos: usa
IntegrationContexto implementaIntegrationContextResolveren tu app. - Usa el Http Client de Laravel con timeouts/retries; el paquete provee un
AbstractLookupProviderde ayuda. - Mantén el retorno como DTO de dominio consistente y documentado por entidad.
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 es un DTO inmutable que describe el contexto de una integración:
channelKey(string): identifica el canal (p.ej.shopify,mercadoLibre).credentials(array<string, mixed>): 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)
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): ModelpersistUpdateOrCreate(string $modelClass, array $where, array $attributes): ModelpersistUpsert(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:
modelClasses el FQCN del modelo (App\Models\...).- Asegúrate de que tu modelo tenga fillable/casts adecuados para los
attributes. persistUpsertsigue la firma deEloquent\Builder::upsert($rows, $uniqueBy, $update).
统计信息
- 总下载量: 13
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-09-23