| name | php-development |
| version | 2.0 |
| last_updated | 2026-08-29T00:00:00.000Z |
| tags | ["php","development","testing","quality","automation"] |
| description | PHP 8.0+ development — XAMPP, RESTful APIs, PDO/MySQL/MariaDB, and authentication. Use when building PHP backends, creating API endpoints, configuring XAMPP, or integrating PHP with databases. |
PHP Development
Optimized for current PHP 8.x releases, PHPUnit 11+, Composer 2.x, and PDO-backed MySQL or MariaDB apps.
Expert guidance for building high-quality PHP applications with PHP 8.0+, PDO for secure database access, RESTful API design, and XAMPP environment configuration following official PHP documentation at https://php.net.
- Leverage native parallel subagent dispatch and 200k+ context windows where available.
Anti-Patterns
- Interpolating SQL directly: Prepared statements are the baseline for correctness and security in PHP data access.
- Mixing request parsing, business rules, and rendering: Tightly coupled scripts are harder to test and evolve into APIs.
- Assuming validation alone prevents XSS: Output encoding still matters when user-controlled content is rendered back to HTML.
Verification Protocol
Before claiming "skill applied successfully":
- Pass/fail: The PHP Development implementation names the target runtime, framework version, and affected files.
- Pass/fail: Build, lint, test, or equivalent local validation is run for the changed surface.
- Pass/fail: Edge cases for errors, dependency drift, and environment differences are addressed or explicitly out of scope.
- Pressure-test scenario: Apply the workflow to a change that passes happy-path tests but fails one boundary condition.
- Success metric: Zero untested success claims; every implementation claim maps to a command or artifact.
Before and After Example
<?php
$stmt = $pdo->query("SELECT * FROM users WHERE email = '$email'");
$user = $stmt->fetch();
$stmt = $pdo->prepare('SELECT id, email, password_hash FROM users WHERE email = :email LIMIT 1');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
Replaces string interpolation with a prepared statement and a narrower result shape.
Activation Conditions
Use symptom -> action triggers: when one matches, apply this skill and verify with the protocol below.
Core PHP Development:
- Building PHP RESTful APIs with proper HTTP methods
- Working with XAMPP (Apache + MySQL + PHP) environment
- Implementing secure database operations with PDO
- Creating authentication and session management systems
- Handling file uploads and form submissions
Database & Data Layer:
- Connecting PHP to MySQL/MariaDB with PDO
- Writing prepared statements to prevent SQL injection
- Implementing transaction handling for data integrity
- Creating repository patterns for data access
- Working with MySQLi vs PDO comparisons
Security & Best Practices:
- Implementing password hashing (password_hash, password_verify)
- Securing against XSS, CSRF, and SQL injection
- Validating and sanitizing user input
- Managing sessions and authentication tokens
- Configuring CORS headers for API access
API Development:
- Designing RESTful endpoints with proper HTTP status codes
- Handling JSON requests and responses
- Implementing middleware for authentication and authorization
- Error handling and logging
- Rate limiting and API versioning
Part 1: PHP 8.0+ Fundamentals
Modern PHP Features
<?php
function createUser(string $name, string $email, bool $isAdmin = false): User {
return new User($name, $email, $isAdmin);
}
$user = createUser(email: 'user@example.com', name: 'John Doe');
function processValue(string|int|float $value): string {
return (string)$value;
}
$country = $session?->user?->address?->country ?? 'Unknown';
class User {
public function __construct(
public string $name,
,
) {}
}
Type Declarations & Strict Types
<?php
declare(strict_types=1);
class Recipe {
private int $id;
private string $title;
private ?DateTime $createdAt;
public function __construct(int $id, string $title) {
$this->id = $id;
$this->title = $title;
}
public function getTitle(): string {
return $this->title;
}
public function setCreatedAt(?DateTime $date): void {
$this->createdAt = $date;
}
}
function processData(string|array $data): string| {
() ? () : ();
}
Part 2: PDO Database Integration
Database Connection Class
<?php
class Database {
private static ?PDO $instance = null;
public static function getInstance(): PDO {
if (self::$instance === null) {
$host = $_ENV['DB_HOST'] ?? 'localhost';
$dbname = $_ENV['DB_NAME'] ?? 'recipe_sharing_system';
$username = $_ENV['DB_USER'] ?? 'root';
$password = $_ENV['DB_PASSWORD'] ?? '';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
try {
self::$instance = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO:: => PDO::,
PDO:: => ,
]);
} (PDOException ) {
( . ->());
();
}
}
::;
}
}
Prepared Statements for Security
<?php
class UserRepository {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function findByEmail(string $email): ?array {
$stmt = $this->db->prepare(
"SELECT id, email, password_hash, role, status
FROM user
WHERE email = :email LIMIT 1"
);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->execute();
$user = $stmt->fetch();
return $user ?: null;
}
public function create(string $name, string $email, string ): {
= (, PASSWORD_DEFAULT);
= ->db->(
);
->(, , PDO::);
->(, , PDO::);
->(, , PDO::);
->();
() ->db->();
}
{
= ->();
( === ) {
;
}
(!(, [])) {
;
}
(([], PASSWORD_DEFAULT)) {
= (, PASSWORD_DEFAULT);
->([], );
}
([]);
;
}
{
= ->db->(
);
->([ => , => ]);
}
}
Transaction Management
<?php
class RecipeService {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function createRecipeWithDetails(array $recipeData, array $ingredients, array $instructions): int {
try {
$this->db->beginTransaction();
$stmt = $this->db->prepare(
"INSERT INTO recipe (title, description, category, difficulty, prep_time, cook_time, servings, author_id, status, created_at, updated_at)
VALUES (:title, :description, :category, :difficulty, :prep_time, :cook_time, :servings, :author_id, 'pending', NOW(), NOW())"
);
$stmt->execute([
':title' => $recipeData['title'],
':description' => $recipeData['description'],
':category' => $recipeData['category'],
=> [],
=> [],
=> [],
=> [],
=> [],
]);
= () ->db->();
= ->db->(
);
( => ) {
->([
=> ,
=> [],
=> [],
=> [],
=> ,
]);
}
= ->db->(
);
( => ) {
->([
=> ,
=> + ,
=> [],
]);
}
->db->();
;
} ( ) {
->db->();
( . ->());
;
}
}
}
Part 3: RESTful API Development
JSON Response Helpers
<?php
class Response {
public static function json(mixed $data, int $statusCode = 200): never {
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
exit;
}
public static function error(string $message, int $statusCode = 400): never {
self::json([
'success' => false,
'error' => $message,
], $statusCode);
}
public static function success(mixed $data = null, string $message = 'Success'): {
::([
=> ,
=> ,
=> ,
]);
}
}
CORS Middleware
<?php
$allowedOrigins = [
'http://localhost:5173',
'http://localhost:3000',
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: $origin");
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
Authentication Middleware
<?php
function requireAuth(): array {
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
Response::error('Unauthorized: Missing or invalid token', 401);
}
$token = $matches[1];
try {
$payload = JWT::decode($token, $_ENV['JWT_SECRET'], ['HS256']);
return (array) $payload;
} catch (Exception $e) {
Response::error('Unauthorized: Invalid token', 401);
}
}
function requireAdmin(): array {
$user = requireAuth();
if ($user['role'] !== 'admin') {
Response::(, );
}
;
}
API Controller Example
<?php
require_once '../../config/database.php';
require_once '../../middleware/cors.php';
require_once '../../utils/response.php';
class RecipeController {
private PDO $db;
public function __construct() {
$this->db = Database::getInstance();
}
public function index(): void {
$category = $_GET['category'] ?? null;
$difficulty = $_GET['difficulty'] ?? null;
$search = $_GET['search'] ?? null;
$limit = (int)($_GET['limit'] ?? 20);
$offset = (int)($_GET['offset'] ?? 0);
$query = "SELECT r.*, u.name as author_name,
COUNT(DISTINCT rv.id) as view_count,
COUNT(DISTINCT lr.id) as like_count,
AVG(rev.rating) as average_rating
FROM recipe r
JOIN user u ON r.author_id = u.id
LEFT JOIN recipe_view rv ON r.id = rv.recipe_id
LEFT JOIN like_record lr ON r.id = lr.recipe_id
LEFT JOIN review rev ON r.id = rev.recipe_id
WHERE r.status = 'published'";
= [];
( !== ) {
.= ;
[] = ;
}
( !== ) {
.= ;
[] = ;
}
( !== ) {
.= ;
= ;
[] = ;
[] = ;
}
.= ;
= ->db->();
->();
= ->();
::();
}
{
= ->db->(
);
->([ => ]);
= ->();
( === ) {
::(, );
}
= ->db->(
);
->([ => ]);
[] = ->();
::();
}
{
= ();
= ((), );
(([]) || ([])) {
::();
}
= [
=> [],
=> [],
=> [] ?? ,
=> [] ?? ,
=> ()([] ?? ),
=> ()([] ?? ),
=> ()([] ?? ),
=> [],
];
= (->db);
{
= ->(
,
[] ?? [],
[] ?? []
);
::([ => ], , );
} ( ) {
::( . ->(), );
}
}
}
Part 4: Input Validation & Sanitization
Validation Functions
<?php
class Validator {
public static function email(string $email): bool {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public static function string(string $value, int $min = 1, int $max = 255): bool {
$length = strlen($value);
return $length >= $min && $length <= $max;
}
public static function integer(int $value, int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): bool {
return $value >= $min && $value <= ;
}
{
(, , );
}
{
= [];
( ) {
(([])) {
[] = ;
}
}
;
}
{
((), ENT_QUOTES, );
}
}
Validation Example
<?php
function validateRecipeData(array $data): array {
$errors = [];
if (empty($data['title'])) {
$errors[] = 'Title is required';
} elseif (!Validator::string($data['title'], 3, 200)) {
$errors[] = 'Title must be between 3 and 200 characters';
}
if (!empty($data['email']) && !Validator::email($data['email'])) {
$errors[] = 'Invalid email address';
}
if (isset($data['rating']) && !Validator::integer((int)$data['rating'], 1, 5)) {
$errors[] = 'Rating must be between 1 and 5';
}
if (!([]) &&
!::{
[] = ;
}
( => ) {
(()) {
[] = ::();
}
}
[ => , => ];
}
Part 5: Security Best Practices
Password Management
<?php
class PasswordManager {
public static function hash(string $password): string {
return password_hash($password, PASSWORD_DEFAULT);
}
public static function verify(string $password, string $hash): bool {
return password_verify($password, $hash);
}
public static function needsRehash(string $hash): bool {
return password_needs_rehash($hash, PASSWORD_DEFAULT);
}
public static function validateStrength(string $password): array {
$errors = [];
if (() < ) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
;
}
}
Session Management
<?php
class Session {
public static function start(): void {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public static function set(string $key, mixed $value): void {
$_SESSION[$key] = $value;
}
public static function get(string $key, mixed $default = null): mixed {
return $_SESSION[$key] ?? $default;
}
public static function remove(string $key): void {
unset($_SESSION[$key]);
}
public {
= [];
();
(()) {
= ();
((), , () - ,
[], [],
[], []
);
}
}
{
();
}
}
CSRF Protection
<?php
class CsrfProtection {
public static function generateToken(): string {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function validateToken(string $token): bool {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
public static function invalidateToken(): void {
unset($_SESSION['csrf_token']);
}
public static function getInputField(): {
= ::();
;
}
}
Part 6: XAMPP Configuration
.htaccess for URL Rewriting
RewriteEngine On
# Redirect trailing slashes
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle API routes
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/(.*)$ api/index.php [QSA,L]
# Handle frontend routes (SPA)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.html [QSA,L]
PHP Configuration (php.ini)
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
error_log = "C:/xampp/php/logs/php_error.log"
upload_max_filesize = 10M
post_max_size = 10M
extension=pdo_mysql
extension=mysqli
session.save_handler = files
session.save_path = "C:/xampp/tmp"
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 0
session.use_only_cookies = 1
date.timezone = "Asia/Bangkok"
PHP Development Best Practices
Code Style (PSR-12)
Security
API Design
Database
Common Pitfalls
- Interpolating SQL directly: Prepared statements are the baseline for correctness and security in PHP data access.
- Mixing request parsing, business rules, and rendering: Tightly coupled scripts become difficult to test or migrate into APIs.
- Ignoring output encoding: Input validation alone does not protect against XSS when data is rendered back to users.
References & Resources
Documentation
Examples
Scripts
Official Documentation
PHP Standards
Security Resources
Cross-Client Portability
This skill is written to stay usable across GitHub Copilot, Claude Code, and Codex.
- GitHub Copilot: keep the folder in a Copilot-visible skill path or wrap the
workflow in project instructions when folder discovery is unavailable.
- Claude Code: keep the folder in a local skills directory or a compatible plugin source.
- Codex: install or sync the folder into
$CODEX_HOME/skills/php-development and restart Codex after major changes.
MCP Availability And Fallback
Preferred MCP Server: None required
- Fallback prompt: "Use the PHP Development skill without MCP. Rely on the local
SKILL.md, bundled references or scripts, and manual verification. Show the exact commands, evidence, and final checks you used before concluding."
- If the current host does not expose a matching server, use the bundled references, scripts, native toolchain, and manual workflow already described in this skill.
- Treat direct local verification, rendered output, logs, tests, or screenshots as the fallback evidence path before completion.
Related Skills
- sql-development: Use it when the workflow also needs SQL query, schema, and performance tuning work.
- code-quality: Use it when the workflow also needs two-stage review (spec compliance first, then code quality), maintainability, and refactoring guidance.
- systematic-debugging: Use it when the workflow also needs root-cause debugging before proposing fixes.
- development-workflow: Use it when the workflow also needs planning, quality gates, and delivery tracking.