Skip to content

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 global namespace for new code

File Organization

Directory Structure

wikit-app/
├── src/
│   ├── PostType/       # Custom post types
│   ├── Taxonomy/       # Custom taxonomies
│   ├── functions/      # Namespaced functions
│   └── *.php           # Feature classes

File 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 namespace

Enqueueing 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

Released under the MIT License.