PHPackages                             thinwrap/location - 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. [HTTP &amp; Networking](/categories/http)
4. /
5. thinwrap/location

ActiveLibrary[HTTP &amp; Networking](/categories/http)

thinwrap/location
=================

Lightweight, SDK-free PHP wrapper for routing, distance matrix, geocoding, and isochrone across Google, Mapbox, HERE, ESRI, TomTom, and OSRM. Stateless, PSR-18 BYO HTTP client; php-http/discovery is the only runtime dependency (auto-wires a PSR-18 client when none is injected); no vendor SDKs.

v1.2.1(2w ago)12[1 PRs](https://github.com/thinwrap/location-php/pulls)MITPHPPHP ^8.2CI passing

Since Jul 3Pushed 2w agoCompare

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

READMEChangelog (4)Dependencies (16)Versions (6)Used By (0)

thinwrap/location
=================

[](#thinwraplocation)

Unified PHP facade for 21 location connectors across routing, matrix, geocoding, and isochrone — over 6 providers (Google, Mapbox, HERE, ESRI, TomTom, OSRM). Stateless. Zero vendor SDKs. Bring your own PSR-18 HTTP client.

Install
-------

[](#install)

```
composer require thinwrap/location
```

Requires PHP ≥8.2. PSR-18 HTTP client + PSR-17 factories are auto-discovered via `php-http/discovery` — if you don't already have one installed:

```
composer require guzzlehttp/guzzle guzzlehttp/psr7
```

End-to-end example — 2-minute time-to-first-route
-------------------------------------------------

[](#end-to-end-example--2-minute-time-to-first-route)

```
use Thinwrap\Location\Enum\LocationProviderId;
use Thinwrap\Location\Config\GoogleConfig;
use Thinwrap\Location\DTO\LatLng;
use Thinwrap\Location\DTO\Routing\RoutingOptions;
use Thinwrap\Location\Routing;
use Thinwrap\Location\ConnectorError;

$routing = new Routing(LocationProviderId::Google, new GoogleConfig(apiKey: getenv('GOOGLE_KEY')));

try {
    $result = $routing->route(new RoutingOptions(
        waypoints: [
            new LatLng(40.7128, -74.0060),  // New York
            new LatLng(41.4173, -73.0001),  // Bridgeport
        ],
        travelMode: 'driving',
    ));
    echo $result->totalDistanceMeters;   // distance in meters
    echo $result->totalDurationSeconds;  // duration in seconds
    echo $result->polyline;              // Google precision-5 polyline string
} catch (ConnectorError $e) {
    error_log($e->providerCode->value . ': ' . ($e->providerMessage ?? ''));
}
```

Routing options that cost money
-------------------------------

[](#routing-options-that-cost-money)

Three inputs exist because the cheap thing and the correct thing are not always the same request. All three default to the lean option; you opt up explicitly.

OptionDefaultWhat it changes`$polylineQuality``PolylineQuality::Simplified`Geometry fidelity. `Detailed` returned a **30x larger** polyline on Mapbox and **31x** on OSRM in measurement, with identical distances and durations. Honoured by Google/Mapbox/OSRM; silently ignored by HERE/TomTom/Esri, which expose no equivalent knob.`$trafficMode``TrafficMode::None`Whether to route against live traffic. `Live` selects a **Pro-tier SKU** on Google, so it is never enabled implicitly — not even by passing `$departureTime`.`$include``[]`Which optional output fields to fetch. Each token maps 1:1 onto one optional result field.```
use Thinwrap\Location\Enum\{PolylineQuality, RoutingInclude, TrafficMode};

$result = $routing->route(new RoutingOptions(
    waypoints: $waypoints,
    trafficMode: TrafficMode::Live,                     // opt into traffic-aware routing
    polylineQuality: PolylineQuality::Detailed,         // opt into full geometry
    include: [RoutingInclude::DurationWithoutTraffic],  // opt into the extra output field
));

// Present only when requested AND returned natively — never synthesized, so null
// tells you this provider did not supply it.
$congestion = $result->totalDurationWithoutTrafficSeconds !== null
    ? $result->totalDurationSeconds - $result->totalDurationWithoutTrafficSeconds
    : null;
```

`DurationWithoutTraffic` is native on Google (`staticDuration`), HERE (`baseDuration`) and TomTom (`noTrafficTravelTimeInSeconds`); Mapbox, OSRM and Esri do not return it, so the field stays null there rather than being faked.

### Making OSRM's avoid-flags work

[](#making-osrms-avoid-flags-work)

Whether OSRM accepts `exclude=toll` is a property of **your server**, not of OSRM. The same request was verified live against two builds with opposite results: the public demo build rejects it with `InvalidValue`, while a self-hosted instance honoured it and genuinely rerouted (138075 m / 5890 s via the toll road → 130421 m / 6513 s without).

Stock OSRM compiles no exclude classes, so the flags are rejected up front by default. If your profile was built with them, declare it:

```
$routing = new Routing(LocationProviderId::Osrm, new OsrmConfig(
    baseUrl: 'https://routing.internal',
    supportedExcludeClasses: ['toll', 'ferry'],
));
```

Autocomplete → place details
----------------------------

[](#autocomplete--place-details)

`autocomplete()` returns predictions; `placeDetails()` resolves one into a full candidate. "Place details" and "geocode by place id" are the same vendor call on all five providers, so this is one operation, not two — and it returns an ordinary `GeocodeCandidate`.

```
$geocoding = new Geocoding(LocationProviderId::Google, new GoogleConfig(apiKey: $key));

$predictions = $geocoding->autocomplete(new AutocompleteOptions(input: 'blue bottle'))->predictions;

// Render the usual two-line suggestion without splitting `description` on a comma.
foreach ($predictions as $p) {
    echo $p->structuredFormat?->mainText ?? $p->description, "\n";
    echo $p->structuredFormat?->secondaryText ?? '', "\n";
}

$details = $geocoding->placeDetails(new PlaceDetailsOptions(placeId: $predictions[0]->placeId));
```

`placeId` values are **provider-scoped** — a Google place id is meaningless to Mapbox.

### Two things that cost money here

[](#two-things-that-cost-money-here)

**Google's Place Details SKU is driven by the field mask**, so `name` (`displayName`) is a Pro-tier field and only requested behind an opt-in:

```
$geocoding->placeDetails(new PlaceDetailsOptions(
    placeId: $placeId,
    include: [PlaceDetailsInclude::Name],
));
```

Note this is the *opposite* of Compute Routes, whose SKU is driven by request *features* — check per API rather than generalizing.

**Mapbox Search Box bills per session, not per request.** A `suggest` and the `retrieve` that follows count as one billable session only when they carry the same `session_token`:

```
$token = bin2hex(random_bytes(16));   // one per user interaction

$mapbox->autocomplete(new AutocompleteOptions(
    input: $input,
    passthrough: new Passthrough(query: ['session_token' => $token]),
));
$mapbox->placeDetails(new MapboxPlaceDetailsOptions(placeId: $placeId, sessionToken: $token));
```

The wrapper cannot generate or remember that token — it holds no state.

### `structuredFormat` support

[](#structuredformat-support)

Provider`mainText` / `secondaryText`Google`structuredFormat.mainText` / `.secondaryText` — default-on, freeMapbox`name` / `place_formatted`HERE`title` / `address.label` — `secondaryText` null for *query*-type suggestions, which carry no addressTomTom`poi.name` / `address.freeformAddress` — **null for street results**, which have no `poi.name`Esrinot supported — returns a single flat `text`It is **never synthesized**: a null `structuredFormat` means the provider gave no distinct main part, and `$description` remains the thing to render.

Switching providers
-------------------

[](#switching-providers)

Change the `LocationProviderId` case and config DTO; the input and output shape stay identical.

```
use Thinwrap\Location\Config\MapboxConfig;

$a = new Routing(LocationProviderId::Google, new GoogleConfig(apiKey: getenv('GOOGLE_KEY')));
$b = new Routing(LocationProviderId::Mapbox, new MapboxConfig(accessToken: getenv('MAPBOX_TOKEN')));

$sameInput = new RoutingOptions(
    waypoints: [$origin, $destination],
    travelMode: 'driving',
);
$ra = $a->route($sameInput);
$rb = $b->route($sameInput);
// $ra and $rb share the same RoutingResult shape:
//   { legs, totalDistanceMeters, totalDurationSeconds, polyline, waypointOrder?, raw }
```

Bring your own PSR-18 client
----------------------------

[](#bring-your-own-psr-18-client)

Inject any PSR-18 client through the third constructor argument on the facade — useful for tracing, retries, mocking, or proxying through `symfony/http-client`. The `*Config` DTO carries only credentials; the HTTP client is a facade-level seam.

**Contract: a non-2xx must be RETURNED, not thrown.** PSR-18 requires this and compliant clients honour it, so each connector can map the status to a `ProviderCode`(429 → `RateLimited`, 401 → `AuthFailed`, …) and read the vendor's message. A client that raises instead — Guzzle used outside its PSR-18 adapter, or a decorator calling raise-on-error — is handled defensively: the answered response is recovered from the exception's `getResponse()` so classification still runs.

```
use GuzzleHttp\Client;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

$tracingClient = new class(new Client()) implements ClientInterface {
    public function __construct(private Client $inner) {}
    public function sendRequest(RequestInterface $req): ResponseInterface
    {
        error_log('→ ' . $req->getMethod() . ' ' . (string) $req->getUri());
        return $this->inner->sendRequest($req);
    }
};

$routing = new Routing(
    LocationProviderId::Google,
    new GoogleConfig(apiKey: getenv('GOOGLE_KEY')),
    $tracingClient,
);
```

The wrapper holds no state — no token cache, no connection pool, no retry buffer. Every operation is a single function call from input to output with one HTTP round-trip (except HERE Matrix v8, which transparently runs a submit → poll → retrieve cycle behind a single `$matrix->matrix($input)` call).

Error handling
--------------

[](#error-handling)

Every failure surfaces as `ConnectorError` with a typed `ProviderCode`. Compose your own retry strategy from `$e->providerCode` and `$e->cause` (which carries the raw `Retry-After` header where the vendor sets one).

```
use Thinwrap\Location\ConnectorError;
use Thinwrap\Location\Enum\ProviderCode;

try {
    $routing->route($input);
} catch (ConnectorError $e) {
    match ($e->providerCode) {
        ProviderCode::RateLimited           => /* respect Retry-After in $e->cause      */ null,
        ProviderCode::AuthFailed            => /* rotate credentials                     */ null,
        ProviderCode::InvalidRequest        => /* fix payload                            */ null,
        ProviderCode::InvalidRecipient      => /* fix destination                        */ null,
        ProviderCode::ProviderUnavailable   => /* transient 5xx — your retry strategy    */ null,
        ProviderCode::UnsupportedField      => /* drop OSRM-incompatible field           */ null,
        ProviderCode::UnsupportedOption     => /* drop OSRM-incompatible option          */ null,
        ProviderCode::UnsupportedTravelMode => /* fall back to a supported travel mode   */ null,
        ProviderCode::ProfileNotConfigured  => /* compile the OSRM profile               */ null,
        ProviderCode::MatrixPollingTimeout  => /* resume via $e->cause['matrixId']       */ null,
        ProviderCode::NoRoute               => /* no route between these points          */ null,
        ProviderCode::Timeout               => /* request exceeded the client's bound    */ null,
        ProviderCode::Unknown               => /* fallback                               */ null,
    };
}
```

### `no_route` — "there is no route", normalized

[](#no_route--there-is-no-route-normalized)

The providers agree on nothing here. Google answers HTTP **200** with the `routes` key absent; HERE 200 with `routes: []` plus a `notices[].code`; Mapbox `code: "NoRoute"` on either 200 or 422; OSRM the same codes on a **400**; TomTom a 400 with `detailedError.code`; Esri a 200 whose in-body `error.code: 400` names an **unlocated**stop in `details[]`. Branching on "no usable route" used to mean reimplementing all six.

In practice it almost always means *a waypoint could not be matched to the road network* rather than *the road network is disconnected*: every provider tested happily routes Reykjavik→Oslo via ferry.

### `timeout`

[](#timeout)

Separated from `provider_unavailable` because it is the one transport failure a caller acts on differently — back off and retry, versus treat the provider as down.

On PHP the `Timeout` classification is **best-effort**: the HTTP client is BYO (PSR-18), which defines no timeout-specific exception type, so it is read from the client's message (cURL error 28 and friends). Configure the timeout on your own client.

The wrapper performs no automatic retry. The `Retry-After` header (when present on HTTP 429) is surfaced via `$e->cause['retryAfter']` (raw header string) and the parsed seconds count is woven into `$e->providerMessage` (`…; retry after N seconds`). There is **no** structured `retryAfterSeconds` field on `ConnectorError`.

`$e->providerMessage` is safe to log — known credential query params are redacted from transport-error messages. But `$e->cause` and `$e->getPrevious()` retain the raw underlying HTTP-client exception, which may embed the full request URL and headers (including live credentials); do not log them unfiltered.

`_passthrough` escape valve
---------------------------

[](#_passthrough-escape-valve)

When the normalized input doesn't expose a vendor-specific field, forward arbitrary keys via the `Passthrough` DTO on the operation options. The wrapper deep-merges `body`, shallow-merges `headers` and `query`. Consumer values win on conflict. Keys are forwarded verbatim — no casing transformation.

```
use Thinwrap\Location\DTO\Passthrough;

$routing->route(new RoutingOptions(
    waypoints: [$origin, $destination],
    passthrough: new Passthrough(
        body:    ['languageCode' => 'fr', 'units' => 'IMPERIAL'],
        headers: ['X-Goog-FieldMask' => 'routes.legs.distanceMeters,routes.duration'],
        query:   ['region' => 'us'],
    ),
));
```

Each per-connector README documents its vendor-specific `_passthrough` examples.

Polyline utilities
------------------

[](#polyline-utilities)

```
use Thinwrap\Location\Util\Polyline;

$latLngs = Polyline::decodePolyline($result->polyline);             // list
$re      = Polyline::encodePolyline($latLngs);                      // back to precision-5
$here    = Polyline::decodeFlexPolyline('BFoz5...');                // HERE flex-polyline
$esri    = Polyline::encodeEsriPaths([[[-74, 40], [-73.5, 40.5]]]); // ESRI paths
```

All facades emit Google precision-5 encoded polyline on `$result->polyline`. The four public static methods on `Polyline` are the only encode/decode primitives exported — locked at v1.0.

Language constraints
--------------------

[](#language-constraints)

- PHP 8.2 minimum; PHPStan level 8 expected for consumer code that uses union-typed config narrowing.
- Runs on PHP 8.2, 8.3, and 8.4 (CI matrix; Linux only at v1.0 — Windows / macOS deferred to v1.1).
- `declare(strict_types=1)` is required on every file in this library and recommended for consumer code.
- Only three runtime dependencies — `psr/http-client` + `psr/http-factory` (interfaces) and `php-http/discovery`, which auto-wires a PSR-18 client when none is injected. No vendor SDKs.
- Server-only. Most providers require server-only secrets — there is no browser story.

Public API surface (locked at v1.0)
-----------------------------------

[](#public-api-surface-locked-at-v10)

CategoryExportsFacades`Routing`, `Matrix`, `Geocoding`, `Isochrone` (top-level under `Thinwrap\Location\`)Error`ConnectorError`, `Thinwrap\Location\Enum\ProviderCode`Geometry`Thinwrap\Location\DTO\LatLng`, `Thinwrap\Location\Util\Polyline` (4 static methods: `encodePolyline`, `decodePolyline`, `decodeFlexPolyline`, `encodeEsriPaths`)Routing connectors`GoogleRoutingConnector`, `MapboxRoutingConnector`, `HereRoutingConnector`, `EsriRoutingConnector`, `TomTomRoutingConnector`, `OsrmRoutingConnector`Matrix connectors`GoogleMatrixConnector`, `MapboxMatrixConnector`, `HereMatrixConnector`, `EsriMatrixConnector`, `TomTomMatrixConnector`, `OsrmMatrixConnector`Geocoding connectors`GoogleGeocodingConnector`, `MapboxGeocodingConnector`, `HereGeocodingConnector`, `EsriGeocodingConnector`, `TomTomGeocodingConnector`Isochrone connectors`MapboxIsochroneConnector`, `HereIsochroneConnector`, `EsriIsochroneConnector`, `TomTomIsochroneConnector`Config DTOs`GoogleConfig`, `MapboxConfig`, `HereConfig`, `EsriConfig`, `TomTomConfig`, `OsrmConfig`Enums`LocationProviderId`, `ProviderCode`, `TravelMode`, `IsochroneType`Per-connector documentation
---------------------------

[](#per-connector-documentation)

Each per-connector README documents auth, endpoints (regional/sandbox), narrowed input augmentations, outlier translations, error-code mappings, and `_passthrough` examples.

### Routing (6)

[](#routing-6)

ProviderREADME`google`[src/Providers/Google/README.md](src/Providers/Google/README.md)`mapbox`[src/Providers/Mapbox/README.md](src/Providers/Mapbox/README.md)`here`[src/Providers/Here/README.md](src/Providers/Here/README.md)`esri`[src/Providers/Esri/README.md](src/Providers/Esri/README.md)`tomtom`[src/Providers/TomTom/README.md](src/Providers/TomTom/README.md)`osrm`[src/Providers/Osrm/README.md](src/Providers/Osrm/README.md)### Matrix (6)

[](#matrix-6)

ProviderREADME`google`[src/Providers/Google/README.md](src/Providers/Google/README.md)`mapbox`[src/Providers/Mapbox/README.md](src/Providers/Mapbox/README.md)`here`[src/Providers/Here/README.md](src/Providers/Here/README.md)`esri`[src/Providers/Esri/README.md](src/Providers/Esri/README.md)`tomtom`[src/Providers/TomTom/README.md](src/Providers/TomTom/README.md)`osrm`[src/Providers/Osrm/README.md](src/Providers/Osrm/README.md)### Geocoding (5)

[](#geocoding-5)

ProviderREADME`google`[src/Providers/Google/README.md](src/Providers/Google/README.md)`mapbox`[src/Providers/Mapbox/README.md](src/Providers/Mapbox/README.md)`here`[src/Providers/Here/README.md](src/Providers/Here/README.md)`esri`[src/Providers/Esri/README.md](src/Providers/Esri/README.md)`tomtom`[src/Providers/TomTom/README.md](src/Providers/TomTom/README.md)### Isochrone (4)

[](#isochrone-4)

ProviderREADME`mapbox`[src/Providers/Mapbox/README.md](src/Providers/Mapbox/README.md)`here`[src/Providers/Here/README.md](src/Providers/Here/README.md)`esri`[src/Providers/Esri/README.md](src/Providers/Esri/README.md)`tomtom`[src/Providers/TomTom/README.md](src/Providers/TomTom/README.md)Baseline-coverage discipline
----------------------------

[](#baseline-coverage-discipline)

The unified facade surface includes only features ≥90% of providers natively support. Sub-baseline fields are accessible via the `Passthrough` escape hatch, plus the one per-provider narrowed type that exists at v1.0 (HERE routing, `src/Providers/Here/DTO/`).

Migrating
---------

[](#migrating)

### From `googlemaps/google-maps-services-php`

[](#from-googlemapsgoogle-maps-services-php)

```
// Before — googlemaps/google-maps-services-php
$client = new \GoogleMaps\Client(['key' => 'YOUR_KEY']);
$response = $client->directions([...]);

// After
use Thinwrap\Location\Enum\LocationProviderId;
use Thinwrap\Location\Config\GoogleConfig;
use Thinwrap\Location\Routing;

$routing = new Routing(LocationProviderId::Google, new GoogleConfig(apiKey: 'YOUR_KEY'));
$result = $routing->route(new RoutingOptions(waypoints: [$origin, $destination]));
```

### From `mapbox/mapbox-sdk-php` (community port)

[](#from-mapboxmapbox-sdk-php-community-port)

```
// Before — community Mapbox SDK
$mapbox = new \Mapbox\Mapbox(['access_token' => 'YOUR_TOKEN']);
$directions = $mapbox->directions([...]);

// After
use Thinwrap\Location\Config\MapboxConfig;

$routing = new Routing(LocationProviderId::Mapbox, new MapboxConfig(accessToken: 'YOUR_TOKEN'));
$result = $routing->route(new RoutingOptions(waypoints: [$origin, $destination]));
```

### From raw HTTP / Guzzle

[](#from-raw-http--guzzle)

If you've been hand-rolling vendor HTTP calls with Guzzle, the facade collapses the boilerplate to one line per call. Error handling and retry composition stay yours.

For AI agents and contributors
------------------------------

[](#for-ai-agents-and-contributors)

- [`.ai/guidelines.md`](.ai/guidelines.md) — contributor entry point: how to add a connector.
- [`.ai/ARCHITECTURE.md`](.ai/ARCHITECTURE.md) — 6 location-distinctive invariants + PHP rules.
- [`.ai/CONVENTIONS.md`](.ai/CONVENTIONS.md) — naming, file layout, test patterns.

Security
--------

[](#security)

Report vulnerabilities **privately** — please do not open a public issue. Preferred: a [private security advisory](https://github.com/thinwrap/location-php/security/advisories/new)on this repository. Alternatively, email ****. Include the affected versions and a minimal reproduction if you have one.

A vulnerability in a *provider's* own API or service belongs to that vendor rather than to this wrapper — please report those upstream.

Supply chain: releases are cosign-signed via GitHub Actions OIDC (no static signing keys), maintainer accounts require two-factor authentication on GitHub, and Packagist consumes the package via webhook auto-sync — no long-lived Packagist API token is stored anywhere.

License
-------

[](#license)

MIT.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance98

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

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 ~9 days

Total

5

Last Release

14d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3479747?v=4)[Dmitry Polyanovsky](/maintainers/danikp)[@danikp](https://github.com/danikp)

---

Top Contributors

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

---

Tags

autocompletedirectionsdistance-matrixesrigeocodinggoogle-mapshereisochronelocationmapboxosrmphppolylinepsr-18reverse-geocodingroutingsdk-freestatelessthinwraptomtomautocompletegeocodingpsr-18routingwrapperfacadelocationgoogle mapsdirectionsstatelesspolylineESRImapboxArcGISosrmheredistance matrixreverse-geocodingtomtomthinwrapsdk-freeisochronegoogle-routes

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/thinwrap-location/health.svg)

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

###  Alternatives

[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36826.2k2](/packages/telnyx-telnyx-php)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[chargebee/chargebee-php

ChargeBee API client implementation for PHP

758.7M10](/packages/chargebee-chargebee-php)[getbrevo/brevo-php

Official PHP SDK for the Brevo API.

1004.1M59](/packages/getbrevo-brevo-php)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

187738.3k49](/packages/laudis-neo4j-php-client)

PHPackages © 2026

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