PHP Coding Standards for Wikit
Overview
All PHP code in the Wikit framework and WDG projects MUST follow WordPress Coding Standards (WPCS) with specific WDG customizations. This ensures consistency, security, and maintainability across all projects.
Namespace Conventions
Core Namespaces
php
// Wikit Core - Base framework classes
namespace WDG\Core;
// Wikit App - Application layer
namespace WDG\App;
namespace WDG\App\PostType;
namespace WDG\App\Taxonomy;
// Wikit Theme - Theme-specific classes
namespace WDG\Theme;
// Project-Specific - Client customizations
namespace ProjectName\PostType;
namespace ProjectName\Feature;Namespace Rules
- Always use proper namespace hierarchy
- Match namespace to directory structure
- Use PascalCase for namespace segments
- Never use
globalnamespace for new code
File Organization
Directory Structure
wikit-app/
├── src/
│ ├── PostType/ # Custom post types
│ ├── Taxonomy/ # Custom taxonomies
│ ├── functions/ # Namespaced functions
│ └── *.php # Feature classesFile Naming
- Classes: PascalCase matching class name (
PostType.php) - Functions: snake_case (
functions/utilities.php) - Templates: kebab-case (
template-parts/) - Includes: lowercase (
inc/)
Code Style
Indentation and Spacing
php
<?php
// CORRECT: Use tabs for indentation
class Example {
protected $property;
public function method() {
if ( $condition ) {
// Tab indented
}
}
}
// INCORRECT: Spaces for indentation
class Example {
protected $property; // Wrong: spaces
}Brackets and Control Structures
php
// CORRECT: WordPress style
if ( $condition ) {
do_something();
} elseif ( $other ) {
do_other();
} else {
do_default();
}
// CORRECT: Space inside parentheses for control structures
if ( ! empty( $value ) ) {
process( $value );
}
// INCORRECT: No spaces
if(!empty($value)){
process($value);
}Arrays
php
// CORRECT: Short array syntax
$array = [
'key' => 'value',
'other' => 'data',
];
// CORRECT: Aligned array values
$args = [
'post_type' => 'product',
'posts_per_page' => 10,
'orderby' => 'date',
];Class Structure
Basic Class Template
php
<?php
/**
* Class description
*
* @package WDG\App\Feature
*/
namespace WDG\App\Feature;
use WDG\Core\PostType;
use WDG\Core\SingletonTrait;
/**
* Feature implementation class
*/
class FeatureName {
use SingletonTrait;
/**
* Property description
*
* @var string
*/
protected string $property = 'default';
/**
* Constructor
*/
protected function __construct() {
$this->init();
}
/**
* Initialize hooks
*
* @return void
*/
public function init(): void {
add_action( 'init', [ $this, 'register' ] );
}
/**
* Register functionality
*
* @return void
*/
public function register(): void {
// Implementation
}
}PostType Class Template
php
<?php
namespace WDG\App\PostType;
use WDG\Core\PostType;
use WDG\Core\Taxonomy;
class Products extends PostType {
/**
* Post type slug
*
* @var string
*/
protected $post_type = 'product';
/**
* Post type arguments
*
* @var array
*/
protected $args = [
'menu_icon' => 'dashicons-cart',
'supports' => [ 'title', 'editor', 'thumbnail' ],
];
/**
* Initialize
*/
public function init() {
parent::init();
new Taxonomy( 'product_category', [ $this->post_type ] );
}
}WordPress Integration
Hooks and Actions
php
// CORRECT: Namespace-aware hooks
add_action( 'init', __NAMESPACE__ . '\\register_post_types' );
add_filter( 'the_content', [ $this, 'filter_content' ], 10, 1 );
// CORRECT: Static method hooks
add_action( 'init', [ __CLASS__, 'static_method' ] );
// INCORRECT: String class names without namespace
add_action( 'init', 'ClassName::method' ); // Missing namespaceEnqueueing Assets
php
// CORRECT: Proper dependency and version handling
wp_enqueue_script(
'wdg-feature',
get_template_directory_uri() . '/dist/js/feature.js',
[ 'jquery', 'wp-data' ],
filemtime( get_template_directory() . '/dist/js/feature.js' ),
true
);
// CORRECT: Localize with proper escaping
wp_localize_script( 'wdg-feature', 'wdgData', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wdg-feature' ),
] );Security
Data Validation
php
// CORRECT: Validate and sanitize input
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
$title = isset( $_POST['title'] ) ? sanitize_text_field( $_POST['title'] ) : '';
$content = isset( $_POST['content'] ) ? wp_kses_post( $_POST['content'] ) : '';
// CORRECT: Nonce verification
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'action_name' ) ) {
wp_die( 'Security check failed' );
}Output Escaping
php
// CORRECT: Always escape output
echo esc_html( $title );
echo esc_attr( $attribute );
echo esc_url( $url );
echo wp_kses_post( $content );
// CORRECT: Late escaping
$html = sprintf(
'<a href="%s" class="%s">%s</a>',
esc_url( $url ),
esc_attr( $class ),
esc_html( $text )
);Database Queries
php
// CORRECT: Use prepare for custom queries
global $wpdb;
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
$post_type,
'publish'
)
);
// PREFERRED: Use WP_Query when possible
$query = new WP_Query( [
'post_type' => 'product',
'posts_per_page' => 10,
'meta_key' => 'price',
'orderby' => 'meta_value_num',
] );Function Documentation
DocBlocks
php
/**
* Function description
*
* @since 1.0.0
*
* @param string $param Parameter description.
* @param array $options Optional. Options array. Default empty.
* @return bool|WP_Error True on success, WP_Error on failure.
*/
function process_data( string $param, array $options = [] ) {
// Implementation
}Type Declarations
php
// CORRECT: Use type hints and return types
public function process( array $data ): bool {
return true;
}
// CORRECT: Nullable types
public function find( int $id ): ?object {
return $object ?? null;
}
// CORRECT: Mixed for truly variable types
public function get_option( string $key ): mixed {
return get_option( $key );
}PHPCS Configuration
The project uses WordPress-Extra ruleset with WDG customizations:
xml
<rule ref="WordPress-Extra">
<!-- Allow short echo tags -->
<exclude name="Generic.PHP.DisallowShortOpenTag.EchoFound" />
<!-- Allow short array syntax -->
<exclude name="Universal.Arrays.DisallowShortArraySyntax" />
<!-- Custom auto-escaped functions -->
<property name="customAutoEscapedFunctions" type="array">
<element value="svg"/>
<element value="html_attributes"/>
</property>
</rule>Common Patterns
Singleton Implementation
php
class Feature {
use \WDG\Core\SingletonTrait;
protected function __construct() {
// Private constructor
}
}
// Usage
$instance = Feature::instance();Ajax Handler
php
class AjaxHandler {
public function __construct() {
add_action( 'wp_ajax_wdg_action', [ $this, 'handle' ] );
add_action( 'wp_ajax_nopriv_wdg_action', [ $this, 'handle' ] );
}
public function handle(): void {
// Verify nonce
check_ajax_referer( 'wdg_action_nonce', 'nonce' );
// Process
$data = $this->process( $_POST );
// Return JSON
wp_send_json_success( $data );
}
}Do's and Don'ts
DO:
- ✅ Use tabs for indentation
- ✅ Follow WordPress naming conventions
- ✅ Escape all output
- ✅ Validate and sanitize input
- ✅ Use type hints and return types
- ✅ Document all functions and classes
- ✅ Use Wikit base classes when available
- ✅ Follow namespace conventions
DON'T:
- ❌ Use spaces for PHP indentation
- ❌ Echo unescaped variables
- ❌ Trust user input
- ❌ Use extract() or eval()
- ❌ Suppress errors with @
- ❌ Modify core Wikit files
- ❌ Use global variables unnecessarily
- ❌ Mix HTML and PHP logic
Validation Commands
bash
# Check PHP syntax
php -l file.php
# Run PHPCS
vendor/bin/phpcs file.php
# Auto-fix PHPCS issues
vendor/bin/phpcbf file.php
# Run in project
composer run-script phpcs