PHPackages                             scafera/integration - 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. [API Development](/categories/api)
4. /
5. scafera/integration

ActiveSymfony-bundle[API Development](/categories/api)

scafera/integration
===================

External system integration for the Scafera framework

v1.0.2(1mo ago)00MITPHPPHP &gt;=8.4

Since Apr 15Pushed 1mo agoCompare

[ Source](https://github.com/scafera/integration)[ Packagist](https://packagist.org/packages/scafera/integration)[ Docs](https://github.com/scafera/integration)[ RSS](/packages/scafera-integration/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (3)Versions (4)Used By (0)

scafera/integration
===================

[](#scaferaintegration)

External system communication for the Scafera framework. Provides an enforceable gateway pattern for calling third-party APIs — all behind Scafera-owned types.

Internally adopts `symfony/http-client`. Userland code never imports Symfony HttpClient types — boundary enforcement blocks it at build time. All alternative HTTP mechanisms (cURL, `file_get_contents` with HTTP URLs) are also blocked.

> **Provides:** External system communication for Scafera — a gateway pattern where each class wraps one third-party system with business-level methods. `HttpClient` (5 methods: get/post/put/patch/delete) and `Response` (statusCode/json/body/headers/header) are Scafera-owned types; gateways wire up via `#[Integration]`. All HTTP escape hatches (Symfony HttpClient, cURL, `file_get_contents`/`fopen` with HTTP URLs) are blocked outside `Integration/`.
>
> **Depends on:** A Scafera host project with an `Integration/` layer (e.g. `src/Integration/` under `App\Integration`). Per-integration config under `integration:` in `config/config.yaml`; secrets belong in `config.local.yaml` (git-ignored).
>
> **Extension points:**
>
> - Attribute — `#[Integration('name')]` resolves to the configured `HttpClient`; `#[Integration('name', 'key')]` resolves to a per-integration config value (ADR-065)
> - User gateways — one class per external system in `Integration/...`, class name must end with `Gateway` (enforced by `GatewayNamingValidator`), business-level methods only
> - Config — `integration:` section in `config/config.yaml` declares each integration's `base_url`, `auth`, and any custom keys (injectable via the two-arg attribute)
> - Testing — `HttpClient` constructor accepts an optional `HttpClientInterface` (e.g. `MockHttpClient`) for test doubles; the bundle never passes it in production
>
> **Not responsible for:** Raw HTTP outside `Integration/` (Symfony HttpClient, cURL, `file_get_contents`/`fopen` with HTTP URLs — blocked by `HttpClientLeakageValidator`, `HttpClientBoundaryValidator`) · auto-throwing on HTTP errors (`Response` never throws; the gateway decides) · full URLs in gateway methods (relative paths only; base URL from config) · secret storage (belongs in `config.local.yaml`, not `config.yaml`) · unused `integration:` config keys (flagged by `UnusedIntegrationConfigValidator`).

This is a **capability package**. It adds optional external system integration to a Scafera project. It does not define folder structure or architectural rules — those belong to architecture packages.

What it provides
----------------

[](#what-it-provides)

- `HttpClient` — wraps Symfony HttpClient with 5 methods (get, post, put, patch, delete)
- `Response` — wraps Symfony response with 5 methods (statusCode, json, body, headers, header)
- `#[Integration('name')]` — attribute for wiring gateways to configured HttpClient instances
- `#[Integration('name', 'key')]` — attribute for injecting integration-specific config values into gateways (ADR-065)
- `HttpClientLeakageValidator` — blocks all HTTP escape hatches in `src/`
- `GatewayNamingValidator` — enforces `*Gateway` naming in `Integration/`
- `HttpClientBoundaryValidator` — ensures `HttpClient` is only used in `Integration/` layer
- `IntegrationConfigBoundaryValidator` — ensures `#[Integration('name', 'key')]` config values are only used in `Integration/` layer
- `UnusedIntegrationConfigValidator` — flags config values defined in `integration:` but not referenced in any gateway

Design decisions
----------------

[](#design-decisions)

- **Gateway, not HTTP client wrapper** — a wrapped HTTP client would scatter `$http->post()` calls across the codebase. Gateways enforce one class per external system with business-level methods (ADR-064).
- **`#[Integration]` attribute for wiring** — extends Symfony's `#[Autowire]`, same pattern as `#[Config]` in the kernel. Explicit at the injection site, greppable, validatable, refactor-safe (ADR-064). With a second argument, resolves integration-specific config values (ADR-065).
- **Relative paths only** — gateways use endpoint paths (`'/charges'`), not full URLs. Base URL is configured once in `config.yaml`.
- **All HTTP escape hatches blocked** — Symfony HttpClient, cURL, `file_get_contents` with HTTP URLs, `fopen` with HTTP URLs. If you need HTTP, you go through a gateway.

Installation
------------

[](#installation)

```
composer require scafera/integration
```

The bundle is auto-discovered via Scafera's `symfony-bundle` type detection. No manual registration needed.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.4
- scafera/kernel

Configuration
-------------

[](#configuration)

```
# config/config.yaml
integration:
    stripe:
        base_url: 'https://api.stripe.com/v1'
        auth: ''
    mailgun:
        base_url: 'https://api.mailgun.net/v3'
        auth: ''
```

```
# config.local.yaml (not committed)
integration:
    stripe:
        auth: 'Bearer sk_live_real_secret'
    mailgun:
        auth: 'Basic key-real_secret'
```

If an integration has different base URLs per environment (e.g., sandbox vs production), override `base_url` in `config.local.yaml` as well.

Gateway
-------

[](#gateway)

A gateway is one class per external system with business-level methods:

```
namespace App\Integration\Stripe;

use Scafera\Integration\HttpClient;
use Scafera\Integration\Attribute\Integration;

final class PaymentGateway
{
    public function __construct(
        #[Integration('stripe')]
        private HttpClient $http,
    ) {}

    public function createPayment(int $amount, string $currency): array
    {
        return $this->http->post('/charges', [
            'amount' => $amount,
            'currency' => $currency,
        ])->json();
    }

    public function refund(string $chargeId): array
    {
        return $this->http->post('/refunds', [
            'charge' => $chargeId,
        ])->json();
    }
}
```

### Gateway rules

[](#gateway-rules)

- Class name must end with `Gateway` — enforced by validator
- One class per external system
- Business-level methods only — `createPayment()`, not `post()`
- No HTTP types in public method signatures — return arrays or domain objects
- No full URLs — endpoint paths only, base URL from config

Using a gateway in a service
----------------------------

[](#using-a-gateway-in-a-service)

Services inject gateways via constructor — same as any Scafera dependency:

```
namespace App\Service\Order;

use App\Integration\Stripe\PaymentGateway;

final class PlaceOrder
{
    public function __construct(
        private PaymentGateway $payment,
    ) {}

    public function handle(int $amount): array
    {
        return $this->payment->createPayment($amount, 'usd');
    }
}
```

Integration-specific configuration
----------------------------------

[](#integration-specific-configuration)

Integrations can carry arbitrary config values beyond `base_url` and `auth`. These are registered as container parameters and injected via the same `#[Integration]` attribute with a second argument:

```
# config/config.yaml
integration:
    linkedin:
        base_url: 'https://api.linkedin.com/v2'
        auth: ''
        contract_id: ''
        seat_limit: 500
```

```
# config.local.yaml (not committed)
integration:
    linkedin:
        auth: 'Bearer token_secret'
        contract_id: 'CONTRACT-2026-XYZ'
```

The gateway receives config values alongside the `HttpClient`:

```
namespace App\Integration\LinkedIn;

use Scafera\Integration\HttpClient;
use Scafera\Integration\Attribute\Integration;

final class PremiumGateway
{
    public function __construct(
        #[Integration('linkedin')]
        private HttpClient $http,
        #[Integration('linkedin', 'contract_id')]
        private string $contractId,
        #[Integration('linkedin', 'seat_limit')]
        private int $seatLimit,
    ) {}

    public function inviteSeat(string $userId): array
    {
        return $this->http->post('/premium/invites', [
            'contract_id' => $this->contractId,
            'user_id' => $userId,
        ])->json();
    }
}
```

`#[Integration('name')]` without a second argument resolves to the `HttpClient` service. `#[Integration('name', 'key')]` resolves to the config value. The `HttpClient` itself has no awareness of these values — they are resolved by the container before the gateway is constructed.

HttpClient API
--------------

[](#httpclient-api)

```
$this->http->get(string $path, array $options = []): Response;
$this->http->post(string $path, array $data = [], array $options = []): Response;
$this->http->put(string $path, array $data = [], array $options = []): Response;
$this->http->patch(string $path, array $data = [], array $options = []): Response;
$this->http->delete(string $path, array $options = []): Response;
```

The `$data` array is sent as JSON body. For other content types (form-encoded, multipart), use the `$options` array directly (e.g., `$this->http->post('/upload', [], ['body' => $formData])`).

Response API
------------

[](#response-api)

```
$response->statusCode(): int;
$response->json(): array;
$response->body(): string;
$response->headers(): array;
$response->header(string $name): ?string;
```

HTTP errors do not throw automatically — error handling is the gateway's responsibility.

Testing
-------

[](#testing)

### Testing services that use gateways

[](#testing-services-that-use-gateways)

Mock the gateway, not the HTTP client:

```
final class PlaceOrderTest extends WebTestCase
{
    public function testPlacesOrder(): void
    {
        // Mock PaymentGateway — stable, behavior-focused
        // No HTTP client mocking, no request/response faking
    }
}
```

### Testing gateways themselves

[](#testing-gateways-themselves)

The `HttpClient` constructor accepts an optional `HttpClientInterface` for testing — the bundle never passes it (engine stays hidden in production):

```
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

$mock = new MockHttpClient([new MockResponse('{"id": 1}')]);
$http = new HttpClient('https://api.example.com', null, $mock);

$gateway = new PaymentGateway($http);
$result = $gateway->createPayment(1000, 'usd');
```

Boundary enforcement
--------------------

[](#boundary-enforcement)

BlockedUse instead`Symfony\Contracts\HttpClient\*``Scafera\Integration\HttpClient` in a Gateway class`Symfony\Component\HttpClient\*``Scafera\Integration\HttpClient` in a Gateway class`curl_*` functions`Scafera\Integration\HttpClient` in a Gateway class`file_get_contents` with HTTP URLs`Scafera\Integration\HttpClient` in a Gateway class`fopen` with HTTP URLs`Scafera\Integration\HttpClient` in a Gateway class`Scafera\Integration\HttpClient` outside `Integration/`Inject the gateway, not the HTTP client`#[Integration('name', 'key')]` outside `Integration/`Integration config values belong in gateways onlyUnused config keys under `integration:`Remove unused keys or reference them in a gatewayEnforced via validators (`scafera validate`). The `HttpClient` and integration config values are only allowed inside the `Integration/` layer — services and controllers inject gateways, not HTTP clients or integration config.

License
-------

[](#license)

MIT

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance89

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

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

3

Last Release

54d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e9233f415f42755f68dc3006e2853b76c34de54b04c9535202b70aa147790e6a?d=identicon)[samaphp](/maintainers/samaphp)

---

Top Contributors

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

---

Tags

phpgatewayintegrationscafera

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/scafera-integration/health.svg)

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

###  Alternatives

[deepseek-php/deepseek-php-client

deepseek PHP client is a robust and community-driven PHP client library for seamless integration with the Deepseek API, offering efficient access to advanced AI and data processing capabilities.

46684.5k5](/packages/deepseek-php-deepseek-php-client)

PHPackages © 2026

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