.ai/skills/woocommerce-dev-cycle/php-linting-patterns.md
NEVER run linting on the entire codebase. Always lint specific files, changed files or staged files only.
# ✅ CORRECT: Check only changed files at the branch level
pnpm lint:php:changes
# ✅ CORRECT: Check only changed files that are staged
pnpm lint:php:changes:staged
# ✅ CORRECT: Lint specific file
pnpm lint:php -- path/to/file.php
pnpm lint:php:fix -- path/to/file.php
# ❌ WRONG: Lints entire codebase
pnpm lint:php
pnpm lint:php:fix
| Issue | Wrong | Correct |
|---|---|---|
| Translators comment | Before return | Before function call |
| File docblock (PSR-12) | After declare() | Before declare() |
| Indentation | Spaces | Tabs only |
| Array alignment | Inconsistent | Align => with context |
| Equals alignment | Inconsistent | Match surrounding style |
Translators comments must be placed immediately before the translation function call, not before the return statement.
/* translators: %s: Gateway name. */
return sprintf(
esc_html__( '%s is not supported.', 'woocommerce' ),
'Gateway'
);
return sprintf(
/* translators: %s: Gateway name. */
esc_html__( '%s is not supported.', 'woocommerce' ),
'Gateway'
);
return sprintf(
/* translators: 1: Gateway name, 2: Country code. */
esc_html__( '%1$s is not available in %2$s.', 'woocommerce' ),
$gateway_name,
$country_code
);
File docblocks must come before the declare() statement, not after.
<?php
declare( strict_types=1 );
/**
* File docblock
*
* @package WooCommerce
*/
<?php
/**
* File docblock
*
* @package WooCommerce
*/
declare( strict_types=1 );
When creating mock classes that must match external class names, use phpcs:disable comments:
if ( ! class_exists( 'WC_Payments_Utils' ) ) {
/**
* Mock class for testing.
*
* phpcs:disable Squiz.Classes.ClassFileName.NoMatch
* phpcs:disable Suin.Classes.PSR4.IncorrectClassName
* phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
*/
class WC_Payments_Utils {
/**
* Mock implementation.
*/
public static function supported_countries() {
return array( 'US', 'GB' );
}
}
}
Use tabs for continuation lines in multi-line conditions:
// Correct - tabs for continuation
if ( class_exists( '\WC_Payments_Utils' ) &&
is_callable( '\WC_Payments_Utils::supported_countries' ) ) {
// code
}
// Also correct - align with opening parenthesis
if ( class_exists( '\WC_Payments_Utils' ) &&
is_callable( '\WC_Payments_Utils::supported_countries' ) ) {
// code
}
When creating closures with parameters required by signature but unused, use unset() to avoid PHPCS errors:
// ❌ WRONG - PHPCS error: Generic.CodeAnalysis.UnusedFunctionParameter
'callback' => function ( string $return_url ) {
return array( 'success' => true );
},
// ✅ CORRECT - unset unused parameters
'callback' => function ( string $return_url ) {
unset( $return_url ); // Avoid parameter not used PHPCS errors.
return array( 'success' => true );
},
'callback' => function ( $arg1, $arg2, $arg3 ) {
unset( $arg1, $arg2 ); // Avoid parameter not used PHPCS errors.
return $arg3;
},
Reference: tests/php/src/Internal/Admin/Settings/PaymentsRestControllerIntegrationTest.php:1647-1655
Align => arrows consistently within each array context:
// Correct - aligned arrows
$options = array(
'gateway_id' => 'stripe',
'enabled' => true,
'country_code' => 'US',
);
// Also correct - no alignment for short arrays
$small = array(
'id' => 123,
'name' => 'Test',
);
Match the surrounding code style:
// When surrounding code aligns, align:
$gateway_id = 'stripe';
$enabled = true;
$country_code = 'US';
// When surrounding code doesn't align, don't align:
$gateway_id = 'stripe';
$enabled = true;
$country_code = 'US';
Always use tabs, never spaces, for indentation.
// ✅ Correct - tabs for indentation
public function process_payment( $order_id ) {
→ $order = wc_get_order( $order_id );
→
→ if ( ! $order ) {
→ → return false;
→ }
→
→ return true;
}
// ❌ Wrong - spaces for indentation
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return false;
}
return true;
}
Run linting on changed files:
pnpm lint:php:changes
Auto-fix what you can:
pnpm lint:php:fix -- path/to/file.php
Review remaining errors - Common issues that require manual fixing:
unset())Address remaining issues manually
Verify the output is clean:
pnpm lint:php -- path/to/file.php
Commit
# Check changed files
pnpm lint:php:changes
# Check specific file
pnpm lint:php -- src/Internal/Admin/ClassName.php
# Fix specific file
pnpm lint:php:fix -- src/Internal/Admin/ClassName.php
# Check with error details
vendor/bin/phpcs -s path/to/file.php