PHP code example of apermo / apermo-coding-standards

1. Go to this page and download the library: Download apermo/apermo-coding-standards library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

apermo / apermo-coding-standards example snippets


// Bad — endpoint is publicly accessible
register_rest_route( 'myplugin/v1', '/items', [
    'methods'  => 'GET',
    'callback' => 'get_items',
] );

// Good — access is controlled
register_rest_route( 'myplugin/v1', '/items', [
    'methods'             => 'GET',
    'callback'            => 'get_items',
    'permission_callback' => function () {
        return current_user_can( 'read' );
    },
] );

// Bad — deprecated, never actually sanitized
$name = filter_input( INPUT_POST, 'name', FILTER_SANITIZE_STRING );

// Good — proper sanitization
$name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) );

// Bad — silently autoloaded on every page load
add_option( 'my_plugin_log', $data );
update_option( 'my_plugin_cache', $value );

// Good — explicit autoload control
add_option( 'my_plugin_log', $data, '', false );
update_option( 'my_plugin_cache', $value, false );

// Good — using named parameter (PHP 8.0+)
add_option( 'my_plugin_setting', $val, autoload: true );

// Bad
die;
die();
die( 'message' );
exit;

// Good
exit();
exit( 1 );
exit( 'message' );

// Bad
$pt = get_post_type();
$cb = function () {};

// Good
$post_type = get_post_type();
$callback = function () {};
$id = get_the_ID(); // allowed (in default allowlist)

// Bad — nested closures
$fn = function () {
    $inner = function () {
        return 1;
    };
};

// Bad — nested arrow functions
$fn = fn() => fn() => 1;

// Good — extract to named function
function get_one(): int {
    return 1;
}
$fn = function () {
    $inner = get_one();
};

/** Disabled: Plugin doesn't support PHP 8.3 yet. */
// add_action( 'init', 'my_func' );

/** Review (2026-02-14): Found during refactor, unclear if still needed. */
// register_post_type( 'legacy_type', $args );

// Bad — 2+ consecutive empty lines at file/class level
$a = 1;


$b = 2;

// Good — at most 1 empty line
$a = 1;

$b = 2;

// Bad
include 'file.php';
include_once 'helpers.php';

// Good

// Warning — 4 levels of associative nesting (literal)
$order = [
    'customer' => [
        'address' => [
            'details' => [
                'city' => 'Berlin',
            ],
        ],
    ],
];

// Warning — 11 associative keys (literal)
$user = [
    'id' => 1, 'name' => 'John', 'email' => '[email protected]',
    'role' => 'admin', 'active' => true, 'verified' => true,
    'locale' => 'en', 'tz' => 'UTC', 'theme' => 'dark',
    'mfa' => true, 'notifications' => 'email',
];

// OK — numeric arrays are ignored
$grid = [ [ 1, 2 ], [ 3, 4 ] ];

// Error — parameter default with 6 top-level keys (ComplexParameterKeys)
function make( array $opts = [
    'a' => 1, 'b' => 2, 'c' => 3,
    'd' => 4, 'e' => 5, 'f' => 6,
] ): void {}

// Error — @param shape exceeds parameterMaxDepth (ComplexParameterDepth)
/**
 * @param array{a: array{b: array{c: int}}} $opts
 */
function handle( array $opts ): void {}

// Good — refactor to a DTO
function handle( Options $opts ): void {}

// Bad — hidden dependency on global state
function get_title() {
    global $post;
    return $post->post_title;
}

// Good — explicit dependency
function get_title( WP_Post $post ) {
    return $post->post_title;
}

// Bad — implicit global access inside function
function render() {
    $title = get_the_title();        // error: MissingArgument
    $id    = get_the_ID();           // error: NoPostParameter
    get_the_title( null );           // error: NullArgument
    get_the_title( $post->ID );      // warning: IntegerArgument
}

// Good — explicit post argument
function render( WP_Post $post ) {
    $title = get_the_title( $post );
    $id    = $post->ID;
}

// Bad — untyped data bags
$config = (object) [ 'host' => 'localhost', 'port' => 3306 ];
$dto = new \stdClass();

// Good — typed classes
class DbConfig {
    public function __construct(
        public string $host,
        public int $port,
    ) {}
}
$config = new DbConfig( 'localhost', 3306 );

// Bad — no documentation
do_action( 'my_plugin_init', $config );

// Good — documented hook
/**
 * Fires after plugin initialization.
 *
 * @param array $config Plugin configuration.
 */
do_action( 'my_plugin_init', $config );

// Bad — filter missing @return
/**
 * @param string $title The title.
 */
apply_filters( 'my_title', $title );

// Good — filter with @return
/**
 * Filters the display title.
 *
 * @param string $title The title.
 *
 * @return string Filtered title.
 */
apply_filters( 'my_title', $title );

// Warning — "Display" doesn't end in s (NotThirdPerson)
/** Display the date. */
function render_date(): void {}

// Warning — "Process" is a bare infinitive (BareInfinitive)
/** Process the queue. */
function handle_queue(): void {}

// Warning — "Allows you to" is an anti-pattern (AntiPattern)
/** Allows you to modify the post content. */

// Good — third-person singular
/** Displays the last modified date for a post. */
function render_date(): void {}

/** Filters the post content before rendering. */

/** Fires after the plugin is initialized. */

/** Callback for the save_post action. */  // whitelist passes

// Skipped — property docblock (noun-form is idiomatic)
class User {
    /** The user's display name. */
    public string $name = '';
}

// OK — all single-space
$a = 1;
$bb = 2;
$ccc = 3;

// OK — all aligned
$a   = 1;
$bb  = 2;
$ccc = 3;

// Warning (fixable) — mixed styles
$short = 1;
$veryLongName = 2;
$x            = 3;

// Error — over-aligned
$short     = 1;
$medium    = 2;
$long      = 3;

// OK — all single-space
$config = [
    'host' => 'localhost',
    'port' => 3306,
    'database' => 'mydb',
];

// OK — all aligned
$config = [
    'host'     => 'localhost',
    'port'     => 3306,
    'database' => 'mydb',
];

// Warning (fixable) — mixed styles
$config = [
    'host' => 'localhost',
    'port' => 3306,
    'database_name' => 'mydb',
    'x'             => 'value',
];

// Error — over-aligned
$config = [
    'a'      => 1,
    'bb'     => 2,
    'ccc'    => 3,
];

// Bad
if ( $a ) {
    // ...
} else if ( $b ) {
    // ...
}

// Good
if ( $a ) {
    // ...
} elseif ( $b ) {
    // ...
}
xml
<rule ref="WordPress.WP.I18n">
    <properties>
        <property name="text_domain" type="array">
            <element value="my-plugin"/>
        </property>
    </properties>
</rule>
xml
<!-- Downgrade to warning -->
<rule ref="Apermo.PHP.NoFilterSanitizeString.Found">
    <type>warning</type>
</rule>
xml
<!-- Add custom keywords -->
<rule ref="Apermo.PHP.ExplainCommentedOutCode">
    <properties>
        <property name="keywords" value="Disabled,Kept,Debug,Review,WIP,Deprecated"/>
    </properties>
</rule>

<!-- Downgrade to warning instead of error -->
<rule ref="Apermo.PHP.ExplainCommentedOutCode">
    <properties>
        <property name="error" value="false"/>
    </properties>
</rule>
xml
<!-- Disable the (object) cast warning -->
<rule ref="Apermo.PHP.ForbiddenObjectCast.Found">
    <severity>0</severity>
</rule>

<!-- Disable the new stdClass() warning -->
<rule ref="SlevomatCodingStandard.PHP.ForbiddenClasses">
    <severity>0</severity>
</rule>
xml
<!-- Disable entirely -->
<rule ref="Apermo.Hooks.RequireHookDocBlock">
    <severity>0</severity>
</rule>

<!-- Only quireHookDocBlock.MissingReturn">
    <severity>0</severity>
</rule>