├── .editorconfig ├── .github └── workflows │ └── quality-assurance.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── BuildTimeVariableAccessException.php ├── Config.php ├── NoCredentialFormatterFoundException.php └── NotValidPlatformException.php └── tests ├── ConfigTest.php ├── bootstrap.php └── valid ├── ENV.json ├── ENV_runtime.json ├── PLATFORM_APPLICATION.json ├── PLATFORM_RELATIONSHIPS.json ├── PLATFORM_ROUTES.json └── PLATFORM_VARIABLES.json /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.github/workflows/quality-assurance.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Quality Assurance 3 | on: 4 | push: ~ 5 | pull_request: ~ 6 | 7 | jobs: 8 | phpunit: 9 | name: PHPUnit tests on ${{ matrix.php }} ${{ matrix.composer-flags }} 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | php: ['7.4', '8.0', '8.1', '8.2', '8.3' ] 14 | composer-flags: [ '' ] 15 | phpunit-flags: [ '--coverage-text' ] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php }} 21 | coverage: xdebug 22 | tools: composer:v2 23 | - run: composer update --no-progress ${{ matrix.composer-flags }} 24 | - run: vendor/bin/phpunit ${{ matrix.phpunit-flags }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.1] - 2019-11-04 4 | 5 | ### Added 6 | 7 | * `CHANGELOG` added. 8 | * `onDedicated` method that determines if the current environment is a Platform.sh Dedicated environment. Replaces deprecated `onEnterprise` method. 9 | 10 | ### Changed 11 | 12 | * Deprecates `onEnterprise` method - which is for now made to wrap around the added `onDedicated` method. `onEnterprise` **will be removed** in a future release, so update your projects to use `onDedicated` instead as soon as possible. 13 | 14 | ## [2.3.0] - 2019-09-19 15 | 16 | ### Added 17 | 18 | * `getPrimaryRoute` method for accessing routes marked "primary" in `routes.yaml`. 19 | * `getUpstreamRoutes` method returns an object map that includes only those routes that point to a valid upstream. 20 | 21 | ## [2.2.2] - 2019-04-29 22 | 23 | ### Changed 24 | 25 | * Updates `routes` method to use `routesDef` instead of `variablesDef` while checking if routes are defined in a Platform.sh environment. 26 | * Updates `getRoute` method documentation in `README`. 27 | 28 | ### Removed 29 | 30 | * Guard on the `variables` method. 31 | 32 | ## [2.2.1] - 2019-04-25 33 | 34 | ### Changed 35 | 36 | * Improved the error handling for missing property variables. 37 | 38 | ## [2.2.0] - 2019-04-24 39 | 40 | ### Changed 41 | 42 | * Route URL addition moved to constructor. 43 | * Switch to more permissive checking of variable availability. 44 | 45 | ## [2.1.0] - 2019-03-22 46 | 47 | ### Added 48 | 49 | * Adds `hasRelationship` method, which determines if a relationship is defined, and thus has credentials available. 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Platform.sh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Platform.sh Config Reader (PHP) 2 | 3 | ![Quality Assurance](https://github.com/platformsh/config-reader-php/workflows/Quality%20Assurance/badge.svg) 4 | 5 | This library provides a streamlined and easy to use way to interact with a Platform.sh environment. It offers utility methods to access routes and relationships more cleanly than reading the raw environment variables yourself. 6 | 7 | This library requires PHP 7.4 or later. 8 | 9 | ## Install 10 | 11 | ```bash 12 | composer require platformsh/config-reader 13 | ``` 14 | 15 | ## Usage Example 16 | 17 | Example: 18 | 19 | ```php 20 | use Platformsh\ConfigReader\Config; 21 | 22 | $config = new Config(); 23 | 24 | if (!$config->isValidPlatform()) { 25 | die("Not in a Platform.sh Environment."); 26 | } 27 | 28 | $credentials = $config->credentials('database'); 29 | 30 | $conn = new \PDO($config->formattedCredentials('database', 'pdo_mysql'), $credentials['username'], $credentials['password'], [ 31 | // Always use Exception error mode with PDO, as it's more reliable. 32 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, 33 | // So we don't have to mess around with cursors and unbuffered queries by default. 34 | \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, 35 | // Make sure MySQL returns all matched rows on update queries including 36 | // rows that actually didn't have to be updated because the values didn't 37 | // change. This matches common behavior among other database systems. 38 | \PDO::MYSQL_ATTR_FOUND_ROWS => TRUE, 39 | ]); 40 | 41 | // Do stuff with the $conn here. 42 | ``` 43 | 44 | ## API Reference 45 | 46 | ### Create a config object 47 | 48 | ```php 49 | use Platformsh\ConfigReader\Config; 50 | 51 | $config = new Config(); 52 | ``` 53 | 54 | `config` is now a `Platformsh\ConfigReder\Config` object that provides access to the Platform.sh environment. 55 | 56 | The `isValidPlatform()` method returns `true` if the code is running in a context that has Platform.sh environment variables defined. If it returns `false` then most other functions will throw exceptions if used. 57 | 58 | ### Inspect the environment 59 | 60 | The following methods return `true` or `false` to help determine in what context the code is running: 61 | 62 | ```php 63 | $config->inBuild(); 64 | 65 | $config->inRuntime(); 66 | 67 | $config->onDedicated(); 68 | 69 | $config->onProduction(); 70 | ``` 71 | 72 | > **Note:** 73 | > 74 | > Platform.sh will no longer refer to its [99.99% uptime SLA product](https://platform.sh/solutions/) as "Enterprise", but rather as "Dedicated". Configuration Reader libraries have in turn been updated to include an `onDedicated` method to replace `onEnterprise`. For now `onEnterprise` remains available. It now calls the new method and no breaking changes have been introduced. 75 | > 76 | > It is recommended that you update your projects to use `onDedicated` as soon as possible, as `onEnterprise` will be removed in a future version of this library. 77 | 78 | ### Read environment variables 79 | 80 | The following magic properties return the corresponding environment variable value. See the [Platform.sh documentation](https://docs.platform.sh/development/variables.html) for a description of each. 81 | 82 | The following are available both in Build and at Runtime: 83 | 84 | ```php 85 | $config->applicationName; 86 | 87 | $config->appDir; 88 | 89 | $config->project; 90 | 91 | $config->treeId; 92 | 93 | $config->projectEntropy; 94 | ``` 95 | 96 | The following are available only if `inRuntime()` returned `true`: 97 | 98 | ```php 99 | $config->branch; 100 | 101 | $config->documentRoot; 102 | 103 | $config->smtpHost; 104 | 105 | $config->environment; 106 | 107 | $config->socket; 108 | 109 | $config->port; 110 | ``` 111 | 112 | ### Reading service credentials 113 | 114 | [Platform.sh services](https://docs.platform.sh/configuration/services.html) are defined in a `services.yaml` file, and exposed to an application by listing a `relationship` to that service in the application's `.platform.app.yaml` file. User, password, host, etc. information is then exposed to the running application in the `PLATFORM_RELATIONSHIPS` environment variable, which is a base64-encoded JSON string. The following method allows easier access to credential information than decoding the environment variable yourself. 115 | 116 | ```php 117 | $creds = $config->credentials('database'); 118 | ``` 119 | 120 | The return value of `credentials()` is an associative array matching the relationship JSON object, which includes the appropriate user, password, host, database name, and other pertinent information. See the [Service documentation](https://docs.platform.sh/configuration/services.html) for your service for the exact structure and meaning of each property. In most cases that information can be passed directly to whatever other client library is being used to connect to the service. 121 | 122 | To make sure that a relationship is defined before you try to access credentials out of it, use the `hasRelationship()` method: 123 | 124 | ```php 125 | if ($config->hasRelationship('database')) { 126 | $creds = $config->credentials('database'); 127 | // ... 128 | } 129 | ``` 130 | 131 | ## Formatting service credentials 132 | 133 | In some cases the library being used to connect to a service wants its credentials formatted in a specific way; it could be a DSN string of some sort or it needs certain values concatenated to the database name, etc. For those cases you can use "Credential Formatters". A Credential Formatter is any `callable` (function, anonymous function, object method, etc.) that takes a credentials array and returns any type, since the library may want different types. 134 | 135 | Credential Formatters can be registered on the configuration object, and a few are included out of the box. That allows 3rd party libraries to ship their own formatters that can be easily integrated into the `Config` object to allow easier use. 136 | 137 | ```php 138 | function formatMyService(array $credentials) string 139 | { 140 | return "some string based on $credentials"; 141 | } 142 | 143 | // Call this in setup. 144 | $config->registerFormatter("my_service", 'formatMyService'); 145 | 146 | 147 | // Then call this method to get the formatted version 148 | 149 | $formatted = $config->FormattedCredentials("database", "my_service"); 150 | ``` 151 | 152 | The first parameter is the name of a relationship defined in `.platform.app.yaml`. The second is a formatter that was previously registered with `registerFormatter()`. If either the service or formatter is missing an exception will be thrown. The type of `formatted` will depend on the formatter function and can be safely passed directly to the client library. 153 | 154 | Two formatters are included out of the box: 155 | 156 | * `pdo_mysql` returns a DSN appropriate for using with `PDO` to connect to MySQL or MariaDB. Note that `PDO` will still need the username and password from the credentials array passed as separate parameters. 157 | * `pdo_pgsql` returns a DSN appropriate for using with `PDO` to connect to PostgreSQL. Note that `PDO` will still need the username and password from the credentials array passed as separate parameters. 158 | 159 | ### Reading Platform.sh variables 160 | 161 | Platform.sh allows you to define arbitrary variables that may be available at build time, runtime, or both. They are stored in the `PLATFORM_VARIABLES` environment variable, which is a base64-encoded JSON string. 162 | 163 | The following two methods allow access to those values from your code without having to bother decoding the values yourself: 164 | 165 | ```php 166 | $config->variables(); 167 | ``` 168 | 169 | This method returns an associative array of all variables defined. Usually this method is not necessary and `$config->variable()` is preferred. 170 | 171 | ```php 172 | $config->variable("foo", "default"); 173 | ``` 174 | 175 | This method looks for the "foo" variable. If found, it is returned. If not, the optional second parameter is returned as a default. 176 | 177 | ### Reading Routes 178 | 179 | [Routes](https://docs.platform.sh/configuration/routes.html) on Platform.sh define how a project will handle incoming requests; that primarily means what application container will serve the request, but it also includes cache configuration, TLS settings, etc. Routes may also have an optional ID, which is the preferred way to access them. 180 | 181 | ```php 182 | $config->getRoute("main"); 183 | ``` 184 | 185 | The `getRoute()` method takes a single string for the route ID ("main" in this case) and returns the corresponding route array. If the route is not found it will throw an exception. 186 | 187 | To access all routes, or to search for a route that has no ID, the `routes()` method returns an associative array of routes keyed by their URL. That mirrors the structure of the `PLATFORM_ROUTES` environment variable. 188 | 189 | If called in the build phase an exception is thrown. 190 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platformsh/config-reader", 3 | "description": "Small helper to access Platform.sh environment variables", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Larry Garfield", 8 | "email": "larry@platform.sh" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=7.4" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "^8.5" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Platformsh\\ConfigReader\\": "src" 20 | } 21 | }, 22 | "autoload-dev": { 23 | "psr-4": { 24 | "Platformsh\\ConfigReader\\": "tests" 25 | } 26 | }, 27 | "config": { 28 | "platform": { 29 | "php": "7.4" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/ 6 | 7 | 8 | 9 | 10 | ./src/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/BuildTimeVariableAccessException.php: -------------------------------------------------------------------------------- 1 | variableName) or !empty($config->variableName). Attempting to 15 | * access a nonexistent variable will throw an exception. 16 | * 17 | * // These properties are available at build time and run time. 18 | * 19 | * @property-read string $project 20 | * The project ID. 21 | * @property-read string $applicationName 22 | * The name of the application, as defined in its configuration. 23 | * @property-read string $treeId 24 | * An ID identifying the application tree before it was built: a unique hash 25 | * is generated based on the contents of the application's files in the 26 | * repository. 27 | * @property-read string $appDir 28 | * The absolute path to the application. 29 | * @property-read string $projectEntropy 30 | * A random string generated for each project, useful for generating hash keys. 31 | * 32 | * // These properties are only available at runtime. 33 | * 34 | * @property-read string $branch 35 | * The Git branch name. 36 | * @property-read string $environment 37 | * The environment ID (usually the Git branch plus a hash). 38 | * @property-read string $environmentType 39 | * The environmentType (prod, staging, dev) 40 | * @property-read string $documentRoot 41 | * The absolute path to the web root of the application. 42 | * @property-read string $smtpHost 43 | * The hostname of the Platform.sh default SMTP server (an empty string if 44 | * emails are disabled on the environment). 45 | * @property-read string $port 46 | * The TCP port number the application should listen to for incoming requests. 47 | * @property-read string $socket 48 | * The Unix socket the application should listen to for incoming requests. 49 | * 50 | */ 51 | class Config 52 | { 53 | 54 | /** 55 | * Local index of the variables that can be accessed as direct properties (build and runtime). 56 | * 57 | * The key is the property that will be read. The value is the environment variable, minus 58 | * prefix, that contains the value to look up. 59 | * 60 | * @var array 61 | */ 62 | protected $directVariables = [ 63 | 'project' => 'PROJECT', 64 | 'appDir' => 'APP_DIR', 65 | 'applicationName' => 'APPLICATION_NAME', 66 | 'treeId' => 'TREE_ID', 67 | 'projectEntropy' => 'PROJECT_ENTROPY', 68 | ]; 69 | 70 | /** 71 | * Local index of the variables that can be accessed as direct properties (runtime only). 72 | * 73 | * The key is the property that will be read. The value is the environment variable, minus 74 | * prefix, that contains the value to look up. 75 | * 76 | * @var array 77 | */ 78 | protected $directVariablesRuntime = [ 79 | 'branch' => 'BRANCH', 80 | 'environment' => 'ENVIRONMENT', 81 | 'environmentType' => 'ENVIRONMENT_TYPE', 82 | 'documentRoot' => 'DOCUMENT_ROOT', 83 | 'smtpHost' => 'SMTP_HOST', 84 | ]; 85 | 86 | protected $unPrefixedVariablesRuntime = [ 87 | 'port' => 'PORT', 88 | 'socket' => 'SOCKET', 89 | ]; 90 | 91 | /** 92 | * A local copy of all environment variables as of when the object was initialized. 93 | * @var array 94 | */ 95 | protected $environmentVariables = []; 96 | 97 | /** 98 | * The vendor prefix for all environment variables we care about. 99 | * 100 | * @var string 101 | */ 102 | protected $envPrefix = ''; 103 | 104 | /** 105 | * The routes definition array. 106 | * 107 | * Only available at runtime. 108 | * 109 | * @var array 110 | */ 111 | protected $routesDef = []; 112 | 113 | /** 114 | * The relationships definition array. 115 | * 116 | * Only available at runtime. 117 | * 118 | * @var array 119 | */ 120 | protected $relationshipsDef = []; 121 | 122 | /** 123 | * The variables definition array. 124 | * 125 | * Available in both build and runtime, although possibly with different values. 126 | * 127 | * @var array 128 | */ 129 | protected $variablesDef = []; 130 | 131 | /** 132 | * The application definition array. 133 | * 134 | * This is, approximately, the .platform.app.yaml file in nested array form. 135 | * 136 | * @var array 137 | */ 138 | protected $applicationDef = []; 139 | 140 | /** 141 | * A map of formatter name strings to callable formatters. 142 | * 143 | * @var array 144 | */ 145 | protected $credentialFormatters = []; 146 | 147 | /** 148 | * Constructs a Config object. 149 | * 150 | * @param array|null $environmentVariables 151 | * The environment variables to read. Defaults to the current environment. 152 | * @param string $envPrefix 153 | * The prefix for environment variables. Defaults to 'PLATFORM_'. 154 | */ 155 | public function __construct(?array $environmentVariables = null, string $envPrefix = 'PLATFORM_') 156 | { 157 | $this->environmentVariables = $environmentVariables ?? getenv(); 158 | $this->envPrefix = $envPrefix; 159 | 160 | if ($routes = $this->getValue('ROUTES')) { 161 | $this->routesDef = $this->decode($routes); 162 | foreach ($this->routesDef as $url => $route) { 163 | $this->routesDef[$url]['url'] = $url; 164 | } 165 | } 166 | if ($relationships = $this->getValue('RELATIONSHIPS')) { 167 | $this->relationshipsDef = $this->decode($relationships); 168 | } 169 | 170 | if ($variables = $this->getValue('VARIABLES')) { 171 | $this->variablesDef = $this->decode($variables); 172 | } 173 | if ($application = $this->getValue('APPLICATION')) { 174 | $this->applicationDef = $this->decode($application); 175 | } 176 | 177 | $this->registerFormatter('pdo_mysql', [$this, 'pdoMySQLFormatter']); 178 | $this->registerFormatter('pdo_pgsql', [$this, 'pdoPostgreSQLFormatter']); 179 | } 180 | 181 | /** 182 | * Checks whether the code is running on a platform with valid environment variables. 183 | * 184 | * @return bool 185 | * True if configuration can be used, false otherwise. 186 | */ 187 | public function isValidPlatform() : bool 188 | { 189 | return (bool)$this->getValue('APPLICATION_NAME'); 190 | } 191 | 192 | /** 193 | * Checks whether the code is running in a build environment. 194 | * 195 | * If false, it's running at deploy time. 196 | * 197 | * @return bool 198 | */ 199 | public function inBuild() : bool 200 | { 201 | return $this->isValidPlatform() && !$this->getValue('ENVIRONMENT'); 202 | } 203 | 204 | /** 205 | * Checks whether the code is running in a runtime environment. 206 | * 207 | * @return bool 208 | */ 209 | public function inRuntime() : bool 210 | { 211 | return $this->isValidPlatform() && $this->getValue('ENVIRONMENT'); 212 | } 213 | 214 | /** 215 | * Retrieves the credentials for accessing a relationship. 216 | * 217 | * The relationship must be defined in the .platform.app.yaml file. 218 | * 219 | * @param string $relationship 220 | * The relationship name as defined in .platform.app.yaml. 221 | * @param int $index 222 | * The index within the relationship to access. This is always 0, but reserved 223 | * for future extension. 224 | * @return array 225 | * The credentials array for the service pointed to by the relationship. 226 | * @throws BuildTimeVariableAccessException 227 | * Thrown if called in a in the build phase, where relationships are not defined. 228 | * @throws \InvalidArgumentException 229 | * If the relationship/index pair requested does not exist. 230 | */ 231 | public function credentials(string $relationship, int $index = 0) : array 232 | { 233 | 234 | if (empty($this->relationshipsDef)) { 235 | if ($this->inBuild()) { 236 | throw new BuildTimeVariableAccessException('Relationships are not available during the build phase.'); 237 | } 238 | throw new NotValidPlatformException('No relationships are defined. Are you sure you are on Platform.sh?' 239 | . ' If you\'re running on your local system you may need to create a tunnel' 240 | . ' to access your environment services. See https://docs.platform.sh/gettingstarted/local/tethered.html'); 241 | } 242 | 243 | if (empty($this->relationshipsDef[$relationship])) { 244 | throw new \InvalidArgumentException(sprintf('No relationship defined: %s. Check your .platform.app.yaml file.', $relationship)); 245 | } 246 | if (empty($this->relationshipsDef[$relationship][$index])) { 247 | throw new \InvalidArgumentException(sprintf('No index %d defined for relationship: %s. Check your .platform.app.yaml file.', $index, $relationship)); 248 | } 249 | 250 | return $this->relationshipsDef[$relationship][$index]; 251 | } 252 | 253 | /** 254 | * Returns a variable from the VARIABLES array. 255 | * 256 | * Note: variables prefixed with `env:` can be accessed as normal environment variables. 257 | * This method will return such a variable by the name with the prefix still included. 258 | * Generally it's better to access those variables directly. 259 | * 260 | * @param string $name 261 | * The name of the variable to retrieve. 262 | * @param mixed $default 263 | * The default value to return if the variable is not defined. Defaults to null. 264 | * @return mixed 265 | * The value of the variable, or the specified default. This may be a string or an array. 266 | */ 267 | public function variable(string $name, $default = null) 268 | { 269 | return $this->variablesDef[$name] ?? $default; 270 | } 271 | 272 | /** 273 | * Returns the full variables array. 274 | * 275 | * If you're looking for a specific variable, the variable() method is a more robust option. 276 | * This method is for cases where you want to scan the whole variables list looking for a pattern. 277 | * 278 | * @return array 279 | * The full variables array. 280 | */ 281 | public function variables() : array 282 | { 283 | // It's valid for there to be no variables defined at all, so there's no guard 284 | // for missing values. 285 | return $this->variablesDef; 286 | } 287 | 288 | /** 289 | * Returns the routes definition. 290 | * 291 | * @return array 292 | * The routes array, in PHP nested array form. 293 | * @throws BuildTimeVariableAccessException 294 | * If the routes are not accessible due to being in the wrong environment. 295 | */ 296 | public function routes() : array 297 | { 298 | if ($this->inBuild()) { 299 | throw new BuildTimeVariableAccessException('Routes are not available during the build phase.'); 300 | } 301 | if (empty($this->routesDef)) { 302 | throw new NotValidPlatformException('No routes are defined. Are you sure you are running on Platform.sh?'); 303 | } 304 | 305 | return $this->routesDef; 306 | } 307 | 308 | /** 309 | * Returns the primary route. 310 | * 311 | * The primary route is the one marked primary in `routes.yaml`, or else 312 | * the first non-redirect route in that file if none are marked. 313 | * 314 | * @return array 315 | * The route definition. The generated URL of the route is added as a "url" key. 316 | */ 317 | public function getPrimaryRoute() : array 318 | { 319 | foreach ($this->routes() as $url => $route) { 320 | if ($route['primary'] == true) { 321 | return $route; 322 | } 323 | } 324 | 325 | throw new \InvalidArgumentException(sprintf('No primary route found. This isn\'t supposed to happen.')); 326 | } 327 | 328 | /** 329 | * Returns just those routes that point to a valid upstream. 330 | * 331 | * This method is similar to routes(), but filters out redirect routes that are rarely 332 | * useful for app configuration. If desired it can also filter to just those routes 333 | * whose upstream is a given application name. To retrieve routes that point to the 334 | * current application where the code is being run, use: 335 | * 336 | * $routes = $config->getUpstreamRoutes($config->applicationName); 337 | * 338 | * @param string|null $appName 339 | * The name of the upstream app on which to filter, if any. 340 | * @return array 341 | * An array of route definitions. 342 | */ 343 | public function getUpstreamRoutes(?string $appName = null) : array 344 | { 345 | return array_filter($this->routes(), function (array $route) use ($appName) { 346 | return $route['type'] == 'upstream' 347 | // On Dedicated, the upstream name sometimes is `app:http` instead of just `app`. 348 | // If no name is specified then don't bother checking. 349 | && (is_null($appName) || $appName == explode(':', $route['upstream'])[0]); 350 | }); 351 | } 352 | 353 | /** 354 | * Returns a single route definition. 355 | * 356 | * Note: If no route ID was specified in routes.yaml then it will not be possible 357 | * to look up a route by ID. 358 | * 359 | * @param string $id 360 | * The ID of the route to load. 361 | * @return array 362 | * The route definition. The generated URL of the route is added as a "url" key. 363 | * @throws \InvalidArgumentException 364 | * If there is no route by that ID, an exception is thrown. 365 | */ 366 | public function getRoute(string $id) : array 367 | { 368 | foreach ($this->routes() as $url => $route) { 369 | if ($route['id'] == $id) { 370 | return $route; 371 | } 372 | } 373 | 374 | throw new \InvalidArgumentException(sprintf('No such route id found: %s', $id)); 375 | } 376 | 377 | /** 378 | * Returns the application definition array. 379 | * 380 | * This is, approximately, the .platform.app.yaml file as a nested array. However, it also 381 | * has other information added by Platform.sh as part of the build and deploy process. 382 | * 383 | * @return array 384 | * The application definition array. 385 | */ 386 | public function application() : array 387 | { 388 | if (empty($this->applicationDef)) { 389 | throw new NotValidPlatformException('No application definition is available. Are you sure you are running on Platform.sh?'); 390 | } 391 | 392 | return $this->applicationDef; 393 | } 394 | 395 | /** 396 | * Determines if the current environment is a Platform.sh Dedicated environment. 397 | * 398 | * @return bool 399 | * True on an Dedicated environment, False otherwise. 400 | */ 401 | public function onDedicated() : bool 402 | { 403 | return $this->isValidPlatform() && $this->getValue('MODE') == 'enterprise'; 404 | } 405 | 406 | /** 407 | * Determines if the current environment is a Platform.sh Dedicated environment. 408 | * 409 | * @deprecated 410 | * 411 | * The Platform.sh "Enterprise" will soon be referred to exclusively as 412 | * "Dedicated". the `onEnterprise` method remains available for now, but it 413 | * will be removed in a future version of this library. 414 | * 415 | * It is recommended that you update your projects to use `onDedicated` as 416 | * soon as possible. 417 | * 418 | * @return bool 419 | * True on an Dedicated environment, False otherwise. 420 | */ 421 | public function onEnterprise() : bool 422 | { 423 | return $this->onDedicated(); 424 | } 425 | 426 | /** 427 | * Determines if the current environment is a production environment. 428 | * 429 | * Note: There may be a few edge cases where this is not entirely correct on Dedicated, 430 | * if the production branch is not named `production`. In that case you'll need to use 431 | * your own logic. 432 | * 433 | * @return bool 434 | * True if the environment is a production environment, false otherwise. 435 | * It will also return false if not running on Platform.sh or in the build phase. 436 | */ 437 | public function onProduction() : bool 438 | { 439 | if (!$this->inRuntime()) { 440 | return false; 441 | } 442 | 443 | // we need to first confirm that we actually have the `ENVIRONMENT_TYPE` variable, 444 | // because not all legacy containers will have this 445 | if($this->hasVariable('ENVIRONMENT_TYPE')) { 446 | // correct way of checking production branch 447 | return $this->getValue('ENVIRONMENT_TYPE') == 'production'; 448 | } else { 449 | // legacy way of checking production type 450 | $prodBranch = $this->onDedicated() ? 'production' : 'master'; 451 | return $this->getValue('BRANCH') == $prodBranch; 452 | } 453 | } 454 | 455 | /** 456 | * Adds a credential formatter to the configuration. 457 | * 458 | * A credential formatter is responsible for formatting the credentials for a relationship 459 | * in a way expected by a particular client library. For instance, it can take the credentials 460 | * from Platform.sh for a PostgreSQL database and format them into a URL string expected by 461 | * PDO. Use the formattedCredentials() method to get the formatted version of a particular 462 | * relationship. 463 | * 464 | * @param string name 465 | * The name of the formatter. This may be any arbitrary alphanumeric string. 466 | * @param {registerFormatterCallback} formatter 467 | * A callback function that will format relationship credentials for a specific client library. 468 | * @return Config 469 | * The called object, for chaining. 470 | */ 471 | public function registerFormatter(string $name, callable $formatter) : self 472 | { 473 | $this->credentialFormatters[$name] = $formatter; 474 | return $this; 475 | } 476 | 477 | /** 478 | * Returns credentials for the specified relationship as formatted by the specified formatter. 479 | * 480 | * @param string relationship 481 | * The relationship whose credentials should be formatted. 482 | * @param string formatter 483 | * The registered formatter to use. This must match a formatter previously registered 484 | * with registerFormatter(). 485 | * @return mixed 486 | * The credentials formatted with the given formatter. 487 | * @throws NoCredentialFormatterFoundException 488 | */ 489 | public function formattedCredentials(string $relationship, string $formatter) 490 | { 491 | if (empty($this->credentialFormatters[$formatter])) { 492 | throw new NoCredentialFormatterFoundException(sprintf('There is no credential formatter named "%s" registered. Did you remember to call registerFormatter()?', $formatter)); 493 | } 494 | 495 | return $this->credentialFormatters[$formatter]($this->credentials($relationship)); 496 | } 497 | 498 | /** 499 | * Determines if a relationship is defined, and thus has credentials available. 500 | * 501 | * @param string $relationship 502 | * The name of the relationship to check. 503 | * @return bool 504 | * True if the relationship is defined, false otherwise. 505 | */ 506 | public function hasRelationship(string $relationship) : bool 507 | { 508 | return isset($this->relationshipsDef[$relationship]); 509 | } 510 | 511 | /** 512 | * Check if an environment variable exists, useful for backwards compatibility checks 513 | * 514 | * @param string $name 515 | * The env variable to check. 516 | * @return bool 517 | */ 518 | protected function hasVariable(string $name) :?bool 519 | { 520 | $checkName = $this->envPrefix . strtoupper($name); 521 | 522 | return array_key_exists($checkName, $this->environmentVariables); 523 | } 524 | /** 525 | * Reads an environment variable, taking the prefix into account. 526 | * 527 | * @param string $name 528 | * The variable to read. 529 | * @return string|null 530 | */ 531 | protected function getValue(string $name) : ?string 532 | { 533 | $checkName = $this->envPrefix . strtoupper($name); 534 | /* 535 | * If enable_smtp is to `false` for the project, then the envPrefix.SMTP_HOST environmental variable is not 536 | * included in the environment. Therefore, if the environmental variable isn't set, return an empty string, not 537 | * a null 538 | */ 539 | if('SMTP_HOST' === $name) { 540 | return $this->environmentVariables[$checkName] ?? ""; 541 | } 542 | return $this->environmentVariables[$checkName] ?? null; 543 | } 544 | 545 | /** 546 | * Decodes a Platform.sh environment variable. 547 | * 548 | * @param string $variable 549 | * Base64-encoded JSON (the content of an environment variable). 550 | * 551 | * @throws \Exception if there is a JSON decoding error. 552 | * 553 | * @return mixed 554 | * An associative array (if representing a JSON object), or a scalar type. 555 | */ 556 | protected function decode($variable) 557 | { 558 | $result = json_decode(base64_decode($variable), true); 559 | if (json_last_error()) { 560 | throw new \Exception( 561 | sprintf('Error decoding JSON, code: %d', json_last_error()) 562 | ); 563 | } 564 | 565 | return $result; 566 | } 567 | 568 | /** 569 | * Gets a configuration property. 570 | * 571 | * @param string $property 572 | * A (magic) property name. The properties are documented in the DocBlock 573 | * for this class. 574 | * 575 | * @throws \Exception if a variable is not found, or if decoding fails. 576 | * 577 | * @return mixed 578 | * The return types are documented in the DocBlock for this class. 579 | */ 580 | public function __get($property) 581 | { 582 | // For now, all unprefixed variables are also runtime variables. If that ever changes this 583 | // logic will change with it. 584 | $isBuildVar = in_array($property, array_keys($this->directVariables)); 585 | $isRuntimeVar = in_array($property, array_keys($this->directVariablesRuntime)); 586 | $isUnprefixedVar = in_array($property, array_keys($this->unPrefixedVariablesRuntime)); 587 | 588 | if (!($isBuildVar || $isUnprefixedVar || $isRuntimeVar)) { 589 | throw new \InvalidArgumentException(sprintf('No such variable defined: %s', $property)); 590 | } 591 | 592 | $value = $this->getPropertyValue($property); 593 | 594 | if (is_null($value)) { 595 | if ($this->inBuild() && !$isBuildVar) { 596 | throw new BuildTimeVariableAccessException(sprintf('The %s variable is not available during build time.', $property)); 597 | } 598 | throw new NotValidPlatformException(sprintf('The %s variable is not defined. Are you sure you\'re running on Platform.sh?', $property)); 599 | } 600 | 601 | return $value; 602 | } 603 | 604 | /** 605 | * Checks whether a configuration property is set. 606 | * 607 | * @param string $property 608 | * A (magic) property name. 609 | * 610 | * @return bool 611 | * True if the property exists and is not null, false otherwise. 612 | */ 613 | public function __isset($property) 614 | { 615 | $value = $this->getPropertyValue($property); 616 | 617 | return !is_null($value); 618 | } 619 | 620 | /** 621 | * Returns the value of a dynamic property, whatever it's configuration. 622 | * 623 | * @param string $property 624 | * The property value to get. 625 | * @return string|null 626 | */ 627 | protected function getPropertyValue(string $property) : ?string 628 | { 629 | // For now, all unprefixed variables are also runtime variables. If that ever changes this 630 | // logic will change with it. 631 | $isBuildVar = in_array($property, array_keys($this->directVariables)); 632 | $isRuntimeVar = in_array($property, array_keys($this->directVariablesRuntime)); 633 | $isUnprefixedVar = in_array($property, array_keys($this->unPrefixedVariablesRuntime)); 634 | 635 | if ($isBuildVar) { 636 | $value = $this->getValue($this->directVariables[$property]); 637 | } 638 | else if ($isUnprefixedVar) { 639 | $value = $this->environmentVariables[$this->unPrefixedVariablesRuntime[$property]] ?? null; 640 | } 641 | else if ($isRuntimeVar) { 642 | $value = $this->getValue($this->directVariablesRuntime[$property]); 643 | } 644 | else { 645 | $value = null; 646 | } 647 | 648 | return $value; 649 | } 650 | 651 | /** 652 | * Returns a DSN for a PDO-MySQL connection. 653 | * 654 | * Note that the username and password will still be needed separately in the PDO constructor. 655 | * 656 | * @param array $credentials 657 | * The credentials array from the relationships. 658 | * @return string 659 | * A formatted PDO DSN. 660 | */ 661 | protected function pdoMySqlFormatter(array $credentials) : string 662 | { 663 | return sprintf('mysql:host=%s;port=%d;dbname=%s', $credentials['host'], $credentials['port'], $credentials['path']); 664 | } 665 | 666 | /** 667 | * Returns a DSN for a PDO-PostgreSQL connection. 668 | * 669 | * Note that the username and password will still be needed separately in the PDO constructor. 670 | * 671 | * @param array $credentials 672 | * The credentials array from the relationships. 673 | * @return string 674 | * A formatted PDO DSN. 675 | */ 676 | protected function pdoPostgreSqlFormatter(array $credentials) : string 677 | { 678 | return sprintf('pgsql:host=%s;port=%d;dbname=%s', $credentials['host'], $credentials['port'], $credentials['path']); 679 | } 680 | 681 | } 682 | -------------------------------------------------------------------------------- /src/NoCredentialFormatterFoundException.php: -------------------------------------------------------------------------------- 1 | loadJsonFile('ENV'); 28 | 29 | // These sub-values are always encoded. 30 | foreach (['PLATFORM_APPLICATION', 'PLATFORM_VARIABLES'] as $item) { 31 | $env[$item] = $this->encode($this->loadJsonFile($item)); 32 | } 33 | 34 | $this->mockEnvironmentBuild = $env; 35 | 36 | // These sub-values are always encoded. 37 | foreach (['PLATFORM_ROUTES', 'PLATFORM_RELATIONSHIPS'] as $item) { 38 | $env[$item] = $this->encode($this->loadJsonFile($item)); 39 | } 40 | 41 | $envRuntime = $this->loadJsonFile('ENV_runtime'); 42 | $env = array_merge($env, $envRuntime); 43 | 44 | $this->mockEnvironmentDeploy = $env; 45 | } 46 | 47 | protected function loadJsonFile(string $name) : array 48 | { 49 | return json_decode(file_get_contents("tests/valid/{$name}.json"), true); 50 | } 51 | 52 | public function test_not_on_platform_returns_correctly() : void 53 | { 54 | $config = new Config(); 55 | 56 | $this->assertFalse($config->isValidPlatform()); 57 | } 58 | 59 | public function test_on_platform_returns_correctly_in_runtime() : void 60 | { 61 | $config = new Config($this->mockEnvironmentDeploy); 62 | 63 | $this->assertTrue($config->isValidPlatform()); 64 | } 65 | 66 | public function test_on_platform_returns_correctly_in_build() : void 67 | { 68 | $config = new Config($this->mockEnvironmentBuild); 69 | 70 | $this->assertTrue($config->isValidPlatform()); 71 | } 72 | 73 | public function test_inbuild_in_build_phase_is_true() : void 74 | { 75 | $config = new Config($this->mockEnvironmentBuild); 76 | 77 | $this->assertTrue($config->inBuild()); 78 | } 79 | 80 | public function test_inbuild_in_deploy_phase_is_false() : void 81 | { 82 | $config = new Config($this->mockEnvironmentDeploy); 83 | 84 | $this->assertFalse($config->inBuild()); 85 | } 86 | 87 | public function test_inruntime_in_runtime_is_true() : void 88 | { 89 | $config = new Config($this->mockEnvironmentDeploy); 90 | 91 | $this->assertTrue($config->inRuntime()); 92 | } 93 | 94 | public function test_inruntime_in_build_phase_is_false() : void 95 | { 96 | $config = new Config($this->mockEnvironmentBuild); 97 | 98 | $this->assertFalse($config->inRuntime()); 99 | } 100 | 101 | public function test_load_routes_in_runtime_works() : void 102 | { 103 | $config = new Config($this->mockEnvironmentDeploy); 104 | 105 | $routes = $config->routes(); 106 | 107 | $this->assertTrue(is_array($routes)); 108 | } 109 | 110 | public function test_load_routes_in_build_fails() : void 111 | { 112 | $this->expectException(BuildTimeVariableAccessException::class); 113 | 114 | $config = new Config($this->mockEnvironmentBuild); 115 | $routes = $config->routes(); 116 | } 117 | 118 | public function test_get_route_by_id_works() : void 119 | { 120 | $config = new Config($this->mockEnvironmentDeploy); 121 | 122 | $route = $config->getRoute('main'); 123 | 124 | $this->assertEquals('https://www.{default}/', $route['original_url']); 125 | } 126 | 127 | public function test_get_non_existent_route_throws_exception() : void 128 | { 129 | $this->expectException(\InvalidArgumentException::class); 130 | 131 | $config = new Config($this->mockEnvironmentDeploy); 132 | 133 | $route = $config->getRoute('missing'); 134 | } 135 | 136 | public function test_primary_route_returns_correct_route() : void 137 | { 138 | $config = new Config($this->mockEnvironmentDeploy); 139 | 140 | $route = $config->getPrimaryRoute(); 141 | 142 | $this->assertEquals('https://www.{default}/', $route['original_url']); 143 | $this->assertEquals('main', $route['id']); 144 | $this->assertTrue($route['primary']); 145 | } 146 | 147 | public function test_upstream_routes() : void 148 | { 149 | $config = new Config($this->mockEnvironmentDeploy); 150 | 151 | $routes = $config->getUpstreamRoutes(); 152 | 153 | $this->assertCount(3, $routes); 154 | $this->assertArrayHasKey('https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/', $routes); 155 | $this->assertEquals('https://www.{default}/', $routes['https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/']['original_url']); 156 | } 157 | 158 | public function test_upstream_routes_for_app() : void 159 | { 160 | $config = new Config($this->mockEnvironmentDeploy); 161 | 162 | $routes = $config->getUpstreamRoutes('app'); 163 | 164 | $this->assertCount(2, $routes); 165 | $this->assertArrayHasKey('https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/', $routes); 166 | $this->assertEquals('https://www.{default}/', $routes['https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/']['original_url']); 167 | } 168 | 169 | public function test_upstream_routes_for_app_on_dedicated() : void 170 | { 171 | $env = $this->mockEnvironmentDeploy; 172 | // Simulate a Dedicated-style upstream name. 173 | $routeData = $this->loadJsonFile('PLATFORM_ROUTES'); 174 | $routeData['https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/']['upstream'] = 'app:http'; 175 | $env['PLATFORM_ROUTES'] = $this->encode($routeData); 176 | 177 | $config = new Config($env); 178 | 179 | $routes = $config->getUpstreamRoutes('app'); 180 | 181 | $this->assertCount(2, $routes); 182 | $this->assertArrayHasKey('https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/', $routes); 183 | $this->assertEquals('https://www.{default}/', $routes['https://www.master-7rqtwti-gcpjkefjk4wc2.us-2.platformsh.site/']['original_url']); 184 | } 185 | 186 | public function test_ondedicated_returns_true_on_dedicated() : void 187 | { 188 | $env = $this->mockEnvironmentDeploy; 189 | $env['PLATFORM_MODE'] = 'enterprise'; 190 | $config = new Config($env); 191 | 192 | $this->assertTrue($config->onDedicated()); 193 | } 194 | 195 | public function test_ondedicated_returns_false_on_standard() : void 196 | { 197 | $env = $this->mockEnvironmentDeploy; 198 | $config = new Config($env); 199 | 200 | $this->assertFalse($config->onDedicated()); 201 | } 202 | 203 | public function test_onproduction_prod_is_true() : void 204 | { 205 | $env = $this->mockEnvironmentDeploy; 206 | $env['PLATFORM_ENVIRONMENT_TYPE'] = 'production'; 207 | $config = new Config($env); 208 | 209 | $this->assertTrue($config->onProduction()); 210 | } 211 | 212 | public function test_onproduction_stg_is_false() : void 213 | { 214 | $env = $this->mockEnvironmentDeploy; 215 | $env['PLATFORM_ENVIRONMENT_TYPE'] = 'staging'; 216 | $config = new Config($env); 217 | 218 | $this->assertFalse($config->onProduction()); 219 | } 220 | 221 | public function test_onproduction_on_dedicated_prod_is_true() : void 222 | { 223 | $env = $this->mockEnvironmentDeploy; 224 | $env['PLATFORM_MODE'] = 'enterprise'; 225 | $env['PLATFORM_BRANCH'] = 'production'; 226 | $config = new Config($env); 227 | 228 | $this->assertTrue($config->onProduction()); 229 | } 230 | 231 | public function test_onproduction_on_dedicated_stg_is_false() : void 232 | { 233 | $env = $this->mockEnvironmentDeploy; 234 | $env['PLATFORM_MODE'] = 'enterprise'; 235 | $env['PLATFORM_BRANCH'] = 'staging'; 236 | $config = new Config($env); 237 | 238 | $this->assertFalse($config->onProduction()); 239 | 240 | } 241 | 242 | public function test_onproduction_on_standard_prod_is_true() : void 243 | { 244 | $env = $this->mockEnvironmentDeploy; 245 | $env['PLATFORM_BRANCH'] = 'master'; 246 | $config = new Config($env); 247 | 248 | $this->assertTrue($config->onProduction()); 249 | } 250 | 251 | public function test_onproduction_on_standard_stg_is_false() : void 252 | { 253 | // The fixture has a non-master branch set by default. 254 | $env = $this->mockEnvironmentDeploy; 255 | $config = new Config($env); 256 | 257 | $this->assertFalse($config->onProduction()); 258 | } 259 | 260 | public function test_credentials_existing_relationship_returns() : void 261 | { 262 | $env = $this->mockEnvironmentDeploy; 263 | $config = new Config($env); 264 | 265 | $creds = $config->credentials('database'); 266 | 267 | $this->assertEquals('mysql', $creds['scheme']); 268 | $this->assertEquals('mysql:10.2', $creds['type']); 269 | } 270 | 271 | public function test_credentials_missing_relationship_throws() : void 272 | { 273 | $this->expectException(\InvalidArgumentException::class); 274 | 275 | $env = $this->mockEnvironmentDeploy; 276 | $config = new Config($env); 277 | 278 | $creds = $config->credentials('does-not-exist'); 279 | } 280 | 281 | public function test_credentials_missing_relationship_index_throws() : void 282 | { 283 | $this->expectException(\InvalidArgumentException::class); 284 | 285 | $env = $this->mockEnvironmentDeploy; 286 | $config = new Config($env); 287 | 288 | $creds = $config->credentials('database', 3); 289 | } 290 | 291 | public function test_credentials_works_in_local() : void 292 | { 293 | $env = $this->mockEnvironmentDeploy; 294 | unset($env['PLATFORM_APPLICATION'], $env['PLATFORM_ENVIRONMENT'], $env['PLATFORM_BRANCH']); 295 | $config = new Config($env); 296 | 297 | $creds = $config->credentials('database'); 298 | 299 | $this->assertEquals('mysql', $creds['scheme']); 300 | $this->assertEquals('mysql:10.2', $creds['type']); 301 | } 302 | 303 | public function test_hasRelationship_returns_true_for_existing_relationship() : void 304 | { 305 | $env = $this->mockEnvironmentDeploy; 306 | $config = new Config($env); 307 | 308 | $this->assertTrue($config->hasRelationship('database')); 309 | } 310 | 311 | public function test_hasRelationship_returns_false_for_missingrelationship() : void 312 | { 313 | $env = $this->mockEnvironmentDeploy; 314 | $config = new Config($env); 315 | 316 | $this->assertFalse($config->hasRelationship('missing')); 317 | } 318 | 319 | public function test_reading_existing_variable_works() : void 320 | { 321 | $env = $this->mockEnvironmentDeploy; 322 | $config = new Config($env); 323 | 324 | $this->assertEquals('someval', $config->variable('somevar')); 325 | } 326 | 327 | public function test_reading_missing_variable_returns_default() : void 328 | { 329 | $env = $this->mockEnvironmentDeploy; 330 | $config = new Config($env); 331 | 332 | $this->assertEquals('default-val', $config->variable('missing', 'default-val')); 333 | } 334 | 335 | public function test_variables_returns_on_platform() : void 336 | { 337 | $env = $this->mockEnvironmentDeploy; 338 | $config = new Config($env); 339 | 340 | $vars = $config->variables(); 341 | 342 | $this->assertEquals('someval', $vars['somevar']); 343 | } 344 | 345 | public function test_build_property_in_build_exists() : void 346 | { 347 | $env = $this->mockEnvironmentBuild; 348 | $config = new Config($env); 349 | 350 | $this->assertEquals('/app', $config->appDir); 351 | $this->assertEquals('app', $config->applicationName); 352 | $this->assertEquals('test-project', $config->project); 353 | $this->assertEquals('abc123', $config->treeId); 354 | $this->assertEquals('def789', $config->projectEntropy); 355 | 356 | $this->assertTrue(isset($config->appDir)); 357 | $this->assertTrue(isset($config->applicationName)); 358 | $this->assertTrue(isset($config->project)); 359 | $this->assertTrue(isset($config->treeId)); 360 | $this->assertTrue(isset($config->projectEntropy)); 361 | } 362 | 363 | public function test_build_and_deploy_properties_in_deploy_exists() : void 364 | { 365 | $env = $this->mockEnvironmentDeploy; 366 | $config = new Config($env); 367 | 368 | $this->assertEquals('/app', $config->appDir); 369 | $this->assertEquals('app', $config->applicationName); 370 | $this->assertEquals('test-project', $config->project); 371 | $this->assertEquals('abc123', $config->treeId); 372 | $this->assertEquals('def789', $config->projectEntropy); 373 | 374 | $this->assertEquals('feature-x', $config->branch); 375 | $this->assertEquals('feature-x-hgi456', $config->environment); 376 | $this->assertEquals('/app/web', $config->documentRoot); 377 | $this->assertEquals('1.2.3.4', $config->smtpHost); 378 | $this->assertEquals('8080', $config->port); 379 | $this->assertEquals('unix://tmp/blah.sock', $config->socket); 380 | 381 | $this->assertTrue(isset($config->appDir)); 382 | $this->assertTrue(isset($config->applicationName)); 383 | $this->assertTrue(isset($config->project)); 384 | $this->assertTrue(isset($config->treeId)); 385 | $this->assertTrue(isset($config->projectEntropy)); 386 | 387 | $this->assertTrue(isset($config->branch)); 388 | $this->assertTrue(isset($config->environment)); 389 | $this->assertTrue(isset($config->documentRoot)); 390 | $this->assertTrue(isset($config->smtpHost)); 391 | $this->assertTrue(isset($config->port)); 392 | $this->assertTrue(isset($config->socket)); 393 | } 394 | 395 | public function test_build_and_deploy_properties_mocked_in_local_exists() : void 396 | { 397 | $env = $this->mockEnvironmentDeploy; 398 | unset($env['PLATFORM_APPLICATION'], $env['PLATFORM_ENVIRONMENT'], $env['PLATFORM_BRANCH']); 399 | $config = new Config($env); 400 | 401 | $this->assertEquals('/app', $config->appDir); 402 | $this->assertEquals('app', $config->applicationName); 403 | $this->assertEquals('test-project', $config->project); 404 | $this->assertEquals('abc123', $config->treeId); 405 | $this->assertEquals('def789', $config->projectEntropy); 406 | 407 | $this->assertEquals('/app/web', $config->documentRoot); 408 | $this->assertEquals('1.2.3.4', $config->smtpHost); 409 | $this->assertEquals('8080', $config->port); 410 | $this->assertEquals('unix://tmp/blah.sock', $config->socket); 411 | 412 | $this->assertTrue(isset($config->appDir)); 413 | $this->assertTrue(isset($config->applicationName)); 414 | $this->assertTrue(isset($config->project)); 415 | $this->assertTrue(isset($config->treeId)); 416 | $this->assertTrue(isset($config->projectEntropy)); 417 | 418 | $this->assertTrue(isset($config->documentRoot)); 419 | $this->assertTrue(isset($config->smtpHost)); 420 | $this->assertTrue(isset($config->port)); 421 | $this->assertTrue(isset($config->socket)); 422 | } 423 | 424 | public function test_deploy_property_in_build_throws() : void 425 | { 426 | $this->expectException(BuildTimeVariableAccessException::class); 427 | 428 | $env = $this->mockEnvironmentBuild; 429 | $config = new Config($env); 430 | 431 | $this->assertFalse(isset($config->branch)); 432 | 433 | $branch = $config->branch; 434 | } 435 | 436 | public function test_missing_property_throws_in_build() : void 437 | { 438 | $this->expectException(\InvalidArgumentException::class); 439 | 440 | $env = $this->mockEnvironmentBuild; 441 | $config = new Config($env); 442 | 443 | $this->assertFalse(isset($config->missing)); 444 | 445 | $branch = $config->missing; 446 | } 447 | 448 | public function test_missing_property_throws_in_deploy() : void 449 | { 450 | $this->expectException(\InvalidArgumentException::class); 451 | 452 | $env = $this->mockEnvironmentDeploy; 453 | $config = new Config($env); 454 | 455 | $this->assertFalse(isset($config->missing)); 456 | 457 | $branch = $config->missing; 458 | } 459 | 460 | public function test_application_array_available() : void 461 | { 462 | $env = $this->mockEnvironmentDeploy; 463 | $config = new Config($env); 464 | 465 | $app = $config->application(); 466 | 467 | $this->assertEquals('php:7.2', $app['type']); 468 | } 469 | 470 | public function test_invalid_json_throws() : void 471 | { 472 | $this->expectException(\Exception::class); 473 | $this->expectExceptionMessage('Error decoding JSON, code: 4'); 474 | 475 | $config = new Config([ 476 | 'PLATFORM_APPLICATION_NAME' => 'app', 477 | 'PLATFORM_ENVIRONMENT' => 'test-environment', 478 | 'PLATFORM_VARIABLES' => base64_encode('{some-invalid-json}'), 479 | ]); 480 | } 481 | 482 | public function test_custom_prefix_works() : void 483 | { 484 | $config = new Config(['FAKE_APPLICATION_NAME' => 'test-application'], 'FAKE_'); 485 | $this->assertTrue($config->isValidPlatform()); 486 | } 487 | 488 | public function test_formattedCredentials_throws_when_no_formatter_defined() : void 489 | { 490 | $this->expectException(NoCredentialFormatterFoundException::class); 491 | 492 | $env = $this->mockEnvironmentDeploy; 493 | $config = new Config($env); 494 | 495 | $config->formattedCredentials('database', 'not-defined'); 496 | } 497 | 498 | public function test_formattedCredentials_calls_a_formatter() : void 499 | { 500 | $env = $this->mockEnvironmentDeploy; 501 | $config = new Config($env); 502 | 503 | $config->registerFormatter('test', function(array $credentials) { 504 | return 'called'; 505 | }); 506 | 507 | $formatted = $config->formattedCredentials('database', 'test'); 508 | 509 | $this->assertEquals('called', $formatted); 510 | } 511 | 512 | public function test_pdomysql_formatter() : void 513 | { 514 | $env = $this->mockEnvironmentDeploy; 515 | $config = new Config($env); 516 | 517 | $formatted = $config->formattedCredentials('database', 'pdo_mysql'); 518 | 519 | $this->assertEquals('mysql:host=database.internal;port=3306;dbname=main', $formatted); 520 | } 521 | 522 | /** 523 | * @param mixed $value 524 | * 525 | * @return string 526 | */ 527 | protected function encode($value) : string 528 | { 529 | return base64_encode(json_encode($value)); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |