Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Config Reader Laravel Package

platformsh/config-reader

A PHP library for reading configuration from the Platform.sh environment. It parses platform relationships and variables into usable service credentials and environment settings, helping applications adapt cleanly across deployments and environments.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and instantiate. The package is a thin, framework-agnostic reader over the environment variables Platform.sh injects into your container (PLATFORM_*). There is no service provider — you construct the class where you need it:

composer require platformsh/config-reader
use Platformsh\ConfigReader\Config;

$config = new Config();

if ($config->isAvailable()) {
    // running on Platform.sh
}

isAvailable() is the single most important method. Every integration should be guarded by it so the app still boots on local machines, CI, and other hosts.

First use case: the database connection. In a Laravel app the quickest win is config/database.php, because that file is ordinary PHP and can read the environment directly:

use Platformsh\ConfigReader\Config;

$platform = new Config();

if ($platform->isAvailable() && $creds = $platform->credentials('database')) {
    $mysql = [
        'driver'    => 'mysql',
        'host'      => $creds['host'],
        'port'      => $creds['port'],
        'database'  => $creds['path'],
        'username'  => $creds['username'],
        'password'  => $creds['password'],
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ];
} else {
    $mysql = [ /* your normal env()-based config */ ];
}

credentials('database') is the workhorse accessor: it decodes the base64-encoded PLATFORM_RELATIONSHIPS JSON and returns the first endpoint's host/port/username/password/path/query/scheme/driver as a flat array, which maps cleanly onto Laravel connection keys. Note path is the database name for MySQL/Postgres, not a filesystem path.

Where to look first. Read the class source (it is one file, ~200 lines) rather than only the README. It exposes public properties ($config->relationships, $config->routes, $config->application, $config->environment) alongside helper methods (getRelationship(), getRoutes(), getRoute(), credentials(), getEnvironmentVariable()). Knowing which values are raw arrays versus decoded convenience objects saves a lot of trial and error.

Implementation Patterns

Pattern 1 — configure in config/*.php, not in a provider. Because platform values are effectively immutable for the life of the container, reading them inside config files is safe and keeps your AppServiceProvider clean. Apply the same shape used for the database to Redis, mail, and filesystems:

// config/database.php — Redis
'redis' => [
    'client' => 'phpredis',
    'options' => ['prefix' => env('REDIS_PREFIX', '')],
    'default' => $platform->isAvailable() && ($r = $platform->credentials('redis'))
        ? ['host' => $r['host'], 'port' => $r['port'], 'password' => $r['password'] ?? null, 'database' => 0]
        : ['host' => env('REDIS_HOST', '127.0.0.1'), 'port' => env('REDIS_PORT', 6379), 'password' => env('REDIS_PASSWORD'), 'database' => 0],
],

The same pattern works for mail (SMTP relationship named mail/smtp) and for S3-style storage if you attach an object-storage relationship. Always keep the else branch pointing at env() so local php artisan serve is unaffected.

Pattern 2 — thin wrapper bound in the container. Do not new Config() scattered across the codebase. Wrap it once:

// app/Support/PlatformConfig.php
class PlatformConfig
{
    private Config $config;
    public function __construct() { $this->config = new Config(); }
    public function available(): bool { return $this->config->isAvailable(); }
    public function credentials(string $name): ?array { return $this->config->credentials($name); }
    public function route(string $name): ?array { return $this->config->getRoute($name); }
}

Bind it as a singleton in AppServiceProvider::register(). This gives you a mockable seam for tests and keeps a single import of the vendor namespace.

Pattern 3 — routes and the public URL. Platform.sh generates routes per environment (https://{default}/, https://www.{default}/, and aliases). For canonical URLs, logging links, or generating absolute URLs in queued jobs, resolve the primary route rather than relying on APP_URL:

if ($platform->isAvailable() && ($route = $platform->getRoute('https://{default}/'))) {
    config(['app.url' => rtrim($route['url'] ?? '', '/')]);
}

Do this early (a service provider is fine) so url(), mail templates, and signed URLs agree with the environment domain. Route data is already decoded; do not base64-decode it again.

Pattern 4 — service provider for runtime-only concerns. Some values cannot live in config files because they concern middleware. On Platform.sh TLS terminates at the router, so the app receives plain HTTP with X-Forwarded-* headers. Laravel's TrustProxies middleware (or $middleware->trustProxies() in Laravel 11) must be configured to trust the forwarded headers, otherwise generated URLs and $request->isSecure() are wrong. This is not something the reader does for you — it is the integration step people most often miss.

Pattern 5 — keep a local fallback contract. Treat the reader as an override source rather than the primary config. A useful convention is: config/*.php define env()-driven defaults; a small bootstrapping block overlays Platform.sh credentials only when isAvailable() is true. That single rule makes the app run identically on Heroku, Docker, and a laptop.

Testing pattern. Because the class reads from the process environment, you can unit-test your wrapper by injecting a fake Config (make your wrapper accept ?Config $config = null). For integration tests against the real class, you would have to populate PLATFORM_APPLICATION, PLATFORM_RELATIONSHIPS, PLATFORM_ROUTES in $_ENV before construction — brittle, so prefer wrapping.

Gotchas and Tips

Build vs. runtime. During the build hook the services are not running, so PLATFORM_RELATIONSHIPS is empty or absent. Any code that constructs Config and immediately calls credentials() will get nothing at build time. Guard with isAvailable() at runtime only, or check the build indicator (inBuild() / the PLATFORM_* build variable) before touching relationships. A common failure is a config:cache run during build that bakes empty credentials into the cached config.

php artisan config:cache freezes platform values. Once cached, changing a PLATFORM_* variable on the platform has no effect until you re-run the cache. Either run config:cache in the deploy hook after services are attached, or skip caching for the values that come from the reader. Document which path you chose, because debugging "the credentials are stale" is otherwise painful.

Everything is base64 JSON. PLATFORM_RELATIONSHIPS and PLATFORM_ROUTES are base64-encoded JSON blobs. The reader decodes them for you. If you also read $_ENV['PLATFORM_RELATIONSHIPS'] directly elsewhere, you will be handling base64 yourself — pick one access path and stick to it.

Relationship key names vary by service type. MySQL exposes host/port/username/password/path; Redis typically host/port; some services include scheme, query, fragment, ip, or socket. credentials() normalizes the common case but the underlying getRelationship() array is the source of truth. Check keys with ?? defaults rather than assuming.

The path key is not a filesystem path. For databases it is the database name. It is only a real path for services like elasticsearch/solr or when a relationship endpoint is local-type. This trips people up when writing generic connection mappers.

Writable directories are mounts, not the package's job. Platform.sh mounts are the only writable locations at runtime; /app itself is read-only. Laravel's storage/ and bootstrap/cache/ must be symlinked into a mount defined in .platform.app.yaml (or configured via storage_path()/bootstrapPath()). The reader gives you getEnvironmentVariable('PLATFORM_APP_DIR') if you need the root, but no mount helper — do not expect one.

Local development looks "unavailable," not broken. On a laptop isAvailable() is false and every accessor returns empty/null. If you see a silently empty array in production, first confirm PLATFORM_APPLICATION_NAME (or the equivalent) is actually present — it is easy to lose when shelling into a worker container rather than the web container.

Multiple relationships with the same name. A service can have several endpoints (read replicas, multiple hosts). getRelationship('db') returns the full structure; credentials('db') collapses to the first endpoint. For read/write splitting you need getRelationships()['db'] and to pick endpoints yourself.

Version pinning. The package is small (last release Feb 2025, 22 stars) and its API is stable but slow-moving. Pin a major/compatible version in composer.json; don't rely on dev-main. Since it has no transitive dependents, upgrading has near-zero blast radius — but also means breaking changes are not widely advertised.

Extension points. The class is not an interface, so the only clean override is your own wrapper. Construct it lazily (inside a closure or a service provider) rather than as a class constant or static initializer — static initialization at file-load time will run before Laravel's environment is fully bootstrapped and make mocking impossible.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings