PHPackages                             yeevy/centris-passerelle - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. yeevy/centris-passerelle

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

yeevy/centris-passerelle
========================

Unofficial PHP client for the Centris® Passerelle FTP feed — parse, sync &amp; reconcile Quebec MLS listings. Not affiliated with or endorsed by Centris or QFREB.

v0.3.1(1mo ago)157[4 PRs](https://github.com/yeevy-ai/centris-passerelle/pulls)1MITPHPPHP ^8.2

Since Jul 4Pushed 2w agoCompare

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

READMEChangelog (4)Dependencies (12)Versions (5)Used By (1)

centris-passerelle
==================

[](#centris-passerelle)

[![Latest Version on Packagist](https://camo.githubusercontent.com/92564ffd07983b81cac84ed1117a41c25f2b6a72d166023197e693d5fc164032/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f79656576792f63656e747269732d706173736572656c6c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/yeevy/centris-passerelle)[![Tests](https://camo.githubusercontent.com/c703493f9b78ba2eadbaae05e3b5ff048c570eadb15701908c621d6f7c920d7c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f79656576792d61692f63656e747269732d706173736572656c6c652f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/yeevy-ai/centris-passerelle/actions/workflows/run-tests.yml)[![Total Downloads](https://camo.githubusercontent.com/083e093dcd92aba43c3a5c9bd53c486775916b604e7e00690cc1ee6b0ee46244/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f79656576792f63656e747269732d706173736572656c6c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/yeevy/centris-passerelle)

> **Non officiel. Aucune affiliation avec Centris ou l'APCIQ/QFREB, ni endossement de leur part. Requiert une entente de diffusion valide.**
>
> **Unofficial. Not affiliated with or endorsed by Centris or QFREB. Requires a valid diffusion agreement.**

[Français](#fran%C3%A7ais) | [English](#english)

---

Français
--------

[](#français)

Client PHP open source non officiel pour le flux FTP **Centris® Passerelle** (données d'inscriptions MLS du Québec distribuées aux courtiers autorisés). Analyse, synchronise et réconcilie les données d'inscriptions.

Noyau PHP pur, sans dépendance à un framework : utilisable depuis une extension WordPress, Laravel, Symfony ou un simple script cron.

### Comment fonctionne le flux Passerelle

[](#comment-fonctionne-le-flux-passerelle)

- Aucune API publique. Le courtier signe une entente de diffusion avec Centris/APCIQ et reçoit des identifiants FTP limités à ses propres inscriptions.
- Centris dépose un **instantané complet** une ou deux fois par jour (pas de deltas) : les retraits se détectent par différence — une inscription présente en base mais absente du nouveau fichier est vendue, expirée ou retirée.
- Fichiers livrés : `INSCRIPTIONS.TXT` (inscriptions), `REMARQUES.TXT` (descriptions FR/EN), `PHOTOS.TXT`, `ADDENDA.TXT`, plus des fichiers de référence (courtiers, agences, caractéristiques, municipalités).
- Format : CSV délimité par virgules, champs entre guillemets, **encodage Windows-1252**, sans ligne d'en-tête, colonnes positionnelles (~150), une inscription par ligne (CRLF).

### Prérequis

[](#prérequis)

- PHP 8.2+ avec l'extension `mbstring`
- Une entente de diffusion Passerelle valide (ce paquet ne fournit **aucune** donnée)

### Installation

[](#installation)

```
composer require yeevy/centris-passerelle
```

### Utilisation

[](#utilisation)

```
use Yeevy\CentrisPasserelle\Parser\ListingsParser;
use Yeevy\CentrisPasserelle\Enums\ListingStatus;

$parser = new ListingsParser();

foreach ($parser->parseFile('/chemin/vers/INSCRIPTIONS.TXT') as $listing) {
    $listing->mlsNumber;      // « 9999999 » — clé d'upsert
    $listing->salePrice;      // 975000 (null pour les locations)
    $listing->status;         // ListingStatus::Active | ListingStatus::Sold | null
    $listing->descriptionFr;  // contient du HTML
    $listing->descriptionEn;
    $listing->latitude;
    $listing->longitude;
    $listing->dirtyHash;      // sha256 de la ligne brute — ignorez les lignes inchangées
    $listing->row;            // ligne brute complète pour les colonnes non cartographiées
}
```

L'analyse est paresseuse (générateur) : les instantanés volumineux ne saturent pas la mémoire. La conversion Windows-1252 → UTF-8 est appliquée automatiquement.

Chaque fichier du dépôt a son analyseur. Les fichiers de détail se joignent aux inscriptions par numéro MLS ; les fichiers de référence par leurs propres codes :

FichierAnalyseurContenu`REMARQUES.TXT``RemarksParser`Descriptions FR/EN`PHOTOS.TXT``PhotosParser`Photos ordonnées, URL media.ashx`ADDENDA.TXT``AddendaParser`Addenda en segments à réassembler`CARACTERISTIQUES.TXT``FeaturesParser`Caractéristiques codées`DEPENSES.TXT``ExpensesParser`Taxes et dépenses`RENOVATIONS.TXT``RenovationsParser`Rénovations déclarées`LIENS_ADDITIONNELS.TXT``AdditionalLinksParser`Visites virtuelles, vidéos`VISITES_LIBRES.TXT``OpenHousesParser`Visites libres planifiées`UNITES_DETAILLEES.TXT``UnitsParser`Unités (principale, logements, intergénération)`PIECES_UNITES.TXT``RoomsParser`Pièces par unité (dimensions, revêtements)`MEMBRES.TXT``BrokersParser`Courtiers (clé : code courtier)`FIRMES.TXT``FirmsParser`Agences (clé : code firme)`BUREAUX.TXT``OfficesParser`Bureaux (clé : code bureau)```
use Yeevy\CentrisPasserelle\Parser\PhotosParser;

foreach ((new PhotosParser())->parseFile('/chemin/vers/PHOTOS.TXT') as $photo) {
    $photo->mlsNumber;      // clé de jointure
    $photo->sequence;       // ordre d'affichage
    $photo->categoryCode;   // FACA = façade, CUI = cuisine, SDB = salle de bain…
    $photo->url;            // https://mediaserver.centris.ca/media.ashx?id=…
}
```

### Positions de colonnes

[](#positions-de-colonnes)

Les positions livrées avec le paquet sont **observées par la communauté** et peuvent varier selon la version de votre entente. Vérifiez-les contre la documentation PDF Passerelle fournie avec **votre** entente, puis surchargez-les au besoin :

```
use Yeevy\CentrisPasserelle\Config\ColumnMap;
use Yeevy\CentrisPasserelle\Parser\ListingsParser;

$columns = ColumnMap::listings()->with([
    'status_code' => 120,   // position vérifiée dans votre documentation
]);

$parser = new ListingsParser($columns);
```

Un logger PSR-3 peut être injecté ; les lignes sans numéro MLS sont journalisées puis ignorées au lieu d'interrompre l'instantané :

```
$parser = new ListingsParser(logger: $monLogger);
```

Si Centris introduit une nouvelle disposition de colonnes, elle sera publiée comme **profil nommé** plutôt qu'en écrasant la carte par défaut : `ColumnMap::listings('2027')` chargera `config/listings-2027.php`, et les profils existants continueront de fonctionner.

### Détection de dérive

[](#détection-de-dérive)

Un changement de structure du flux ne provoque aucune erreur par lui-même — il se manifeste par des données décalées importées silencieusement. Validez l'instantané avant l'import :

```
use Yeevy\CentrisPasserelle\Validation\SnapshotValidator;

$validator = new SnapshotValidator($columns);

// Échantillonne les lignes et vérifie les invariants (numéro MLS numérique,
// format des dates, coordonnées dans les bornes du Québec…).
// Lève ColumnMapMismatch si la structure ne correspond plus à la carte,
// ou si l'instantané est vide (ce qui dépublierait toutes les inscriptions).
$validator->validateFile('/chemin/vers/INSCRIPTIONS.TXT');
```

Les vérifications sont injectables — ajoutez des invariants propres à votre entente ou assouplissez ceux par défaut :

```
new SnapshotValidator($columns, checks: [
    ...SnapshotValidator::defaultChecks(),
    fn (array $row, ColumnMap $columns): ?string => /* votre invariant */ null,
]);
```

### Cycle de vie des inscriptions

[](#cycle-de-vie-des-inscriptions)

SignalInterprétation`EV` (en vigueur)Publier`VE` (vendue)Marquer vendueAbsente de l'instantanéDépublier (étape de réconciliation)### Synchronisation

[](#synchronisation)

Le moteur applique un instantané complet à votre stockage : validation de dérive d'abord (rien n'est écrit si elle échoue), upsert avec saut des lignes inchangées (dirty hash), puis réconciliation des retraits. Le paquet ne touche jamais à une base de données — implémentez `ListingRepository` contre votre propre schéma (Eloquent, PDO, WordPress…) :

```
use Yeevy\CentrisPasserelle\Sync\ListingsSynchronizer;

$synchronizer = new ListingsSynchronizer(
    repository: $monRepository,   // implémente Contracts\ListingRepository
    events: $dispatcherPsr14,     // optionnel — ListingCreated / ListingUpdated / ListingRemoved
);

$result = $synchronizer->sync('/chemin/vers/instantane'); // dossier ou fichier
// $result->created, $result->updated, $result->skipped, $result->removed
```

Pour récupérer l'instantané depuis le compte FTP Passerelle, utilisez `FlysystemFeedSource` (installez `league/flysystem` et `league/flysystem-ftp` ou `-sftp-v3`) :

```
use Yeevy\CentrisPasserelle\Feed\FlysystemFeedSource;

$source = new FlysystemFeedSource($filesystem, localDirectory: '/tmp/centris');

$result = $synchronizer->sync($source);
```

Si votre entente livre l'instantané en archive ZIP, décorez la source — l'extraction se fait sur place (requiert `ext-zip`) :

```
use Yeevy\CentrisPasserelle\Feed\ZipExtractingSource;

$result = $synchronizer->sync(new ZipExtractingSource($source));
```

### Téléchargement des photos

[](#téléchargement-des-photos)

`PhotoDownloader` télécharge les photos via n'importe quel client PSR-18 (p. ex. Guzzle) dans des fichiers adressés par contenu (`{sha256}.jpg`) — les octets identiques ne sont stockés qu'une seule fois :

```
use Yeevy\CentrisPasserelle\Photo\PhotoDownloader;

$downloader = new PhotoDownloader($clientPsr18, $requestFactory, '/chemin/vers/photos');

foreach ($downloader->downloadAll($photosParser->parseFile('/chemin/vers/PHOTOS.TXT')) as $photo) {
    $photo->path;             // /chemin/vers/photos/{sha256}.jpg
    $photo->wasDeduplicated;  // true si les octets existaient déjà
}
```

`download()` lève `PhotoDownloadFailed` ; `downloadAll()` journalise et ignore les échecs pour qu'une URL brisée n'interrompe jamais le lot. La conversion WebP reste une préoccupation de l'application consommatrice.

### Feuille de route

[](#feuille-de-route)

- Enveloppe Laravel : [`yeevy/laravel-centris`](https://github.com/yeevy-ai/laravel-centris) — commande `centris:sync`, événements Laravel, jobs de photos en file d'attente à venir

### Gestion des versions

[](#gestion-des-versions)

Le paquet suit [SemVer](https://semver.org/lang/fr/) via les étiquettes git.

- **0.x** : les positions de colonnes sont observées par la communauté et l'API se stabilise — des ruptures peuvent survenir dans les versions mineures.
- **À partir de 1.0** : correctifs = patch ; nouveaux champs et analyseurs = mineure ; changement d'API = majeure.
- **Cartes de colonnes** : corriger une position par défaut est publié au minimum en version mineure avec une entrée de changelog explicite — le code compile mais les données changent. Une nouvelle disposition Centris devient un nouveau profil nommé, jamais un écrasement du profil existant.

### Tests

[](#tests)

```
composer test      # Pest
composer analyse   # PHPStan niveau 8
composer format    # Pint
```

**Important :** ne commettez jamais de données réelles du flux. Les tests utilisent exclusivement des fixtures synthétiques.

### Licence

[](#licence)

[MIT](LICENSE.md) — © Digital Unity Inc. ([Yeevy](https://yeevy.ai))

---

English
-------

[](#english)

Unofficial open-source PHP client for the **Centris® Passerelle** FTP feed (Quebec MLS listing data distributed to authorized brokers). Parses, syncs, and reconciles listing data.

Pure PHP core with no framework dependency: consumable from a WordPress plugin, Laravel, Symfony, or a bare cron script.

### How the Passerelle feed works

[](#how-the-passerelle-feed-works)

- No public API. The broker signs a diffusion agreement with Centris/QFREB and receives FTP credentials scoped to their own listings.
- Centris drops a **full snapshot** once or twice daily (no deltas): removals are detected by diffing — a listing present in your database but absent from the new file is sold, expired, or withdrawn.
- Delivered files: `INSCRIPTIONS.TXT` (listings master), `REMARQUES.TXT` (FR/EN descriptions), `PHOTOS.TXT`, `ADDENDA.TXT`, plus reference files (brokers, agencies, features, municipalities).
- Format: comma-delimited CSV, quoted fields, **Windows-1252 encoding**, no header row, positional columns (~150), one listing per CRLF line.

### Requirements

[](#requirements)

- PHP 8.2+ with the `mbstring` extension
- A valid Passerelle diffusion agreement (this package ships **no** data)

### Installation

[](#installation-1)

```
composer require yeevy/centris-passerelle
```

### Usage

[](#usage)

```
use Yeevy\CentrisPasserelle\Parser\ListingsParser;
use Yeevy\CentrisPasserelle\Enums\ListingStatus;

$parser = new ListingsParser();

foreach ($parser->parseFile('/path/to/INSCRIPTIONS.TXT') as $listing) {
    $listing->mlsNumber;      // "9999999" — upsert key
    $listing->salePrice;      // 975000 (null for rentals)
    $listing->status;         // ListingStatus::Active | ListingStatus::Sold | null
    $listing->descriptionFr;  // contains HTML
    $listing->descriptionEn;
    $listing->latitude;
    $listing->longitude;
    $listing->dirtyHash;      // sha256 of the raw row — skip unchanged rows on upsert
    $listing->row;            // full raw row for unmapped columns
}
```

Parsing is lazy (generator-based), so large snapshots don't exhaust memory. Windows-1252 → UTF-8 conversion is applied automatically.

Every file in the drop has its own parser. Detail files join to listings by MLS number; reference files by their own codes:

FileParserContent`REMARQUES.TXT``RemarksParser`FR/EN descriptions`PHOTOS.TXT``PhotosParser`Ordered photos, media.ashx URLs`ADDENDA.TXT``AddendaParser`Chunked addenda to reassemble`CARACTERISTIQUES.TXT``FeaturesParser`Coded features`DEPENSES.TXT``ExpensesParser`Taxes and expenses`RENOVATIONS.TXT``RenovationsParser`Declared renovations`LIENS_ADDITIONNELS.TXT``AdditionalLinksParser`Virtual tours, videos`VISITES_LIBRES.TXT``OpenHousesParser`Scheduled open houses`UNITES_DETAILLEES.TXT``UnitsParser`Units (main, rental, intergenerational)`PIECES_UNITES.TXT``RoomsParser`Rooms per unit (dimensions, flooring)`MEMBRES.TXT``BrokersParser`Brokers (key: broker code)`FIRMES.TXT``FirmsParser`Firms (key: firm code)`BUREAUX.TXT``OfficesParser`Offices (key: office code)```
use Yeevy\CentrisPasserelle\Parser\PhotosParser;

foreach ((new PhotosParser())->parseFile('/path/to/PHOTOS.TXT') as $photo) {
    $photo->mlsNumber;      // join key
    $photo->sequence;       // display order
    $photo->categoryCode;   // FACA = façade, CUI = kitchen, SDB = bathroom…
    $photo->url;            // https://mediaserver.centris.ca/media.ashx?id=…
}
```

### Column positions

[](#column-positions)

The positions shipped with the package are **community-observed** and may vary by agreement version. Verify them against the Passerelle PDF documentation that came with **your** agreement, then override as needed:

```
use Yeevy\CentrisPasserelle\Config\ColumnMap;
use Yeevy\CentrisPasserelle\Parser\ListingsParser;

$columns = ColumnMap::listings()->with([
    'status_code' => 120,   // position verified in your documentation
]);

$parser = new ListingsParser($columns);
```

A PSR-3 logger can be injected; rows without an MLS number are logged and skipped instead of aborting the snapshot:

```
$parser = new ListingsParser(logger: $myLogger);
```

If Centris introduces a new column layout, it will ship as a **named profile** rather than overwriting the default map: `ColumnMap::listings('2027')` loads `config/listings-2027.php`, and existing profiles keep working.

### Drift detection

[](#drift-detection)

A feed structure change raises no error by itself — it shows up as shifted data imported silently. Validate the snapshot before importing:

```
use Yeevy\CentrisPasserelle\Validation\SnapshotValidator;

$validator = new SnapshotValidator($columns);

// Samples rows and checks invariants (numeric MLS number, date format,
// coordinates within Quebec bounds…). Throws ColumnMapMismatch when the
// structure no longer lines up with the map, or when the snapshot is
// empty (which would unpublish every listing).
$validator->validateFile('/path/to/INSCRIPTIONS.TXT');
```

Checks are injectable — add per-agreement invariants or relax the defaults:

```
new SnapshotValidator($columns, checks: [
    ...SnapshotValidator::defaultChecks(),
    fn (array $row, ColumnMap $columns): ?string => /* your invariant */ null,
]);
```

### Listing lifecycle

[](#listing-lifecycle)

SignalInterpretation`EV` (en vigueur)Publish`VE` (vendue)Mark soldAbsent from snapshotUnpublish (reconciliation step)### Synchronization

[](#synchronization)

The engine applies a full snapshot to your storage: drift validation first (nothing is written if it fails), upserts with unchanged-row skipping (dirty hash), then removal reconciliation. The package never touches a database — implement `ListingRepository` against your own schema (Eloquent, PDO, WordPress…):

```
use Yeevy\CentrisPasserelle\Sync\ListingsSynchronizer;

$synchronizer = new ListingsSynchronizer(
    repository: $myRepository,    // implements Contracts\ListingRepository
    events: $psr14Dispatcher,     // optional — ListingCreated / ListingUpdated / ListingRemoved
);

$result = $synchronizer->sync('/path/to/snapshot'); // directory or file
// $result->created, $result->updated, $result->skipped, $result->removed
```

To fetch the snapshot from the Passerelle FTP account, use `FlysystemFeedSource` (install `league/flysystem` plus `league/flysystem-ftp` or `-sftp-v3`):

```
use Yeevy\CentrisPasserelle\Feed\FlysystemFeedSource;

$source = new FlysystemFeedSource($filesystem, localDirectory: '/tmp/centris');

$result = $synchronizer->sync($source);
```

If your agreement delivers the snapshot as a ZIP archive, decorate the source — extraction happens in place (requires `ext-zip`):

```
use Yeevy\CentrisPasserelle\Feed\ZipExtractingSource;

$result = $synchronizer->sync(new ZipExtractingSource($source));
```

### Photo downloads

[](#photo-downloads)

`PhotoDownloader` fetches photos through any PSR-18 client (e.g. Guzzle) into content-addressed files (`{sha256}.jpg`) — identical bytes are stored exactly once:

```
use Yeevy\CentrisPasserelle\Photo\PhotoDownloader;

$downloader = new PhotoDownloader($psr18Client, $requestFactory, '/path/to/photos');

foreach ($downloader->downloadAll($photosParser->parseFile('/path/to/PHOTOS.TXT')) as $photo) {
    $photo->path;             // /path/to/photos/{sha256}.jpg
    $photo->wasDeduplicated;  // true when the bytes already existed
}
```

`download()` throws `PhotoDownloadFailed`; `downloadAll()` logs and skips failures so one broken URL never aborts the batch. WebP conversion stays a consumer-application concern.

### Roadmap

[](#roadmap)

- Laravel wrapper: [`yeevy/laravel-centris`](https://github.com/yeevy-ai/laravel-centris) — `centris:sync` command, Laravel events, queued photo jobs upcoming

### Versioning

[](#versioning)

The package follows [SemVer](https://semver.org) via git tags.

- **0.x**: column positions are community-observed and the API is still settling — breaking changes may land in minor versions.
- **From 1.0 on**: fixes = patch; new fields and parsers = minor; API changes = major.
- **Column maps**: correcting a shipped default position is released as at least a minor version with an explicit changelog entry — code still compiles, but data shifts. A new Centris layout becomes a new named profile, never an overwrite of an existing one.

### Testing

[](#testing)

```
composer test      # Pest
composer analyse   # PHPStan level 8
composer format    # Pint
```

**Important:** never commit real feed data. Tests use synthetic fixtures only.

### License

[](#license)

[MIT](LICENSE.md) — © Digital Unity Inc. ([Yeevy](https://yeevy.ai))

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance94

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 84.4% 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

4

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1685702?v=4)[Chris Harrison](/maintainers/digitalunity)[@digitalunity](https://github.com/digitalunity)

---

Top Contributors

[![digitalunityca](https://avatars.githubusercontent.com/u/38138816?v=4)](https://github.com/digitalunityca "digitalunityca (54 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (5 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (5 commits)")

---

Tags

centrisftpimmobilierlaravelmlsphpquebecreal-estateftpmlsreal-estateimmobilierquebecyeevycentrispasserelle

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/yeevy-centris-passerelle/health.svg)

```
[![Health](https://phpackages.com/badges/yeevy-centris-passerelle/health.svg)](https://phpackages.com/packages/yeevy-centris-passerelle)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

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

The Symfony PHP framework

31.4k87.4M2.2k](/packages/symfony-symfony)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.9k](/packages/cakephp-cakephp)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6943.5M450](/packages/drupal-core-recommended)

PHPackages © 2026

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