team-mate-pro / infra-bundle
Infrastructure verification bundle for Symfony applications
Package info
github.com/team-mate-pro/infra-bundle
Type:symfony-bundle
pkg:composer/team-mate-pro/infra-bundle
Requires
- php: >=8.3
- symfony/console: ^7.0 || ^8.0
- symfony/framework-bundle: ^7.0 || ^8.0
- symfony/http-client: ^7.0 || ^8.0
- symfony/http-kernel: ^7.0 || ^8.0
- symfony/process: ^7.0 || ^8.0
Requires (Dev)
- phpstan/extension-installer: *
- phpstan/phpstan: *
- phpstan/phpstan-phpunit: *
- phpstan/phpstan-symfony: *
- phpunit/phpunit: ^11.5
- squizlabs/php_codesniffer: ^4.0
- symfony/yaml: ^7.0 || ^8.0
- team-mate-pro/make: ^1.0
Suggests
Provides
None
Conflicts
None
Replaces
None
README
Version: 1.5.0 Type: symfony-bundle
Infrastructure verification bundle for Symfony applications. Provides a base command for verifying server configuration before deployment.
Installation
composer require team-mate-pro/infra-bundle:^1.0
Configuration
Register the bundle in config/bundles.php:
return [ // ... TeamMatePro\InfraBundle\TeamMateProInfraBundle::class => ['all' => true], ];
Usage
Create a verification command by extending AbstractInfraVerifyCommand:
<?php declare(strict_types=1); namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; use TeamMatePro\InfraBundle\Command\AbstractInfraVerifyCommand; #[AsCommand( name: 'app:infra:verify', description: 'Verify infrastructure configuration', )] final class InfraVerifyCommand extends AbstractInfraVerifyCommand { protected function verify(): void { // Add your verification logic here } }
Run the command:
php bin/console app:infra:verify
Available Verifiers
section(string $title)
Creates a visual section in the output.
$this->section('PHP Extensions');
verifyPhpVersion(?string $projectDir = null)
Checks the running PHP version against the require.php constraint from the project's
composer.json. Only the lowest version in the constraint is compared, which covers
>=8.3, ^8.3 and >=8.3 <9.
$this->verifyPhpVersion(); // composer.json in the current working directory $this->verifyPhpVersion($this->projectDir); // explicit project directory
verifyPhpExtension(string $name, ?string $description = null)
Checks if a PHP extension is loaded.
$this->verifyPhpExtension(name: 'pdo_mysql'); $this->verifyPhpExtension(name: 'gd', description: 'image processing');
verifyPhpIniSetting(string $name, string $expectedValue, ?string $description = null)
Checks if a PHP ini setting has the expected value. Normalizes boolean-like values (On/Off/1/0/true/false).
// Sentry - stack trace arguments $this->verifyPhpIniSetting('zend.exception_ignore_args', 'Off', 'Sentry stack trace arguments'); // Memory limit $this->verifyPhpIniSetting('memory_limit', '256M'); // Display errors off in production $this->verifyPhpIniSetting('display_errors', 'Off', 'must be disabled in production');
verifyEnvVariable(string $name, ?string $description = null, ?string $expectedValue = null)
Checks if an environment variable is set. Optionally validates expected value.
// Check if set $this->verifyEnvVariable(name: 'APP_SECRET'); // Check with description $this->verifyEnvVariable(name: 'DATABASE_URL', description: 'database connection'); // Check exact value $this->verifyEnvVariable( name: 'APP_ENV', expectedValue: 'prod', ); // Full example $this->verifyEnvVariable( name: 'SSO_AUTH_URL', description: 'frontend redirects here for login', expectedValue: 'https://login.example.com/', );
verifyBinary(array $command, ?string $description = null)
Checks if a system binary is available and executable.
$this->verifyBinary( command: ['/usr/bin/wkhtmltopdf', '--version'], description: 'PDF generation', ); $this->verifyBinary( command: ['node', '--version'], description: 'Node.js runtime', );
verifyDatabaseConnection(int $timeoutSeconds = 5)
Verifies MySQL database connection using DATABASE_URL environment variable.
Note: If
ext-pdois not loaded, shows[WARN]and skips the check instead of failing.
$this->verifyDatabaseConnection(); $this->verifyDatabaseConnection(timeoutSeconds: 10);
verifyHttpConnection(string $url, ?string $description = null, int $expectedStatusCode = 200, int $timeoutSeconds = 5)
Checks HTTP endpoint and validates response status code.
$this->verifyHttpConnection( url: 'https://api.example.com/health', description: 'API health check', ); $this->verifyHttpConnection( url: 'https://api.example.com/status', expectedStatusCode: 204, timeoutSeconds: 10, );
verifyHttpConnectionHandshake(string $url, ?string $description = null, int $timeoutSeconds = 5)
Checks if HTTP endpoint is reachable (any response is OK). Use this when you only need to verify network connectivity.
$this->verifyHttpConnectionHandshake( url: 'https://login.example.com/oauth2/jwks', description: 'SSO JWKS endpoint', ); $this->verifyHttpConnectionHandshake( url: 'https://external-api.example.com/', description: 'External API', timeoutSeconds: 10, );
verifyCorsHeaders(string $url, string $origin, array $expectedHeaders = ['Access-Control-Allow-Origin'], string $method = 'OPTIONS', array $requestHeaders = [], int $timeoutSeconds = 5)
Probes a URL with an Origin request header and asserts the expected CORS response
headers are present and well-formed. Detects three common misconfigurations: a missing
header, a header returned more than once (e.g. both nginx and the app append it), and a
single header value with merged, comma-separated values. Pass the response headers you
want to assert via $expectedHeaders.
For a preflight check use method: 'OPTIONS' (the default) — the standard
Access-Control-Request-Method/Access-Control-Request-Headers request headers are
added automatically unless overridden via $requestHeaders. For a simple request use
method: 'GET' or 'HEAD'.
// Preflight check for a public form endpoint $this->verifyCorsHeaders( url: 'https://example.com/public/forms/00000000-0000-0000-0000-000000000000', origin: 'https://example.com', ); // Simple request, asserting several CORS headers at once $this->verifyCorsHeaders( url: 'https://example.com/assets/form.js', origin: 'https://example.com', expectedHeaders: [ 'Access-Control-Allow-Origin', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Headers', ], method: 'HEAD', );
verifyEncryptedZipArchive(array $files = [], ?string $description = null)
Verifies that the server can actually produce a password-protected ZIP archive. A loaded
ext-zip proves nothing about encryption — that additionally requires libzip built with
encryption support, and the gap only surfaces when an archive is written. The check packs
the given files with AES-256 and a random password, re-opens the archive and asserts that
no entry is readable without the password and that every entry decrypts to its original
content. Everything happens in a temporary directory that is removed afterwards.
Pass $files as entry name in the archive => path of an existing file. Leave it empty
to pack a self-generated probe pair — enough to prove the capability without touching
application data.
// Capability check only $this->verifyEncryptedZipArchive(); // Pack real documents, e.g. the attachments a mailing feature ships $this->verifyEncryptedZipArchive( files: [ 'referral.pdf' => '/var/www/app/var/samples/referral.pdf', 'attachments.csv' => '/var/www/app/var/samples/attachments.csv', ], description: 'Email attachment archive', );
Example Command
<?php declare(strict_types=1); namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; use TeamMatePro\InfraBundle\Command\AbstractInfraVerifyCommand; #[AsCommand( name: 'app:infra:verify', description: 'Verify infrastructure configuration', )] final class InfraVerifyCommand extends AbstractInfraVerifyCommand { protected function verify(): void { $this->section('PHP Extensions'); $this->verifyPhpExtension(name: 'pdo_mysql', description: 'database'); $this->verifyPhpExtension(name: 'intl', description: 'internationalization'); $this->section('Environment Variables'); $this->verifyEnvVariable(name: 'APP_ENV', expectedValue: 'prod'); $this->verifyEnvVariable(name: 'APP_SECRET'); $this->verifyEnvVariable(name: 'DATABASE_URL'); $this->section('System Binaries'); $this->verifyBinary( command: ['/usr/bin/wkhtmltopdf', '--version'], description: 'PDF generation', ); $this->section('Services'); $this->verifyDatabaseConnection(); $this->section('External Connections'); $this->verifyHttpConnectionHandshake( url: 'https://api.example.com/', description: 'External API', ); } }
Output Example
Infrastructure Verification
===========================
PHP Extensions
--------------
[OK] ext-pdo_mysql (database)
[OK] ext-intl (internationalization)
Environment Variables
---------------------
[OK] APP_ENV
[OK] APP_SECRET
[OK] DATABASE_URL
System Binaries
---------------
[OK] /usr/bin/wkhtmltopdf (PDF generation)
Services
--------
[OK] MySQL connection
External Connections
--------------------
[OK] https://api.example.com/ (External API)
[OK] All infrastructure checks passed
Requirements
- PHP 8.3+ (tested on 8.3, 8.4 and 8.5)
- Symfony 7.0+
Development
This package follows the TeamMatePro quality standards: PSR-12 (PHP_CodeSniffer),
PHPStan at max level, and a PHPUnit test suite. Everything runs in Docker, so no
local PHP extensions are required.
# Start / stop the dev container make start make stop # Run the full CI gate locally (phpcs + phpstan + unit tests) make check # Auto-fix coding-standard issues make fix
The same checks are also available as Composer scripts and are what CI runs via the
shared GitLab templates (docker-compose.gitlab.yml):
composer phpcs # PSR-12 coding standard composer phpstan # static analysis (level max) composer tests:unit # PHPUnit unit suite
Layout
| Path | Purpose |
|---|---|
src/ |
bundle source (AbstractInfraVerifyCommand, the bundle class) |
tests/Unit/ |
unit tests mirroring src/ |
tests/_Data/ |
test fixtures (the concrete command subclass, HTTP router) |
tests/App/ |
minimal kernel used by container-aware tests |
docker/, docker-compose*.yml |
local + CI containers |
phpcs.xml, phpstan.neon, phpunit.xml |
quality-tool configuration |