diff --git a/.github/workflows/php-qa.yml b/.github/workflows/php-qa.yml index 38c96c7..8a44714 100644 --- a/.github/workflows/php-qa.yml +++ b/.github/workflows/php-qa.yml @@ -6,11 +6,19 @@ on: - '**workflows/php-qa.yml' - '**.php' - '**templates/**' - - '**phpcs.xml.dist' + - '**phpcs.xml.dist' - '**phpunit.xml.dist' - '**psalm.xml' - '**composer.json' pull_request: + paths: + - '**workflows/php-qa.yml' + - '**.php' + - '**templates/**' + - '**phpcs.xml.dist' + - '**phpunit.xml.dist' + - '**psalm.xml' + - '**composer.json' workflow_dispatch: concurrency: @@ -19,9 +27,9 @@ concurrency: jobs: - static-qa: + coding-styles: runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'skip qa') }} + if: ${{ !contains(github.event.head_commit.message, 'skip cs') }} steps: - name: Checkout @@ -30,11 +38,11 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none tools: cs2pr - - name: Setup Composer aut for GitHub + - name: Setup Composer auth for GitHub run: composer config github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} - name: Install dependencies @@ -45,10 +53,7 @@ jobs: - name: Annotate code styles for PRs if: ${{ github.event_name == 'pull_request' }} - run: cs2pr --graceful-warnings phpcs-report.xml - - - name: Check Psalm - run: ./vendor/bin/psalm ${{ ((github.event_name == 'pull_request') && '--output-format=github ') || '' }} --no-suggestions --find-unused-psalm-suppress --no-diff --no-cache --no-file-cache + run: cs2pr --graceful-warnings phpcs-report.xm unit-tests: runs-on: ubuntu-latest @@ -58,28 +63,27 @@ jobs: strategy: fail-fast: false matrix: - php-ver: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ] - composer-ver: [ '^1', '~2.0.0', '~2.1.0', '~2.2.0', '~2.3.0', '~2.4.0', '~2.5.0' ] + php-ver: [ '7.4', '8.0', '8.1', '8.2' ] + composer-ver: [ '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6' ] + dependency-versions: [ 'lowest', 'highest' ] exclude: - - php-ver: '7.1' - composer-ver: '~2.3.0' - - php-ver: '7.1' - composer-ver: '~2.4.0' - - php-ver: '7.1' - composer-ver: '~2.5.0' - php-ver: '8.1' - composer-ver: '^1' + composer-ver: '2.0' + - php-ver: '8.1' + composer-ver: '2.1' - php-ver: '8.1' - composer-ver: '~2.0.0' + composer-ver: '2.2' - php-ver: '8.2' - composer-ver: '^1' + composer-ver: '2.0' - php-ver: '8.2' - composer-ver: '~2.0.0' + composer-ver: '2.1' - php-ver: '8.2' - composer-ver: '~2.1.0' + composer-ver: '2.2' + - php-ver: '8.2' + composer-ver: '2.3' steps: - name: Update "USE_COVERAGE" env var based on matrix - if: ${{ matrix.php-ver == '7.4' && matrix.composer-ver == '~2.3.0' }} + if: ${{ (matrix.php-ver == '8.0') && (matrix.dependency-versions == 'highest') && (matrix.composer-ver == '2.5') }} run: echo "USE_COVERAGE=yes" >> $GITHUB_ENV - name: Checkout @@ -96,17 +100,19 @@ jobs: - name: Adjust Composer dependencies run: | composer remove --dev --no-update "roave/security-advisories" - composer require --dev --no-update "composer/composer:${{ matrix.composer-ver }}" + composer require --dev --no-update "composer/composer:~${{ matrix.composer-ver }}.0" - name: Install dependencies uses: ramsey/composer-install@v2 with: - dependency-versions: highest + dependency-versions: ${{ matrix.dependency-versions }} + + - name: Check Psalm + if: ${{ matrix.dependency-versions == 'highest' }} + run: ./vendor/bin/psalm ${{ ((github.event_name == 'pull_request') && '--output-format=github ') || '' }} --no-suggestions --no-diff --no-cache --no-file-cache --php-version=${{ matrix.php-ver }} - name: Run unit tests - run: | - ./vendor/bin/phpunit --atleast-version 9 && ./vendor/bin/phpunit --migrate-configuration || echo 'Config does not need updates.' - ./vendor/bin/phpunit ${{ ((env.USE_COVERAGE == 'yes') && '--coverage-html=coverage-report') || '--no-coverage' }} + run: ./vendor/bin/phpunit ${{ ((env.USE_COVERAGE == 'yes') && '--coverage-html=coverage-report') || '--no-coverage' }} - name: Upload coverage report uses: actions/upload-artifact@v3 @@ -121,7 +127,7 @@ jobs: strategy: fail-fast: true matrix: - php-ver: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ] + php-ver: [ '7.4', '8.0', '8.1', '8.2' ] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/README.md b/README.md index dca1df9..93b9459 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,22 @@ WP Starter is the easiest and fastest way to bootstrap WordPress sites entirely # System Requirements - - PHP 7.1+ - - [Composer](https://getcomposer.org/) >= 1.10 + - PHP 7.4+ + - [Composer](https://getcomposer.org/) >= 2.0 ### Composer - PHP Support Table -| Composer ↓ / PHP → | 7.1 | 7.2 | 7.3 | 7.4 | 8.0 | 8.1 | 8.2 | -| :----------------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | -| 1.10 | ✔ | ✔ | ✔ | ✔ | ✔ | - | - | -| 2.0 | ✔ | ✔ | ✔ | ✔ | ✔ | - | - | -| 2.1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | - | -| 2.2 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.3 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.4 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.5 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| Composer ↓ / PHP → | 7.4 | 8.0 | 8.1 | 8.2 | +|:------------------:|:---:|:---:|:---:|:---:| +| 2.0 | ✔ | ✔ | - | - | +| 2.1 | ✔ | ✔ | - | - | +| 2.2 | ✔ | ✔ | - | - | +| 2.3 | ✔ | ✔ | ✔ | - | +| 2.4 | ✔ | ✔ | ✔ | ✔ | +| 2.5 | ✔ | ✔ | ✔ | ✔ | +| 2.6 | ✔ | ✔ | ✔ | ✔ | diff --git a/composer.json b/composer.json index 4a13ac1..d1e44a8 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,17 @@ "issues": "https://github.com/wecodemore/wpstarter/issues", "source": "https://github.com/wecodemore/wpstarter" }, + "repositories": [ + { + "type": "composer", + "url": "https://raw.githubusercontent.com/inpsyde/wp-stubs/main", + "only": [ + "inpsyde/wp-stubs-versions" + ] + } + ], "require": { - "php": ">=7.1 < 8.3", + "php": ">=7.4 < 8.3", "ext-curl": "*", "ext-dom": "*", "ext-filter": "*", @@ -36,20 +45,20 @@ "ext-mbstring": "*", "ext-mysqli": "*", "ext-SPL": "*", - "composer-plugin-api": "^1.10 || ^2", - "composer/installers": "^1.12.0 || ^2", - "symfony/dotenv": "^3.4 || ^5 || ^6 || ^7" + "composer-plugin-api": "^2", + "composer/installers": "^2", + "symfony/dotenv": "^5 || ^6 || ^7" }, "require-dev": { "roave/security-advisories": "dev-latest", "composer/package-versions-deprecated": "^1.11.99", - "composer/composer": "^2.5.4", - "symfony/process": "^3.4.47 || ^5.4.19 || ^6 || ^7", - "wp-cli/wp-cli": "^2.7.1", - "inpsyde/php-coding-standards": "^1.0.0", - "vimeo/psalm": "^4.30.0 || ^5", - "phpunit/phpunit": "^7.5.20 || ^9.6.3", - "mockery/mockery": "^1.3.6", + "composer/composer": "^2.6.5", + "symfony/process": "^5.4.19 || ^6 || ^7", + "wp-cli/wp-cli": "^v2.9.0", + "inpsyde/php-coding-standards": "^2@dev", + "vimeo/psalm": "^5.15.0", + "phpunit/phpunit": "^9.6.13", + "mockery/mockery": "^1.6.6", "mikey179/vfsstream": "^1.6.11" }, "autoload": { @@ -83,7 +92,8 @@ "qa": [ "@cs", "@psalm", - "@tests:unit:no-cov" + "@tests:unit:no-cov", + "@tests:integration" ] }, "extra": { diff --git a/docs/01-Introduction.md b/docs/01-Introduction.md index 2bbf17d..6d53f07 100644 --- a/docs/01-Introduction.md +++ b/docs/01-Introduction.md @@ -64,8 +64,8 @@ Of course, this is the bare minimum. WP Starter is quite powerful and flexible a ## Requirements -- PHP 7.1+ -- Composer +- PHP 7.4+ +- Composer 2.0+ ------ diff --git a/docs/02-Environment-Variables.md b/docs/02-Environment-Variables.md index 8519c27..0da5303 100644 --- a/docs/02-Environment-Variables.md +++ b/docs/02-Environment-Variables.md @@ -68,7 +68,23 @@ In PHP there are two functions: [`getevn`](http://php.net/manual/en/function.get There's nothing in PHP core that parse env files, but is no surprise that there are different libraries to do that. -WP Starter uses one of these libraries: the **[Symfony Dotenv Component](https://symfony.com/doc/3.4/components/dotenv.html).** +WP Starter uses one of these libraries: the **[Symfony Dotenv Component](https://symfony.com/components/Dotenv).** + + + +### About `putenv()` and `getenv()` + +These two PHP functions are problematic as they are not thread safe. So **recent versions of WP Starter** (as well as recent version of Symfony Dotenv Component) **do not use `putenv()` by default** to store in the environment the values loaded via `.env` files. + +Even if `.env` files are discouraged in production where thread safety is important, we still prefer to have a safe behavior by default and not use `putenv()`. This means that environment variables loaded from WP Starter `.env` files will not be available to `getenv()`. + +There are different ways those values could be read instead: + +- Because WP Starter converts environment variables to constants (after converting to the correct type), one approach is to access values constants. +- Directly access super globals: `$_ENV['var_name'] ?? $_SERVER['var_name'] ?? null`. +- Make use of WP Starter helper, like ` WeCodeMore\WpStarter\Env\Helpers::readVar('var_name');` This approach has the benefit of obtaining "filtered values". E. g. obtaining the intended type instead of always a string. + +If any of the above can be used for some reason, it is still possible to let WP Starter use `putenv()` to load environments by setting the `use-putenv` configuration to `true`. @@ -82,6 +98,8 @@ Moreover, **if the "actual environment" contains all the variables WP Starter an Configuring WP Starter to **bypass** the loading of env files is accomplished via the **`WPSTARTER_ENV_LOADED`** env variable, which when set tells WP Starter to assume all variables are in the actual environment. + + ### Important security note about `.env` file WP Starter loads an `.env` file found in the project root folder, and it is worth noting that if no additional configuration is made, project root is also the folder assumed as webroot for the project. @@ -121,7 +139,7 @@ If there's a plugin that supports a constant like `"AWESOME_PLUGIN_CONFIG"`, by So you might need to write code like: ```php -$config = getenv('AWESOME_PLUGIN_CONFIG'); +$config = $_ENV['AWESOME_PLUGIN_CONFIG'] ?? $_SERVER['AWESOME_PLUGIN_CONFIG'] ?? null; if ($config) { define('AWESOME_PLUGIN_CONFIG', $config); } @@ -131,6 +149,8 @@ There are many places in which such code can be placed, for example a MU plugin. Alternatively WP Starter can be instructed to do something like this automatically. + + ### Let WP Starter define constants for custom env vars WP Starter supports an environment variable called `WP_STARTER_ENV_TO_CONST` containing a list of of comma-separated environment variables to be turned into constants: @@ -165,6 +185,8 @@ As described above, all WordPress configuration constants are natively supported Moreover, there are a few env variables that have a special meaning for WP Starter. + + ### DB check env vars During its bootstrap process, before doing any operation on the system, WP Starter checks if the environment is already setup for database connection. @@ -181,6 +203,8 @@ The three env vars are registered in the order they are listed above: if one is Sometime it might be desirable to bypass this WP Starter check and there's a way to accomplish that via the `skip-db-check` setting. Learn more about that in the [_"WP-Starter-Configuration"_ chapter](04-WP-Starter-Configuration.md). + + ### WordPress Configuration Those are a few env vars that are used in `wp-config.php` that WP Starter generates. @@ -226,9 +250,7 @@ The filter is executed very late (so could be added in MU plugins, plugins and e For example, to only allow cache in production a code like the following can be used: ```php -add_filter('wpstarter.skip-cache-env', function ($skip, $envName) { - return $skip || $envName !== 'production'; -}); +add_filter('wpstarter.skip-cache-env', fn ($skip, $envName) => $skip || $envName !== 'production'); ``` diff --git a/docs/09-Settings-Cheat-Sheet.md b/docs/09-Settings-Cheat-Sheet.md index f2494d5..bf0ee58 100644 --- a/docs/09-Settings-Cheat-Sheet.md +++ b/docs/09-Settings-Cheat-Sheet.md @@ -7,35 +7,36 @@ nav_order: 9 Alphabetically ordered: -| Key | Description | Default value | -|:------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------:| -| `autoload` | PHP file loaded before WP Starter its steps.
Path to the file, relative to root. | "./wpstarter-autoload.php" | -| `cache-env` | Whether to cache env for WP requests. | true | -| `check-vcs-ignore` | Whether to check for VCS-ignored paths.
By default `true`, can be set to `false` to not do any check.
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | true | -| `command-steps` | Objects steps of custom steps to add to be ran via WP Starter command.
Values must be steps FQCN, keys the step names, matching what `Step::name()` returns.
Given classes must be autoloadable. | {} | -| `content-dev-dir` | Source folder for "development content".
Path to the folder, relative to root. | "./content-dev" | +| Key | Description | Default value | +| :----------------------: | :----------------------------------------------------------- | :------------------------: | +| `autoload` | PHP file loaded before WP Starter its steps.
Path to the file, relative to root. | "./wpstarter-autoload.php" | +| `cache-env` | Whether to cache env for WP requests. | true | +| `check-vcs-ignore` | Whether to check for VCS-ignored paths.
By default `true`, can be set to `false` to not do any check.
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | true | +| `command-steps` | Objects steps of custom steps to add to be ran via WP Starter command.
Values must be steps FQCN, keys the step names, matching what `Step::name()` returns.
Given classes must be autoloadable. | {} | +| `content-dev-dir` | Source folder for "development content".
Path to the folder, relative to root. | "./content-dev" | | `content-dev-op` | Operation to perform for "development content"
That is, plugins, themes, MU plugins, translations and dropins shipped with the project.
Valid values are "auto", symlink", "copy" and "none".
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | "auto" | -| `create-vcs-ignore-file` | Whether to check for VCS-ignore file if it does not exist.
By default `true`, can be set to `false` to not create it.
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | true | -| `custom-steps` | Map of custom step names to custom step classes.
Classes must be autoloadable. | null | -| `db-check` | Determine if and how DB check is done.
By default `true`, can be set to `false` to not do any check, or to `health` to do a health check using `mysqlcheck` binary. | true | -| `dropins` | Array of dropins files to move to WP content folder.
Can be local paths or remote URLs. | [] | -| `dropins-op` | Operation to perform for "dropins"
Valid values are "auto", symlink", "copy" and "none".
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | "auto" | -| `early-hook-file` | PHP file that adds callbacks to early hooks.
Path to the file, relative to root. | null | -| `env-bootstrap-dir` | Folder where to look for env bootstrap files.
Path to the folder, relative to root. | null | -| `env-dir` | Folder where to look for `.env` file.
Path to the folder, relative to root. | "./" | -| `env-example` | How to deal with `.env.example` file. Can be:
- `true` (copy default example file to root)
- `false` (do nothing)
- path, relative to root, to example file to copy.
- `"ask"` (user will be asked what to do) | true | -| `env-file` | Name of the `.env` file.
Will be searched inside `env-dir` | ".env" | -| `install-wp-cli` | Whether to install WP CLI from phar if necessary. | true | -| `move-content` | Whether to move default themes and plugins
to project wp-content folder.
Can be set to`"ask"`, in which case WP Starter will ask user what to do. | false | -| `prevent-overwrite` | Array of paths to preserve from overwrite.
Paths relative to root, might use glob patterns.
Can be set to`"ask"`, in which case WP Starter will ask confirmation before *each* overwrite attempt. | [] | -| `register-theme-folder` | Whether to register default themes.
Can be set to`"ask"`, in which case WP Starter will ask user what to do. | false | -| `require-wp` | Whether to check for WP package being required. | true | -| `scripts` | Array of script to run before or after steps.
An object where key is in the format:
`"pre-{$step}"` or `"post-{$step}"`
and value is either a callback.
Callbacks must be autoloadable. | [] | -| `skip-steps` | Array of step *names* to skip. | [] | -| `templates-dir` | Folder where to look for custom templates.
Path relative to root. | null | -| `wp-cli-commands` | Array of WP CLI commands.
Can be a path to a PHP file _returning_ the array of commands, or to a JSON file containing the array.
Paths relative to root. | [] | -| `wp-cli-files` | Array of file paths to be passed to WP CLI `eval file`. Can be an array of objects with "file", "args", and "skip-wordpress" properties.
Paths relative to root. | [] | -| `wp-version` | When `require-wp` is set to `false` this instruct WP Starter about the WP version that will be used with WP Starter. | null | +| `create-vcs-ignore-file` | Whether to check for VCS-ignore file if it does not exist.
By default `true`, can be set to `false` to not create it.
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | true | +| `custom-steps` | Map of custom step names to custom step classes.
Classes must be autoloadable. | null | +| `db-check` | Determine if and how DB check is done.
By default `true`, can be set to `false` to not do any check, or to `health` to do a health check using `mysqlcheck` binary. | true | +| `dropins` | Array of dropins files to move to WP content folder.
Can be local paths or remote URLs. | [] | +| `dropins-op` | Operation to perform for "dropins"
Valid values are "auto", symlink", "copy" and "none".
Can also be set to`"ask"`, in which case WP Starter will ask user what to do. | "auto" | +| `early-hook-file` | PHP file that adds callbacks to early hooks.
Path to the file, relative to root. | null | +| `env-bootstrap-dir` | Folder where to look for env bootstrap files.
Path to the folder, relative to root. | null | +| `env-dir` | Folder where to look for `.env` file.
Path to the folder, relative to root. | "./" | +| `env-example` | How to deal with `.env.example` file. Can be:
- `true` (copy default example file to root)
- `false` (do nothing)
- path, relative to root, to example file to copy.
- `"ask"` (user will be asked what to do) | true | +| `env-file` | Name of the `.env` file.
Will be searched inside `env-dir` | ".env" | +| `install-wp-cli` | Whether to install WP CLI from phar if necessary. | true | +| `move-content` | Whether to move default themes and plugins
to project wp-content folder.
Can be set to`"ask"`, in which case WP Starter will ask user what to do. | false | +| `prevent-overwrite` | Array of paths to preserve from overwrite.
Paths relative to root, might use glob patterns.
Can be set to`"ask"`, in which case WP Starter will ask confirmation before *each* overwrite attempt. | [] | +| `register-theme-folder` | Whether to register default themes.
Can be set to`"ask"`, in which case WP Starter will ask user what to do. | false | +| `require-wp` | Whether to check for WP package being required. | true | +| `scripts` | Array of script to run before or after steps.
An object where key is in the format:
`"pre-{$step}"` or `"post-{$step}"`
and value is either a callback.
Callbacks must be autoloadable. | [] | +| `skip-steps` | Array of step *names* to skip. | [] | +| `templates-dir` | Folder where to look for custom templates.
Path relative to root. | null | +| `use-putenv` | Tell WP Starter to store variables loaded from `.env` files _also_ using `putenv()`.
Use only if something is relying on `getenv()` and can not be changed. | false | +| `wp-cli-commands` | Array of WP CLI commands.
Can be a path to a PHP file _returning_ the array of commands, or to a JSON file containing the array.
Paths relative to root. | [] | +| `wp-cli-files` | Array of file paths to be passed to WP CLI `eval file`. Can be an array of objects with "file", "args", and "skip-wordpress" properties.
Paths relative to root. | [] | +| `wp-version` | When `require-wp` is set to `false` this instruct WP Starter about the WP version that will be used with WP Starter. | null | diff --git a/docs/index.md b/docs/index.md index 1f2565d..e07d425 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,21 +26,21 @@ WP Starter is the easiest and fastest way to bootstrap WordPress sites entirely # System Requirements - - PHP 7.1+ - - [Composer](https://getcomposer.org/) >= 1.10 + - PHP 7.4+ + - [Composer](https://getcomposer.org/) >= 2.0 ### Composer - PHP Support Table -| Composer ↓ / PHP → | 7.1 | 7.2 | 7.3 | 7.4 | 8.0 | 8.1 | 8.2 | -| :----------------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | -| 1.10 | ✔ | ✔ | ✔ | ✔ | ✔ | - | - | -| 2.0 | ✔ | ✔ | ✔ | ✔ | ✔ | - | - | -| 2.1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | - | -| 2.2 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.3 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.4 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| 2.5 | - | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| Composer ↓ / PHP → | 7.4 | 8.0 | 8.1 | 8.2 | +|:------------------:|:---:|:---:|:---:|:---:| +| 2.0 | ✔ | ✔ | - | - | +| 2.1 | ✔ | ✔ | - | - | +| 2.2 | ✔ | ✔ | - | - | +| 2.3 | ✔ | ✔ | ✔ | - | +| 2.4 | ✔ | ✔ | ✔ | ✔ | +| 2.5 | ✔ | ✔ | ✔ | ✔ | +| 2.6 | ✔ | ✔ | ✔ | ✔ | # License diff --git a/phpcs.xml.dist b/phpcs.xml.dist index b6431c7..4a69882 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -17,6 +17,7 @@ + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8d63e32..d42d3d9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,19 @@ + convertDeprecationsToExceptions="true" + xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"> - - + + ./src - - + + @@ -21,4 +23,5 @@ ./tests/integration + diff --git a/psalm.xml b/psalm.xml index 94850af..e1581a5 100644 --- a/psalm.xml +++ b/psalm.xml @@ -6,6 +6,8 @@ hideExternalErrors="true" errorLevel="1" allowNamedArgumentCalls="false" + findUnusedBaselineEntry="true" + findUnusedCode="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" @@ -17,6 +19,10 @@ + + + + diff --git a/src/Cli/SystemProcess.php b/src/Cli/SystemProcess.php index e5b4a33..6836c34 100644 --- a/src/Cli/SystemProcess.php +++ b/src/Cli/SystemProcess.php @@ -99,7 +99,6 @@ public function executeCapturing(string $command, ?string $cwd = null): array { $out = ''; $err = ''; - /** @psalm-suppress UnusedClosureParam */ $printer = static function (string $type, string $buffer) use (&$out, &$err): void { /** * @var string $out diff --git a/src/ComposerPlugin.php b/src/ComposerPlugin.php index f5f045f..9c3ac21 100644 --- a/src/ComposerPlugin.php +++ b/src/ComposerPlugin.php @@ -220,7 +220,7 @@ public function run(Util\SelectedStepsFactory $factory): void try { $config = $this->prepareRun($factory); } catch (\Throwable $throwable) { - $print = function (string $line) { + $print = function (string $line): void { $this->io->writeError(sprintf(' %s', trim($line))); }; @@ -343,7 +343,7 @@ private function prepareRun(Util\SelectedStepsFactory $factory): Config\Config private function convertErrorsToExceptions(): void { set_error_handler( - static function (int $code, string $msg, string $file = '', int $line = 0) { + static function (int $code, string $msg, string $file = '', int $line = 0): void { if ($file && $line) { $msg = rtrim($msg, '. ') . ", in {$file} line {$line}."; } @@ -413,7 +413,7 @@ private function loadExtensionAutoload(PackageInterface $package): void * @param Config\Config $config * @return void */ - private function checkWp(Config\Config $config) + private function checkWp(Config\Config $config): void { $requireWp = $config[Config\Config::REQUIRE_WP]->not(false); /** @var string $fallbackVer */ @@ -465,7 +465,7 @@ private function factoryStepsToRun(Util\SelectedStepsFactory $factory): array */ private function psr4LoaderFor(string $namespace, string $dir): callable { - return static function (string $class) use ($namespace, $dir) { + return static function (string $class) use ($namespace, $dir): void { if (stripos($class, $namespace) === 0) { /** @psalm-ignore-falsable-return */ $file = substr(str_replace('\\', '/', $class), strlen($namespace)); diff --git a/src/Config/Config.php b/src/Config/Config.php index 33186f0..dacf48a 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -18,6 +18,8 @@ * * A single place that can be used to access validated configuration read from JSON configuration, * but also to pass arbitrary data across steps. + * + * @template-implements \ArrayAccess */ final class Config implements \ArrayAccess { @@ -38,6 +40,7 @@ final class Config implements \ArrayAccess public const ENV_DIR = 'env-dir'; public const ENV_EXAMPLE = 'env-example'; public const ENV_FILE = 'env-file'; + public const ENV_USE_PUTENV = 'use-putenv'; public const INSTALL_WP_CLI = 'install-wp-cli'; public const IS_COMPOSER_INSTALL = 'is-composer-install'; public const IS_COMPOSER_UPDATE = 'is-composer-update'; @@ -74,6 +77,7 @@ final class Config implements \ArrayAccess self::ENV_DIR => null, self::ENV_EXAMPLE => true, self::ENV_FILE => '.env', + self::ENV_USE_PUTENV => false, self::INSTALL_WP_CLI => true, self::IS_COMPOSER_INSTALL => null, self::IS_WPSTARTER_COMMAND => null, @@ -110,6 +114,7 @@ final class Config implements \ArrayAccess self::ENV_DIR => 'validateDirName', self::ENV_EXAMPLE => 'validateBoolOrAskOrUrlOrPath', self::ENV_FILE => 'validateFileName', + self::ENV_USE_PUTENV => 'validateBool', self::INSTALL_WP_CLI => 'validateBool', self::IS_COMPOSER_INSTALL => 'validateBool', self::IS_COMPOSER_UPDATE => 'validateBool', diff --git a/src/Config/Result.php b/src/Config/Result.php index 602e61d..3b094b3 100644 --- a/src/Config/Result.php +++ b/src/Config/Result.php @@ -87,9 +87,12 @@ public static function promise(callable $provider): Result /** * @param mixed $value * @param \Throwable|null $error + * + * phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.IncorrectVoidReturn */ private function __construct($value = null, \Throwable $error = null) { + // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration.IncorrectVoidReturn if ($value instanceof Result) { $this->value = $value->value; $this->error = $value->error; diff --git a/src/Env/Filters.php b/src/Env/Filters.php index a087eb4..e47ba4e 100644 --- a/src/Env/Filters.php +++ b/src/Env/Filters.php @@ -204,7 +204,7 @@ private function filterOctalMod($value): int return $value; } - if (!is_string($value) || !is_numeric($value)) { + if (!is_string($value) || !preg_match('~^[0-7]+$~', $value)) { throw new \Exception('Invalid octal mod.'); } diff --git a/src/Env/Helpers.php b/src/Env/Helpers.php new file mode 100644 index 0000000..3892f12 --- /dev/null +++ b/src/Env/Helpers.php @@ -0,0 +1,168 @@ +read($key); + } + + /** + * @param bool $reset + * @return string + */ + final public static function envType(bool $reset = false): string + { + if (!isset(static::$type) || $reset) { + static::$type = static::envBridge()->determineEnvType(); + } + + return static::$type; + } + + /** + * @param string $fileName + * @param string $path + * @return array{string, bool} + */ + final public static function loadEnvFiles(string $fileName, string $path): array + { + $loader = static::envBridge(); + + if (!$loader->hasCachedValues()) { + $loader->load($fileName, $path); + $envType = static::envType(true); + if ($envType !== 'example') { + $loader->load("{$fileName}.{$envType}", $path); + } + + $loader->setupConstants(); + + return [$envType, false]; + } + + $loader->setupEnvConstants(); + + return [static::envType(true), true]; + } + + /** + * @return void + */ + final public static function dumpEnvCache(): void + { + if (!defined('WPSTARTER_ENV_PATH')) { + return; + } + if (static::shouldCacheEnv()) { + $path = WPSTARTER_ENV_PATH . WordPressEnvBridge::CACHE_DUMP_FILE; + static::envBridge()->dumpCached($path); + } + } + + /** + * @return bool + */ + final public static function shouldCacheEnv(): bool + { + if (!self::$cacheEnabled || !static::envBridge()->isWpSetup()) { + return false; + } + + if (!isset(static::$shouldCache)) { + $env = static::envType(); + $skip = ($env === 'local') || (defined('WP_DEVELOPMENT_MODE') && WP_DEVELOPMENT_MODE); + // Do not statically cache before plugins get a chance to change via filter + if (!did_action('plugins_loaded')) { + return !$skip; + } + static::$shouldCache = !apply_filters('wpstarter.skip-cache-env', $skip); + } + + return static::$shouldCache; + } + + /** + * @return bool + */ + final public static function isEnvCacheEnabled(): bool + { + return static::$cacheEnabled; + } + + /** + * @return WordPressEnvBridge + */ + private static function envBridge(): WordPressEnvBridge + { + if (!static::$bridge) { + if (!defined('WPSTARTER_ENV_PATH') || !self::$cacheEnabled) { + static::$bridge = new WordPressEnvBridge(); + static::$usePutenv and static::$bridge->usePutEnv(); + + return static::$bridge; + } + + $envCacheFile = WPSTARTER_ENV_PATH . WordPressEnvBridge::CACHE_DUMP_FILE; + static::$bridge = WordPressEnvBridge::buildFromCacheDump($envCacheFile); + static::$usePutenv and static::$bridge->usePutEnv(); + } + + return static::$bridge; + } +} diff --git a/src/Env/WordPressEnvBridge.php b/src/Env/WordPressEnvBridge.php index 5576d76..2206fa8 100644 --- a/src/Env/WordPressEnvBridge.php +++ b/src/Env/WordPressEnvBridge.php @@ -15,9 +15,12 @@ /** * Handle WordPress related environment variables using Symfony Env component. + * + * phpcs:disable Inpsyde.CodeQuality.PropertyPerClassLimit */ class WordPressEnvBridge { + /** @var array */ public const WP_CONSTANTS = [ 'ABSPATH' => Filters::FILTER_STRING, 'ADMIN_COOKIE_PATH' => Filters::FILTER_STRING, @@ -249,12 +252,12 @@ class WordPressEnvBridge /** * @var Dotenv|null */ - private static $defaultDotEnv; + private static $defaultDotEnv = null; /** - * @var array|null + * @var array|null */ - private static $loadedVars; + private static $loadedVars = null; /** * @var array @@ -269,7 +272,7 @@ class WordPressEnvBridge /** * @var Filters|null */ - private $filters; + private $filters = null; /** * @var bool @@ -282,20 +285,25 @@ class WordPressEnvBridge private $customFiltersConfig = []; /** - * @var list + * @var list|null */ - private $definedConstants = []; + private $definedConstants = null; /** * @var string|null */ - private $envType; + private $envType = null; /** * @var bool */ private $wordPressSetup = false; + /** + * @var bool + */ + private $usePutEnv = false; + /** * @param string $file * @return WordPressEnvBridge @@ -316,12 +324,20 @@ public static function buildFromCacheDump(string $file): WordPressEnvBridge /** * Symfony stores a variable with the keys of variables it loads. * - * @return array + * @return array */ public static function loadedVars(): array { if (self::$loadedVars === null) { - self::$loadedVars = array_flip(explode(',', (getenv('SYMFONY_DOTENV_VARS') ?: ''))); + $vars = $_SERVER['WPSTARTER_DOTENV_VARS'] ?? $_ENV['WPSTARTER_DOTENV_VARS'] ?? null; + if ($vars === null) { + self::$loadedVars = []; + + return []; + } + + /** @var array $loadedVars */ + self::$loadedVars = array_flip(explode(',', $vars)); unset(self::$loadedVars['']); } @@ -337,13 +353,19 @@ public function __construct(Dotenv $dotenv = null) } /** - * @param string $file Environment file path relative to `$path` - * @param string|null $path Environment file path * @return void */ - public function load(string $file = '.env', ?string $path = null): void + public function usePutEnv(): void { - $this->loadFile($this->fullpathFor($file, $path)); + $this->usePutEnv = true; + } + + /** + * @return void + */ + public function dontUsePutEnv(): void + { + $this->usePutEnv = false; } /** @@ -371,42 +393,19 @@ public function determineEnvType(): string } /** + * @param string $file * @param string $path * @return void */ - public function loadFile(string $path): void + public function load(string $file, string $path): void { - $loaded = $_ENV['WPSTARTER_ENV_LOADED'] ?? $_SERVER['WPSTARTER_ENV_LOADED'] ?? null; - if ($loaded !== null) { + $loaded = $_ENV['WPSTARTER_ENV_LOADED'] ?? $_SERVER['WPSTARTER_ENV_LOADED'] ?? false; + if (filter_var($loaded, FILTER_VALIDATE_BOOLEAN)) { self::$loadedVars = []; return; } - if (self::$loadedVars !== null) { - return; - } - - $path = $this->fullpathFor('', $path); - if ($path) { - $this->dotenv()->load($path); - self::loadedVars(); - } - } - - /** - * @param string $file - * @param string|null $path - * @return void - */ - public function loadAppended(string $file, ?string $path = null): void - { - if (self::$loadedVars === null) { - $this->load($file, $path); - - return; - } - $fullpath = $this->fullpathFor($file, $path); if (!$fullpath) { return; @@ -415,12 +414,26 @@ public function loadAppended(string $file, ?string $path = null): void $contents = @file_get_contents($fullpath); /** @var array $values */ $values = $contents ? $this->dotenv()->parse($contents, $fullpath) : []; + + $index = count(self::loadedVars()); foreach ($values as $name => $value) { - if ($this->isWritable($name)) { - $this->write($name, $value); - self::$loadedVars[$name] = true; + if (!$this->isWritable($name)) { + continue; } + $this->doWrite($name, $value, false); + if (!isset(self::$loadedVars[$name])) { + self::$loadedVars[$name] = $index; + $index++; + } + } + + if (!self::$loadedVars) { + return; } + + $keysLoaded = implode(',', array_keys(self::$loadedVars)); + $_SERVER['WPSTARTER_DOTENV_VARS'] = $keysLoaded; + $_ENV['WPSTARTER_DOTENV_VARS'] = $keysLoaded; } /** @@ -429,7 +442,7 @@ public function loadAppended(string $file, ?string $path = null): void */ public function has(string $name): bool { - return $this->read($name) !== null; + return isset(self::$cache[$name]) || ($this->doRead($name) !== null); } /** @@ -444,63 +457,19 @@ public function hasCachedValues(): bool * @param string $name * @return mixed * - * phpcs:disable Generic.Metrics.CyclomaticComplexity * phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration */ public function read(string $name) { - // phpcs:enable Generic.Metrics.CyclomaticComplexity // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration - $cached = self::$cache[$name] ?? null; if ($cached !== null) { return $cached[1]; } - if ($this->fromCache && (self::$loadedVars === null)) { - self::loadedVars(); - } - - // We don't check $_SERVER for keys starting with 'HTTP_' because clients can write there. - $serverSafe = strpos($name, 'HTTP_') !== 0; - - // We consider anything not loaded by Symfony Dot Env as "actual" environment, and because - // of thread safety issues, we don't use getenv() for those "actual" environment variables. - $loadedVar = self::$loadedVars && $this->isLoadedVar($name); - - $readGetEnv = false; - switch (true) { - case ($loadedVar && $serverSafe): - // Both $_SERVER and getenv() are ok. - $value = $_ENV[$name] ?? $_SERVER[$name] ?? null; - $readGetEnv = true; - break; - case ($loadedVar && !$serverSafe): - // $_SERVER is not ok, getenv() is. - $value = $_ENV[$name] ?? null; - $readGetEnv = true; - break; - case ($serverSafe): - // $_SERVER is ok, getenv() is not. - $value = $_ENV[$name] ?? $_SERVER[$name] ?? null; - break; - default: - // Neither $_SERVER nor getenv() are ok. - $value = $_ENV[$name] ?? null; - break; - } - - if (($value === null) && $readGetEnv) { - $value = getenv($name); - ($value === false) and $value = null; - } - - // Superglobals can contain anything, but environment variables must be strings. - // We can cast later scalar values. - // `is_scalar()` also discards null, and that is fine because we want to return null if - // that's the value we got here. + $value = $this->doRead($name); - return is_scalar($value) ? $this->maybeFilterThenCache($name, (string)$value) : null; + return ($value === null) ? null : $this->maybeFilterThenCache($name, (string)$value); } /** @@ -528,11 +497,7 @@ public function write(string $name, string $value): void throw new \BadMethodCallException("{$name} is not a writable ENV var."); } - putenv("{$name}={$value}"); - $_ENV[$name] = $value; - (strpos($name, 'HTTP_') !== 0) and $_SERVER[$name] = $value; - - $this->maybeFilterThenCache($name, $value); + $this->doWrite($name, $value, true); } /** @@ -546,29 +511,26 @@ public function dumpCached(string $file): bool } // Make sure cached env contains all loaded vars. - $symfonyLoaded = ''; - if (self::$loadedVars) { - foreach (array_keys(self::$loadedVars) as $key) { - $symfonyLoaded .= $symfonyLoaded ? ",{$key}" : $key; - $this->read($key); - } - } + $loaded = $this->collectVariables(); if (!static::$cache) { return false; } - $content = " list($value, $filtered)) { $slashed = str_replace("'", "\'", $value); // For defined constants, dump the `define` with filtered value, if any. if ( - in_array($key, $this->definedConstants, true) + in_array($key, $this->definedConstants ?? [], true) || array_key_exists($key, self::WP_CONSTANTS) ) { $define = $value !== $filtered @@ -584,7 +546,7 @@ public function dumpCached(string $file): bool } // For env loaded from file, dump the variable definition. - $content .= "putenv('{$key}={$slashed}');\n"; + $content .= $this->usePutEnv ? "putenv('{$key}={$slashed}');\n" : ''; $content .= "\$_ENV['{$key}'] = '{$slashed}';\n"; (strpos($key, 'HTTP_') !== 0) and $content .= "\$_SERVER['{$key}'] = '{$slashed}';\n\n"; } @@ -660,11 +622,49 @@ public function isWpSetup(): bool /** * @param string $name - * @return bool + * @param string $value + * @param bool $filterAndCache + * @return void */ - private function isLoadedVar(string $name): bool + private function doWrite(string $name, string $value, bool $filterAndCache): void { - return array_key_exists($name, self::loadedVars()); + $_ENV[$name] = $value; + (strpos($name, 'HTTP_') !== 0) and $_SERVER[$name] = $value; + $this->usePutEnv and putenv("{$name}={$value}"); + $filterAndCache and $this->maybeFilterThenCache($name, $value); + } + + /** + * @param string $name + * @return scalar|null + * + * phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration + */ + private function doRead(string $name) + { + // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration + $value = $_ENV[$name] ?? null; + + // We don't check $_SERVER for keys starting with 'HTTP_' because clients can write there. + if (($value === null) && (strpos($name, 'HTTP_') !== 0)) { + $value = $_SERVER[$name] ?? null; + } + + if ($value === null) { + (self::$loadedVars === null) and self::loadedVars(); + if (!isset(self::$loadedVars[$name])) { + // Maybe someone used `putenv`, so we're going to use `getenv`, but this is + // discouraged. We're going to cache this on read to hopeful reduce issues. + $value = getenv($name); + ($value === false) and $value = null; + } + } + + // Superglobals can contain anything, but environment variables must be strings. + // We can cast later scalar values. + // `is_scalar()` also discards null, and that is fine because we are going to return null if + // that's the $value we got here. + return is_scalar($value) ? $value : null; } /** @@ -707,7 +707,6 @@ private function maybeFilterThenCache(string $name, string $value) private function defineConstantFromVar(string $name): bool { $value = $this->read($name); - if ($value === null) { return false; } @@ -748,7 +747,17 @@ private function determineWpEnvType(string $envType): string */ private function isWritable(string $name): bool { - return !$this->has($name) || $this->isLoadedVar($name); + if (isset(static::$cache[$name])) { + return false; + } + + if (!$this->has($name)) { + return true; + } + + $loaded = self::loadedVars(); + + return isset($loaded[$name]); } /** @@ -768,6 +777,22 @@ private function fullpathFor(string $filename, ?string $basePath = null): string return $fullpath; } + /** + * @return string + */ + private function collectVariables(): string + { + $loaded = ''; + if (self::$loadedVars) { + foreach (array_keys(self::$loadedVars) as $key) { + $loaded .= $loaded ? ",{$key}" : $key; + $this->read($key); + } + } + + return $loaded; + } + /** * @return Dotenv */ @@ -776,10 +801,6 @@ private function dotEnv(): Dotenv $dotEnv = $this->dotenv ?? self::$defaultDotEnv; if (!$dotEnv) { self::$defaultDotEnv = new Dotenv(); - /** @psalm-suppress RedundantCondition */ - if (is_callable([self::$defaultDotEnv, 'usePutenv'])) { - self::$defaultDotEnv->usePutenv(true); - } $dotEnv = self::$defaultDotEnv; } diff --git a/src/Io/Question.php b/src/Io/Question.php index efc640c..50f461f 100644 --- a/src/Io/Question.php +++ b/src/Io/Question.php @@ -63,9 +63,12 @@ public static function newWithValidator( * @param array $lines * @param array $answers * @param string|null $default + * + * phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.IncorrectVoidReturn */ public function __construct(array $lines, array $answers = [], ?string $default = null) { + // phpcs:enable Inpsyde.CodeQuality.ReturnTypeDeclaration.IncorrectVoidReturn $this->lines = array_filter( $lines, static function (string $line): bool { diff --git a/src/Step/EnvExampleStep.php b/src/Step/EnvExampleStep.php index 067e783..6da0bfb 100644 --- a/src/Step/EnvExampleStep.php +++ b/src/Step/EnvExampleStep.php @@ -89,11 +89,11 @@ public function allowed(Config $config, Paths $paths): bool /** @var string $envFileName */ $envFileName = $config[Config::ENV_FILE]->unwrapOrFallback('.env'); /** @var string $envDir */ - $envDir = $config[Config::ENV_DIR]->unwrapOrFallback($paths->root()); + $envDir = $config[Config::ENV_DIR]->unwrapOrFallback(''); $envFile = $this->filesystem->normalizePath("{$envDir}/{$envFileName}"); if (is_file($paths->root($envFile))) { - $this->reason = sprintf('environment file already exists', Config::ENV_EXAMPLE); + $this->reason = 'environment file already exists'; return false; } @@ -110,7 +110,10 @@ public function allowed(Config $config, Paths $paths): bool public function targetPath(Paths $paths): string { /** @var string $envDir */ - $envDir = $this->config[Config::ENV_DIR]->unwrap(); + $envDir = $this->config[Config::ENV_DIR]->unwrapOrFallback(''); + if ($envDir === '') { + $envDir = $paths->root(''); + } return $this->filesystem->normalizePath("{$envDir}/.env.example"); } diff --git a/src/Step/FileCreationStep.php b/src/Step/FileCreationStep.php index 77483ff..bbe1308 100644 --- a/src/Step/FileCreationStep.php +++ b/src/Step/FileCreationStep.php @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace WeCodeMore\WpStarter\Step; use WeCodeMore\WpStarter\Util\Paths; diff --git a/src/Step/FileCreationStepInterface.php b/src/Step/FileCreationStepInterface.php index cb2e342..bc41a3f 100644 --- a/src/Step/FileCreationStepInterface.php +++ b/src/Step/FileCreationStepInterface.php @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace WeCodeMore\WpStarter\Step; /** diff --git a/src/Step/OptionalStep.php b/src/Step/OptionalStep.php index ea06879..b0b7fba 100644 --- a/src/Step/OptionalStep.php +++ b/src/Step/OptionalStep.php @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace WeCodeMore\WpStarter\Step; use WeCodeMore\WpStarter\Config\Config; diff --git a/src/Step/PostProcessStep.php b/src/Step/PostProcessStep.php index 7c7e212..edb4f2b 100644 --- a/src/Step/PostProcessStep.php +++ b/src/Step/PostProcessStep.php @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace WeCodeMore\WpStarter\Step; use WeCodeMore\WpStarter\Io\Io; diff --git a/src/Step/Step.php b/src/Step/Step.php index 6009912..6493bef 100644 --- a/src/Step/Step.php +++ b/src/Step/Step.php @@ -7,6 +7,8 @@ * file that was distributed with this source code. */ +declare(strict_types=1); + namespace WeCodeMore\WpStarter\Step; use WeCodeMore\WpStarter\Config\Config; diff --git a/src/Step/VcsIgnoreCheckStep.php b/src/Step/VcsIgnoreCheckStep.php index b6f9c9d..27805b4 100644 --- a/src/Step/VcsIgnoreCheckStep.php +++ b/src/Step/VcsIgnoreCheckStep.php @@ -118,10 +118,7 @@ public function run(Config $config, Paths $paths): int : $this->printNotIgnoredMessage($notIgnored); /** @psalm-suppress TypeDoesNotContainType */ - if ( - ($this->success !== '') - && $config[Config::IS_WPSTARTER_SELECTED_COMMAND]->is(true) - ) { + if ($this->success !== '') { return Step::SUCCESS; } @@ -183,7 +180,7 @@ private function pathsToIgnore(Paths $paths, Config $config): array { $from = $paths->root(); - $envDirName = $config[Config::ENV_DIR]->unwrap(); + $envDirName = $config[Config::ENV_DIR]->unwrapOrFallback(''); $envFileName = $config[Config::ENV_FILE]->unwrapOrFallback('.env'); /** @var list $toIgnore */ @@ -241,11 +238,12 @@ private function findCommonParents(array $pathsToIgnore, string $root): array } } + /** @var list $children */ foreach ($parents as $parent => $children) { - $count = count($children); - if ($count < 1) { + if (!$children) { continue; } + $count = count($children); if ($count > 1) { $fullpath = "{$root}/{$parent}"; $relPath = is_dir($fullpath) ? "{$parent}/" : $parent; @@ -277,7 +275,9 @@ private function relativizePaths(array $paths, string $root): array if (strpos($relPath, '..') === 0) { continue; } - (strpos($relPath, './') === 0) and $relPath = substr($relPath, 2); + if (strpos($relPath, './') === 0) { + $relPath = substr($relPath, 2) ?: ''; + } $relative[] = ($relPath !== '') ? $relPath : $path; } @@ -335,17 +335,16 @@ private function printNoVcsMessage(array $pathsToIgnore, Config $config): void private function printIgnoreConfigNotFound(array $pathsToIgnore, Config $config): void { if ( - ($this->vcs !== 'git') - && !$this->io->isVerbose() + !$this->io->isVerbose() && $config[Config::IS_WPSTARTER_SELECTED_COMMAND]->not(true) ) { return; } - $vcsName = $this->vcs ? self::VCS_LABELS[$this->vcs] : ''; + $vcsName = $this->vcs ? self::VCS_LABELS[$this->vcs] : 'a'; $this->io->writeCommentBlock( - "Looks like you are using {$vcsName} for version control, but " + "Looks like you are using {$vcsName} version control software, but " . 'WP Starter was not able to determine which paths are ignored, if any.', 'Please do not keep under version control the following paths:', sprintf('"%s".', implode('", "', array_keys($pathsToIgnore))), @@ -414,8 +413,9 @@ private function findNotGitIgnored(array $pathsToIgnore, Paths $paths, Config $c } $content = file_get_contents("{$root}/.gitignore"); + if ($content && strpos($content, self::IGNORE_SIGNATURE) !== false) { - $this->success = "Found a WP-Starter generated .gitignore file."; + $this->success = "Found a WP Starter-generated .gitignore file."; return []; } @@ -473,7 +473,7 @@ private function findNotHgIgnored(array $pathsToIgnore, Paths $paths, Config $co $content = file_get_contents("{$root}/.hgignore"); if ($content && strpos($content, self::IGNORE_SIGNATURE) !== false) { - $this->success = "Found a WP-Starter generated .hgignore file."; + $this->success = "Found a WP Starter-generated .hgignore file."; return []; } @@ -520,10 +520,10 @@ private function maybeCreateVcsIgnore( $filepath = $paths->root($file); if (!$this->filesystem->writeContent($built, $filepath)) { - $this->io->writeError("Creation of '{$filepath}' failed."); + $this->io->writeError("Creation of '{$filepath}' failed."); } - $this->success = "'{$filepath}' written."; + $this->success = "{$file} written."; return []; } diff --git a/src/Step/WpCliCommandsStep.php b/src/Step/WpCliCommandsStep.php index 20cf48e..9a875de 100644 --- a/src/Step/WpCliCommandsStep.php +++ b/src/Step/WpCliCommandsStep.php @@ -211,7 +211,7 @@ private function initMessage(string ...$commands): void array_walk( $commands, - function (string $command, int $i) { + function (string $command, int $i): void { $num = $i + 1; $commandDesc = ltrim($this->commandDesc(" {$command}")); $this->io->writeIfVerbose(" {$num}) \$ wp {$commandDesc}"); diff --git a/src/Step/WpConfigStep.php b/src/Step/WpConfigStep.php index ee9e17a..b7bd724 100644 --- a/src/Step/WpConfigStep.php +++ b/src/Step/WpConfigStep.php @@ -120,7 +120,7 @@ public function run(Config $config, Paths $paths): int } /** @var string $envDirName */ - $envDirName = $config[Config::ENV_DIR]->unwrap(); + $envDirName = $config[Config::ENV_DIR]->unwrapOrFallback(''); $envDir = $paths->root($envDirName); $this->filesystem->createDir($envDir); $envRelDir = $this->relPath($from, $envDir); @@ -144,6 +144,7 @@ public function run(Config $config, Paths $paths): int 'EARLY_HOOKS_FILE' => $earlyHookFile, 'ENV_BOOTSTRAP_DIR' => $envBootstrapDir ?: $envRelDir, 'ENV_FILE_NAME' => $config[Config::ENV_FILE]->unwrapOrFallback('.env'), + 'ENV_USE_PUTENV' => $config[Config::ENV_USE_PUTENV]->unwrapOrFallback(false), 'WPSTARTER_PATH' => $compatMode ? $envRelDir : $rootRelDir, 'ENV_REL_PATH' => $envRelDir, 'REGISTER_THEME_DIR' => $register ? 'true' : 'false', diff --git a/src/Util/Locator.php b/src/Util/Locator.php index b1c69c4..fe916e0 100644 --- a/src/Util/Locator.php +++ b/src/Util/Locator.php @@ -335,7 +335,7 @@ public function env(): WordPressEnvBridge $bridge = new WordPressEnvBridge(); $bridge->load($file, $dir); $environment = $bridge->determineEnvType(); - ($environment !== 'example') and $bridge->loadAppended("{$file}.{$environment}", $dir); + ($environment !== 'example') and $bridge->load("{$file}.{$environment}", $dir); $this->objects[__FUNCTION__] = $bridge; } diff --git a/src/Util/MuPluginList.php b/src/Util/MuPluginList.php index e7ed3fc..78c6fb3 100644 --- a/src/Util/MuPluginList.php +++ b/src/Util/MuPluginList.php @@ -96,6 +96,10 @@ public function pluginsList(Config $config): array // Because we have no indication files are actually plugins, we require the plugin header to // be there even if a single PHP file is in a path. $muPluginsDir = $this->paths->wpContent('/mu-plugins/'); + if (!is_dir($muPluginsDir)) { + return $list; + } + $muPluginsSubDirs = Finder::create() ->in($muPluginsDir) ->depth(0) diff --git a/src/Util/Paths.php b/src/Util/Paths.php index 37b3347..e4254b9 100644 --- a/src/Util/Paths.php +++ b/src/Util/Paths.php @@ -19,6 +19,8 @@ * * Many paths can be configured, this helper provides a way to do the configuration parsing only * once that use helper methods to obtain relative or absolute paths to specific folders. + * + * @template-implements \ArrayAccess */ final class Paths implements \ArrayAccess { @@ -319,7 +321,7 @@ private function parse(?string $root = null): array if (strpos($wpContentFullDir, $cwd) !== 0 || ($cwd === $wpContentFullDir)) { $to = ($cwd === $wpContentFullDir) ? 'root dir' : 'a dir outside root'; throw new \Exception( - "Config for WP config dir is pointing to {$to}, WP Starter does not support that." + "Config for WP content dir is pointing to {$to}, WP Starter does not support that." ); } diff --git a/templates/.env.example b/templates/.env.example index ff88fd0..d91ad6c 100644 --- a/templates/.env.example +++ b/templates/.env.example @@ -1,11 +1,22 @@ #--------------------------------------------------------------------------------------------------# # ENVIRONMENT SETTING # -# It can be anything, but "development", "staging", and "production" are supported out of the box. # +# It can be anything, but "local", "development", "staging", and "production" are supported # +# out-of-the of the box. # # Do not use "development" on production and vice versa because it affects debug settings. # #--------------------------------------------------------------------------------------------------# WP_ENVIRONMENT_TYPE=development +#--------------------------------------------------------------------------------------------------# +# DEVELOPMENT MODE # +# Supported values are: "core", "plugin", "theme", "all". Do not define to disable. # +# Setting a development mode is only relevant for sites where any kind of development occurs. # +# For example, it is not advised or relevant to use on a production site. # +# Most usage today relates to theme.json caching. # +#--------------------------------------------------------------------------------------------------# +WP_DEVELOPMENT_MODE=all + + #--------------------------------------------------------------------------------------------------# # MANDATORY DATABASE SETTINGS # #--------------------------------------------------------------------------------------------------# @@ -16,7 +27,7 @@ DB_PASSWORD= #--------------------------------------------------------------------------------------------------# # HOME PAGE URL # -# Optional, but recommended or current server name will be used, e.g., https://www.example.com. # +# Optional, but recommended or current server name will be used, e.g., 'https://www.example.com'. # #--------------------------------------------------------------------------------------------------# WP_HOME= @@ -230,5 +241,5 @@ WP_HOME= # MISCELLANEOUS # #--------------------------------------------------------------------------------------------------# #WP_MAIL_INTERVAL=300 -#WP_DEFAULT_THEME=twentyfifteen +#WP_DEFAULT_THEME=twentytwentythree #WP_DISABLE_FATAL_ERROR_HANDLER=true diff --git a/templates/wp-config.php b/templates/wp-config.php index 3ac4b15..636ba42 100644 --- a/templates/wp-config.php +++ b/templates/wp-config.php @@ -8,7 +8,7 @@ * are required, you can get them from your web host. */ -use WeCodeMore\WpStarter\Env\WordPressEnvBridge; +use WeCodeMore\WpStarter\Env\{WordPressEnvBridge, Helpers}; DEBUG_INFO_INIT: { $debugInfo = []; @@ -49,27 +49,6 @@ ]; } #@@/AUTOLOAD -WPS_GETENV_FUNCTION: { - function wpstarter_getenv(?string $key) { - static $env; - if ($env && ($key === null)) { - throw new TypeError('wpstarter_env(): Argument #1 ($key) must be of type string, null given.'); - } - if ($key === '') { - throw new InvalidArgumentException('wpstarter_env(): Argument #1 ($key) must be a non-empty string.'); - } - if (!$env) { - $envCacheEnabled = filter_var('{{{CACHE_ENV}}}', FILTER_VALIDATE_BOOLEAN); - $envCacheFile = WPSTARTER_ENV_PATH . WordPressEnvBridge::CACHE_DUMP_FILE; - $env = $envCacheEnabled - ? WordPressEnvBridge::buildFromCacheDump($envCacheFile) - : new WordPressEnvBridge(); - } - return ($key === null) ? $env : $env->read($key); - } - $envLoader = wpstarter_getenv(null); -} #@@/WPS_GETENV_FUNCTION - ENV_VARIABLES: { /** * Environment variables will be loaded from file, unless `WPSTARTER_ENV_LOADED` env var is @@ -78,14 +57,10 @@ function wpstarter_getenv(?string $key) { * Environment variables that are set in the *real* environment (e.g. via webserver) will not be * overridden from file, even if `WPSTARTER_ENV_LOADED` is not set. */ - $envIsCached = $envLoader->hasCachedValues(); - if (!$envIsCached) { - $envLoader->load('{{{ENV_FILE_NAME}}}', WPSTARTER_ENV_PATH); - $envType = $envLoader->determineEnvType(); - if ($envType !== 'example') { - $envLoader->loadAppended("{{{ENV_FILE_NAME}}}.{$envType}", WPSTARTER_ENV_PATH); - } - } + filter_var('{{{CACHE_ENV}}}', FILTER_VALIDATE_BOOLEAN) and Helpers::enableCache(); + filter_var('{{{ENV_USE_PUTENV}}}', FILTER_VALIDATE_BOOLEAN) and Helpers::usePutenv(); + [$envType, $envIsCached] = Helpers::loadEnvFiles('{{{ENV_FILE_NAME}}}', WPSTARTER_ENV_PATH); + /** * Define all WordPress constants from environment variables. * @@ -95,29 +70,27 @@ function wpstarter_getenv(?string $key) { * In that case, `wp_get_environment_type()` will return "development", but `WP_ENV` will still * be "dev" (or "develop", or "develop-1"). */ - $envIsCached ? $envLoader->setupEnvConstants() : $envLoader->setupConstants(); - isset($envType) or $envType = $envLoader->determineEnvType(); defined('WP_ENVIRONMENT_TYPE') or define('WP_ENVIRONMENT_TYPE', 'production'); $envCacheFile = realpath(WPSTARTER_ENV_PATH . WordPressEnvBridge::CACHE_DUMP_FILE); - $debugInfo['env-cache-file'] = [ - 'label' => 'Env cache file', - 'value' => $envCacheFile ?: 'None', - 'debug' => $envCacheFile, - ]; - $envCacheEnabled = filter_var('{{{CACHE_ENV}}}', FILTER_VALIDATE_BOOLEAN); - $debugInfo['env-cache-enabled'] = [ - 'label' => 'Env cache enabled', + $envCacheEnabled = Helpers::isEnvCacheEnabled(); + $debugInfo['env-cache-config'] = [ + 'label' => 'Env cache enabled in configuration', 'value' => $envCacheEnabled ? 'Yes' : 'No', 'debug' => $envCacheEnabled, ]; - $debugInfo['cached-env'] = [ + $debugInfo['env-loaded-from-cache'] = [ 'label' => 'Is env loaded from cache', 'value' => $envIsCached ? 'Yes' : 'No', 'debug' => $envIsCached, ]; - $debugInfo['env-type'] = [ - 'label' => 'Env type', + $debugInfo['env-loaded-cache-file'] = [ + 'label' => 'Env cache file', + 'value' => $envCacheFile ?: '*None*', + 'debug' => $envCacheFile, + ]; + $debugInfo['wpstarter-env-type'] = [ + 'label' => 'WP Starter env type', 'value' => $envType, 'debug' => $envType, ]; @@ -136,10 +109,10 @@ function wpstarter_getenv(?string $key) { } $debugInfo['env-php-file'] = [ 'label' => 'Env-specific PHP file', - 'value' => $hasPhpEnvFile ? $phpEnvFilePath : 'None', + 'value' => $hasPhpEnvFile ? $phpEnvFilePath : '*None*', 'debug' => $hasPhpEnvFile ? $phpEnvFilePath : '', ]; - unset($phpEnvFilePath, $hasPhpEnvFile); + unset($phpEnvFilePath, $hasPhpEnvFile, $envType); } #@@/ENV_VARIABLES KEYS: { @@ -167,7 +140,7 @@ function wpstarter_getenv(?string $key) { * WordPress Database Table prefix. */ global $table_prefix; - $table_prefix = $envLoader->read('DB_TABLE_PREFIX') ?: 'wp_'; + $table_prefix = Helpers::readVar('DB_TABLE_PREFIX') ?: 'wp_'; } #@@/DB_SETUP EARLY_HOOKS : { @@ -184,7 +157,7 @@ function wpstarter_getenv(?string $key) { } $debugInfo['early-hooks-file'] = [ 'label' => 'Early hooks file', - 'value' => $earlyHookFile ? __DIR__ . '{{{EARLY_HOOKS_FILE}}}' : 'None', + 'value' => $earlyHookFile ? __DIR__ . '{{{EARLY_HOOKS_FILE}}}' : '*None*', 'debug' => $earlyHookFile ? __DIR__ . '{{{EARLY_HOOKS_FILE}}}' : '', ]; unset($earlyHookFile); @@ -223,7 +196,7 @@ function wpstarter_getenv(?string $key) { } #@@/DEFAULT_ENV SSL_FIX : { - $doSslFix = $envLoader->read('WP_FORCE_SSL_FORWARDED_PROTO') + $doSslFix = Helpers::readVar('WP_FORCE_SSL_FORWARDED_PROTO') && array_key_exists('HTTP_X_FORWARDED_PROTO', $_SERVER) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https'; $doSslFix and $_SERVER['HTTPS'] = 'on'; @@ -274,8 +247,8 @@ function wpstarter_getenv(?string $key) { /** Allow changing admin color scheme. Useful to distinguish environments in the dashboard. */ add_filter( 'get_user_option_admin_color', - static function ($color) use ($envLoader) { - return $envLoader->read('WP_ADMIN_COLOR') ?: $color; + static function ($color): string { + return (string)(Helpers::readVar('WP_ADMIN_COLOR') ?: $color); }, 999 ); @@ -283,23 +256,19 @@ static function ($color) use ($envLoader) { ENV_CACHE : { /** On shutdown, we dump environment so that on subsequent requests we can load it faster */ - if ('{{{CACHE_ENV}}}' && $envLoader->isWpSetup()) { - register_shutdown_function( - static function () use ($envLoader, $envType) { - $isLocal = $envType === 'local'; - $isDevMode = defined('WP_DEVELOPMENT_MODE') && WP_DEVELOPMENT_MODE; - if (!apply_filters('wpstarter.skip-cache-env', $isLocal || $isDevMode, $envType)) { - $envLoader->dumpCached(WPSTARTER_ENV_PATH . WordPressEnvBridge::CACHE_DUMP_FILE); - } - } - ); - } + register_shutdown_function([Helpers::class, 'dumpEnvCache']); } #@@/ENV_CACHE DEBUG_INFO : { add_filter( 'debug_information', static function ($info) use ($debugInfo): array { + $shouldCache = Helpers::shouldCacheEnv(); + $debugInfo['env-should-cache'] = [ + 'label' => 'Should cache env', + 'value' => $shouldCache ? 'Yes' : 'No', + 'debug' => $shouldCache, + ]; is_array($info) or $info = []; $info['wp-starter'] = ['label' => 'WP Starter', 'fields' => $debugInfo]; @@ -307,16 +276,13 @@ static function ($info) use ($debugInfo): array { }, 30 ); + unset($debugInfo); } #@@/DEBUG_INFO BEFORE_BOOTSTRAP : { /** A pre-defined section to extend configuration. */ } #@@/BEFORE_BOOTSTRAP -CLEAN_UP : { - unset($debugInfo, $envType, $envLoader); -} #@@/CLEAN_UP - WP_CLI_HACK : { if (defined('WP_STARTER_WP_CONFIG_PATH') && defined('WP_CLI') && \WP_CLI) { return; diff --git a/tests/integration/Cli/PhpToolProcessFactoryTest.php b/tests/integration/Cli/PhpToolProcessFactoryTest.php index 29fa6e3..35ce403 100644 --- a/tests/integration/Cli/PhpToolProcessFactoryTest.php +++ b/tests/integration/Cli/PhpToolProcessFactoryTest.php @@ -229,7 +229,7 @@ private function factoryPhpToolProcessFactory( $paths = $locator->paths(); $io = $locator->io(); - $installer = new PharInstaller($io, $urlDownloader); + $installer = new PharInstaller($io, $urlDownloader); $finder = $locator->packageFinder(); return new PhpToolProcessFactory($paths, $io, $installer, $finder, $locator->phpProcess()); diff --git a/tests/integration/Env/WordPressEnvBridgeTest.php b/tests/integration/Env/WordPressEnvBridgeTest.php index 2b203bb..47e77d9 100644 --- a/tests/integration/Env/WordPressEnvBridgeTest.php +++ b/tests/integration/Env/WordPressEnvBridgeTest.php @@ -38,7 +38,7 @@ public function testLoadSkippingFile(): void * @test * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge */ - public function testLoadFile(): void + public function testLoad(): void { $bridge = new WordPressEnvBridge(); @@ -52,21 +52,6 @@ public function testLoadFile(): void static::assertSame('', $bridge->read('COOKIE_DOMAIN')); } - /** - * @test - * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge - */ - public function testLoadFileMoreTimesDoNothing(): void - { - $bridge = new WordPressEnvBridge(); - - $bridge->load('example.env', $this->fixturesPath()); - $bridge->load('more.env', $this->fixturesPath()); - - static::assertSame('localhost', $bridge->read('DB_HOST')); - static::assertNull($bridge->read('FOO')); - } - /** * @test * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge @@ -88,26 +73,11 @@ public function testHttpServerVarsAreReturnedOnlyIfLoaded(): void * @test * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge */ - public function testGetEnvIsSkippedForNotLoadedVars(): void - { - putenv('PUT_THE_ENV=HERE'); - - $bridge = new WordPressEnvBridge(); - $bridge->load('more.env', $this->fixturesPath()); - - static::assertSame('HERE', getenv('PUT_THE_ENV')); - static::assertNull($bridge->read('PUT_THE_ENV')); - } - - /** - * @test - * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge - */ - public function testLoadAppended(): void + public function testMultipleLoads(): void { $bridge = new WordPressEnvBridge(); $bridge->load('example.env', $this->fixturesPath()); - $bridge->loadAppended('more.env', $this->fixturesPath()); + $bridge->load('more.env', $this->fixturesPath()); static::assertSame('192.168.1.255', $bridge->read('DB_HOST')); static::assertSame('BAR BAR', $bridge->read('BAZ')); @@ -117,49 +87,29 @@ public function testLoadAppended(): void * @test * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge */ - public function testLoadAppendedWrongFileDoNothing(): void + public function testMultipleLoadsDontOverrideIfRead(): void { $bridge = new WordPressEnvBridge(); + $bridge->load('example.env', $this->fixturesPath()); - $bridge->loadAppended('not-more.env', $this->fixturesPath()); + static::assertSame('localhost', $bridge->read('DB_HOST')); + $bridge->load('more.env', $this->fixturesPath()); static::assertSame('localhost', $bridge->read('DB_HOST')); - static::assertNull($bridge->read('BAZ')); } /** * @test * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge */ - public function testLoadAppendedAlwaysLoadsIfLoadWasCalledAndEnvLoaded(): void + public function testLoadAppendedWrongFileDoNothing(): void { - $_ENV['WPSTARTER_ENV_LOADED'] = 1; - $bridge = new WordPressEnvBridge(); $bridge->load('example.env', $this->fixturesPath()); + $bridge->load('not-more.env', $this->fixturesPath()); - static::assertNull($bridge->read('DB_HOST')); - - $bridge->loadAppended('more.env', $this->fixturesPath()); - - static::assertSame('192.168.1.255', $bridge->read('DB_HOST')); - } - - /** - * @test - * @covers \WeCodeMore\WpStarter\Env\WordPressEnvBridge - */ - public function testLoadAppendedNotLoadsIfLoadWasNotCalledAndEnvLoaded(): void - { - $_ENV['WPSTARTER_ENV_LOADED'] = 1; - - $bridge = new WordPressEnvBridge(); - - static::assertNull($bridge->read('DB_HOST')); - - $bridge->loadAppended('more.env', $this->fixturesPath()); - - static::assertNull($bridge->read('DB_HOST')); + static::assertSame('localhost', $bridge->read('DB_HOST')); + static::assertNull($bridge->read('BAZ')); } /** @@ -172,15 +122,15 @@ public function testLoadAppendedDoesNotOverrideActualEnv(): void $bridge = new WordPressEnvBridge(); $bridge->load('example.env', $this->fixturesPath()); - $bridge->loadAppended('more.env', $this->fixturesPath()); + $bridge->load('more.env', $this->fixturesPath()); $bridge->write('NEW', 'new!'); $env = $bridge->readMany('DB_NAME', 'DB_HOST', 'FOO', 'NEW'); - static::assertSame('wp', $env['DB_NAME']); // example.env + static::assertSame('wp', $env['DB_NAME']); // example.env static::assertSame('192.168.1.255', $env['DB_HOST']); // more.env - static::assertSame('I come first.', $env['FOO']); // actual.env - static::assertSame('new!', $env['NEW']); // offsetSet + static::assertSame('I come first.', $env['FOO']); // actual env + static::assertSame('new!', $env['NEW']); // offsetSet } /** @@ -291,7 +241,7 @@ public function testDumpCacheAndLoadFromDump(): void $_ENV['FS_CHMOD_DIR'] = '0644'; $bridge = new WordPressEnvBridge(); - $bridge->loadFile($this->fixturesPath() . '/example.env'); + $bridge->load('example.env', $this->fixturesPath()); $bridge->write('FOO', 'Bar!'); static::assertFalse($bridge->hasCachedValues()); @@ -304,7 +254,6 @@ public function testDumpCacheAndLoadFromDump(): void static::assertSame('Awesome!', $bridge->read('MY_AWESOME_VAR')); // and also works with "manual" added env. static::assertSame('Bar!', $bridge->read('FOO')); - static::assertSame('Bar!', getenv('FOO')); static::assertSame('Bar!', $_ENV['FOO'] ?? ''); $loadedVars = WordPressEnvBridge::loadedVars(); @@ -318,11 +267,11 @@ public function testDumpCacheAndLoadFromDump(): void $cleanLoaded = \Closure::bind( /** @bound */ - function (bool $setCache) use (&$oldCache) { + function (bool $setCache) use (&$oldCache): void { $setCache and $oldCache = static::$cache; static::$cache = []; static::$loadedVars = null; - putenv('SYMFONY_DOTENV_VARS'); + unset($_SERVER['WPSTARTER_DOTENV_VARS'], $_ENV['WPSTARTER_DOTENV_VARS']); }, $bridge, WordPressEnvBridge::class @@ -345,18 +294,15 @@ function (bool $setCache) use (&$oldCache) { unset($_ENV['DB_NAME']); unset($_SERVER['DB_NAME']); // ...and prove it is clean - static::assertSame(false, getenv('MY_BAD_VAR')); static::assertNull($_ENV['MY_BAD_VAR'] ?? null); static::assertNull($_ENV['MY_BAD_VAR'] ?? null); - static::assertSame(false, getenv('DB_HOST')); static::assertNull($_ENV['DB_HOST'] ?? null); static::assertNull($_ENV['DB_HOST'] ?? null); - static::assertSame(false, getenv('DB_NAME')); static::assertNull($_ENV['DB_NAME'] ?? null); static::assertNull($_ENV['DB_NAME'] ?? null); $cachedBridge = WordPressEnvBridge::buildFromCacheDump($cacheFile); - $cachedContent = file_get_contents($cacheFile); + $contents = file_get_contents($cacheFile); static::assertFalse($cachedBridge->isWpSetup()); static::assertTrue($cachedBridge->hasCachedValues()); @@ -374,13 +320,18 @@ function (): array { static::assertSame($oldCache, $newCache); // These variables were accessed via read() and should be part of the dump - static::assertStringContainsString("putenv('MY_AWESOME_VAR=Awesome!');", $cachedContent); - static::assertStringContainsString("putenv('DB_NAME=wp');", $cachedContent); - static::assertStringContainsString("define('DB_NAME', 'wp');", $cachedContent); + static::assertStringContainsString("define('DB_NAME', 'wp');", $contents); + static::assertStringContainsString("\$_SERVER['DB_NAME'] = 'wp';", $contents); + static::assertStringContainsString("\$_ENV['DB_NAME'] = 'wp';", $contents); + static::assertStringContainsString("\$_SERVER['MY_AWESOME_VAR'] = 'Awesome!';", $contents); + static::assertStringContainsString("\$_ENV['MY_AWESOME_VAR'] = 'Awesome!';", $contents); + // ... and these variables were NOT accessed via read() but still should be part of the dump - static::assertStringContainsString("putenv('MY_BAD_VAR=Bad!');", $cachedContent); - static::assertStringContainsString("putenv('EMPTY_TRASH_DAYS=12');", $cachedContent); - static::assertStringContainsString("define('EMPTY_TRASH_DAYS', 12);", $cachedContent); + static::assertStringContainsString("\$_SERVER['MY_BAD_VAR'] = 'Bad!';", $contents); + static::assertStringContainsString("\$_ENV['MY_BAD_VAR'] = 'Bad!';", $contents); + static::assertStringContainsString("\$_SERVER['EMPTY_TRASH_DAYS'] = '12';", $contents); + static::assertStringContainsString("\$_ENV['EMPTY_TRASH_DAYS'] = '12';", $contents); + static::assertStringContainsString("define('EMPTY_TRASH_DAYS', 12);", $contents); // WP constants are set for actual env, accessed loaded env, and not accessed loaded env static::assertTrue(defined('WP_POST_REVISIONS')); @@ -397,15 +348,12 @@ function (): array { static::assertSame(7, WP_POST_REVISIONS); static::assertSame('0644', $_ENV['FS_CHMOD_DIR'] ?? null); static::assertSame(0644, FS_CHMOD_DIR); - static::assertSame('Bar!', getenv('FOO')); static::assertSame('Bar!', $_ENV['FOO'] ?? null); static::assertSame('Bar!', $_SERVER['FOO'] ?? null); // Loaded env can be read, we proved they were not there before cache - static::assertSame('Bad!', getenv('MY_BAD_VAR')); static::assertSame('Bad!', $_ENV['MY_BAD_VAR'] ?? null); static::assertSame('Bad!', $_SERVER['MY_BAD_VAR'] ?? null); - static::assertSame('localhost', getenv('DB_HOST')); static::assertSame('localhost', $_ENV['DB_HOST'] ?? null); static::assertSame('localhost', $_SERVER['DB_HOST'] ?? null); @@ -437,7 +385,7 @@ public function testsSetupConstants(): void $_ENV['FS_CHMOD_DIR'] = '0644'; $bridge = new WordPressEnvBridge(); - $bridge->loadFile($this->fixturesPath() . '/example.env'); + $bridge->load('example.env', $this->fixturesPath()); $bridge->write('FOO', 'Bar!'); $bridge->write( @@ -463,6 +411,7 @@ public function testsSetupConstants(): void static::assertSame(0644, FS_CHMOD_DIR); static::assertSame('on', SUNRISE); + static::assertSame('One!', PLUGIN_CONFIG_ONE); static::assertSame(2, PLUGIN_CONFIG_TWO); static::assertTrue(PLUGIN_CONFIG_THREE); static::assertSame('4', PLUGIN_CONFIG_FOUR); @@ -510,37 +459,32 @@ public function testLoadCacheFromScratch(): void static::assertSame("on", SUNRISE); // Variables from actual env are not set in env in the dump file... - static::assertFalse(getenv('WP_POST_REVISIONS')); static::assertNull($_ENV['WP_POST_REVISIONS'] ?? null); - static::assertFalse(getenv('FS_CHMOD_DIR')); static::assertNull($_ENV['FS_CHMOD_DIR'] ?? null); - static::assertFalse(getenv('FOO')); static::assertNull($_ENV['FOO'] ?? null); + static::assertNull($_SERVER['WP_POST_REVISIONS'] ?? null); + static::assertNull($_SERVER['FS_CHMOD_DIR'] ?? null); + static::assertNull($_SERVER['FOO'] ?? null); // but because there were accessed, cache still contains them. static::assertSame(7, $cachedBridge->read('WP_POST_REVISIONS')); static::assertSame(0644, $cachedBridge->read('FS_CHMOD_DIR')); static::assertSame('Bar!', $cachedBridge->read('FOO')); // These loaded env vars were accessed in previous test (via setupWordPress()). - static::assertSame('xxx_', getenv('DB_TABLE_PREFIX')); static::assertSame('xxx_', $_ENV['DB_TABLE_PREFIX'] ?? null); static::assertSame('xxx_', $_SERVER['DB_TABLE_PREFIX'] ?? null); static::assertSame('xxx_', $cachedBridge->read('DB_TABLE_PREFIX')); - static::assertSame('wp', getenv('DB_NAME')); static::assertSame('wp', $_ENV['DB_NAME'] ?? null); static::assertSame('wp', $_SERVER['DB_NAME'] ?? null); static::assertSame('wp', $cachedBridge->read('DB_NAME')); - static::assertSame('', getenv('COOKIE_DOMAIN')); static::assertSame('', $_ENV['COOKIE_DOMAIN'] ?? null); static::assertSame('', $_SERVER['COOKIE_DOMAIN'] ?? null); static::assertSame('', $cachedBridge->read('COOKIE_DOMAIN')); - static::assertSame('', getenv('COOKIE_DOMAIN')); static::assertSame('on', $_ENV['SUNRISE'] ?? null); static::assertSame('on', $_SERVER['SUNRISE'] ?? null); static::assertSame('on', $cachedBridge->read('SUNRISE')); // These loaded env vars were NOT accessed in previous test, but they can be accessed now. - static::assertSame('Bad!', getenv('MY_BAD_VAR')); static::assertSame('Bad!', $_ENV['MY_BAD_VAR'] ?? null); static::assertSame('Bad!', $_SERVER['MY_BAD_VAR'] ?? null); static::assertSame('Bad!', $cachedBridge->read('MY_BAD_VAR')); diff --git a/tests/integration/Util/PackageFinderTest.php b/tests/integration/Util/PackageFinderTest.php index 82b4f8c..3e46805 100644 --- a/tests/integration/Util/PackageFinderTest.php +++ b/tests/integration/Util/PackageFinderTest.php @@ -34,9 +34,8 @@ public function testFindByType(): void $names[] = $plugin->getName(); } - static::assertCount(3, $names); + static::assertCount(2, $names); static::assertTrue(in_array('composer/installers', $names, true)); - static::assertTrue(in_array('composer/package-versions-deprecated', $names, true)); static::assertTrue( in_array('dealerdirect/phpcodesniffer-composer-installer', $names, true) ); diff --git a/tests/src/IntegrationTestCase.php b/tests/src/IntegrationTestCase.php index 5277f4a..b4b37df 100644 --- a/tests/src/IntegrationTestCase.php +++ b/tests/src/IntegrationTestCase.php @@ -82,7 +82,7 @@ protected function factoryConsoleOutput( public $lines = []; /** @noinspection PhpSignatureMismatchDuringInheritanceInspection */ - protected function doWrite($message, $newline) + protected function doWrite($message, $newline): void { if (!$newline && $this->lines) { $last = array_pop($this->lines); diff --git a/tests/unit/Config/ConfigTest.php b/tests/unit/Config/ConfigTest.php index 109303a..655621c 100644 --- a/tests/unit/Config/ConfigTest.php +++ b/tests/unit/Config/ConfigTest.php @@ -108,7 +108,7 @@ public function testValidateWithError(): void { $config = new Config(['hello' => 'Hello!'], $this->factoryValidator()); - $config->appendValidator('hello', static function () { + $config->appendValidator('hello', static function (): void { throw new \Error('No hello!'); }); @@ -125,7 +125,7 @@ public function testValidateWithThrowable(): void { $config = new Config(['hello' => 'Hello!'], $this->factoryValidator()); - $config->appendValidator('hello', static function () { + $config->appendValidator('hello', static function (): void { throw new \Error('No hello!'); }); diff --git a/tests/unit/Env/FiltersTest.php b/tests/unit/Env/FiltersTest.php index c8e9452..a3d7463 100644 --- a/tests/unit/Env/FiltersTest.php +++ b/tests/unit/Env/FiltersTest.php @@ -23,7 +23,7 @@ class FiltersTest extends TestCase * @param mixed $input * @param mixed $expectedOutput */ - public function testFilter(string $mode, $input, $expectedOutput) + public function testFilter(string $mode, $input, $expectedOutput): void { $filter = new Filters(); diff --git a/tests/unit/Io/IoTest.php b/tests/unit/Io/IoTest.php index d4f96a4..7ee513b 100644 --- a/tests/unit/Io/IoTest.php +++ b/tests/unit/Io/IoTest.php @@ -45,7 +45,7 @@ static function (string $error): void { ->twice() ->with(\Mockery::type('string')) ->andReturnUsing( - static function (string $error) { + static function (string $error): void { static $i; $i = $i ?? 0; $i++; @@ -93,7 +93,7 @@ static function (string $error): void { ->twice() ->with(\Mockery::type('string')) ->andReturnUsing( - static function (string $error) { + static function (string $error): void { static $i; $i = $i ?? 0; $i++; diff --git a/tests/unit/Util/RequirementsTest.php b/tests/unit/Util/RequirementsTest.php index 7e66109..37b3bff 100644 --- a/tests/unit/Util/RequirementsTest.php +++ b/tests/unit/Util/RequirementsTest.php @@ -20,7 +20,7 @@ class RequirementsTest extends TestCase { - public function testGenericCommandInstanceCreation() + public function testGenericCommandInstanceCreation(): void { $composer = \Mockery::mock(\Composer\Composer::class); $composerConfig = \Mockery::mock(\Composer\Config::class); @@ -39,7 +39,7 @@ public function testGenericCommandInstanceCreation() static::assertFalse($config[Config::IS_COMPOSER_INSTALL]->unwrap()); } - public function testSelectedStepsCommandInstanceCreation() + public function testSelectedStepsCommandInstanceCreation(): void { $composer = \Mockery::mock(\Composer\Composer::class); $composerConfig = \Mockery::mock(\Composer\Config::class); @@ -58,7 +58,7 @@ public function testSelectedStepsCommandInstanceCreation() static::assertFalse($config[Config::IS_COMPOSER_INSTALL]->unwrap()); } - public function testComposerInstallInstanceCreation() + public function testComposerInstallInstanceCreation(): void { $composer = \Mockery::mock(\Composer\Composer::class); $composerConfig = \Mockery::mock(\Composer\Config::class); @@ -80,7 +80,7 @@ public function testComposerInstallInstanceCreation() static::assertSame([$pkg1, $pkg2], $config[Config::COMPOSER_UPDATED_PACKAGES]->unwrap()); } - public function testComposerUpdateInstanceCreation() + public function testComposerUpdateInstanceCreation(): void { $composer = \Mockery::mock(\Composer\Composer::class); $composerConfig = \Mockery::mock(\Composer\Config::class); @@ -103,7 +103,7 @@ public function testComposerUpdateInstanceCreation() /** * When no configs are there, and config file is not there, configuration ends up as default. */ - public function testConfigsAreEmptyIfNoExtraValue() + public function testConfigsAreEmptyIfNoExtraValue(): void { // custom root to make sure wpstarter.json in fixtures root is not loaded @@ -113,7 +113,7 @@ public function testConfigsAreEmptyIfNoExtraValue() /** * Settings in extra settings are loaded. */ - public function testConfigsContainsExtraIfThere() + public function testConfigsContainsExtraIfThere(): void { $extra = [ComposerPlugin::EXTRA_KEY => ['foo' => 'bar']]; @@ -125,12 +125,12 @@ public function testConfigsContainsExtraIfThere() /** * Settings form a JSON file passed as configs are loaded. */ - public function testConfigsLoadedFromFileIfNamePassed() + public function testConfigsLoadedFromFileIfNamePassed(): void { // @see /tests/fixtures/paths-root/custom-starter.json $extra = [ComposerPlugin::EXTRA_KEY => 'custom-starter.json']; - $config = $this->executeExtractConfig($extra); + $config = $this->executeExtractConfig($extra); static::assertSame('copy', $config['content-dev-op']); static::assertSame('./public/boot-hooks.php', $config['early-hook-file']); @@ -139,10 +139,10 @@ public function testConfigsLoadedFromFileIfNamePassed() /** * Settings form default JSON are loaded if file is found. */ - public function testConfigsLoadedFromDefaultFileIfThere() + public function testConfigsLoadedFromDefaultFileIfThere(): void { // @see /tests/fixtures/paths-root/wpstarter.json - $config = $this->executeExtractConfig([]); + $config = $this->executeExtractConfig([]); static::assertSame(false, $config['unknown-dropins']); static::assertSame('4.5.1', $config['wp-version']); @@ -151,7 +151,7 @@ public function testConfigsLoadedFromDefaultFileIfThere() /** * Settings loaded from default JSON are loaded and merged with settings in extra. */ - public function testConfigsLoadedFromDefaultFileAreMerged() + public function testConfigsLoadedFromDefaultFileAreMerged(): void { $extra = [ ComposerPlugin::EXTRA_KEY => [ @@ -161,7 +161,7 @@ public function testConfigsLoadedFromDefaultFileAreMerged() ]; // @see /tests/fixtures/paths-root/wpstarter.json - $config = $this->executeExtractConfig($extra); + $config = $this->executeExtractConfig($extra); static::assertSame(false, $config['unknown-dropins'], 'File wins over extra'); static::assertSame('bar', $config['foo']);