Install
$ agentstack add skill-xobotyi-cc-foundry-php ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
PHP
Strict types, explicit contracts, no magic. If a class needs a docblock to explain what its properties do, the properties are named wrong.
PHP 8.5+ is the baseline. Use modern syntax unconditionally — union types, enums, readonly classes, property hooks, named arguments, match, pipe operator. No backward compatibility with older PHP versions unless the project explicitly requires it.
Every PHP file starts with declare(strict_types=1).
References
- Type system →
${CLAUDE_SKILL_DIR}/references/typing.md— Union/intersection/DNF types, nullable patterns, typed
properties and constants, coercion rules, variance
- OOP patterns →
${CLAUDE_SKILL_DIR}/references/oop.md— Interfaces, traits, readonly, property hooks, enums,
constructor promotion, lazy objects, magic methods
- Concurrency →
${CLAUDE_SKILL_DIR}/references/concurrency.md— Fiber API, generator coroutines, comparison table,
async library guidance
- Packaging →
${CLAUDE_SKILL_DIR}/references/packaging.md— composer.json templates, version constraints, project
layouts, namespace conventions
Naming
- Classes, interfaces, traits, enums — PascalCase:
UserService,Renderable,Status - Methods, functions — camelCase:
findById,getFullName - Properties, variables — camelCase:
$userName,$isActive - Constants (class and global) — UPPERSNAKECASE:
MAX_RETRIES,DEFAULT_LOCALE - Namespaces — PascalCase segments:
App\Http\Controller - Enum cases — PascalCase:
Status::Active,Suit::Hearts
- Descriptive names.
$userCountnot$n. Short names ($i,$k,$v) only in tiny scopes (loops, array
operations).
- No redundant context.
$car->makenot$car->carMake. - Boolean names:
is/has/can/shouldprefix:$isValid,$hasAccess. - Abbreviations as words.
HttpClientnotHTTPClient,JsonParsernotJSONParser. Treat abbreviations and
acronyms as regular words — uppercase first letter only (PER-CS).
- No underscore prefix for protected/private visibility. Visibility modifiers exist for that.
Type Declarations
PHP 8.5+ provides a complete type system. Use it everywhere.
Core Rules
- Type all public API boundaries — function parameters, return types, class properties, class constants.
Internal/private code benefits from types too.
declare(strict_types=1)in every file. No exceptions.- Short type names:
bool,int,float,string. Neverboolean,integer,double. - Union types with
|:string|int,Foo|null. Prefer?Tfor single-type nullable. - Intersection types with
&:Countable&Traversable. Class/interface types only. - DNF types:
array|(ArrayAccess&Traversable)— union of intersections in parentheses. voidreturn: annotate on functions that return nothing.neverreturn: functions that always throw or exit.- Avoid
mixed— it disables type safety. Useobjectwhen you mean "any object." Usemixedonly at true interop
boundaries with untyped code.
nulllast in unions:string|int|null, notnull|string|int.
Typed Properties
- Every class property gets a type declaration.
- Typed properties must be initialized before access — use constructor promotion, default values, or constructor
assignment.
callablecannot be used as a property type. UseClosureinstead.
Typed Constants (8.3+)
class Config
{
public const int MAX_RETRIES = 3;
public const string DEFAULT_LOCALE = 'en';
protected const float TAX_RATE = 0.21;
}
- Type all class constants. Interface constants especially — they enforce the contract at compile time.
Variance
- Parameters are contravariant — child class can accept wider types.
- Return types are covariant — child class can return narrower types.
mixedreturn can be narrowed to any type in a subclass.
See ${CLAUDE_SKILL_DIR}/references/typing.md for the complete type system reference.
Enumerations
- Use enums for categorical constants. Never bare strings or ints as pseudo-enums.
- Backed enums (
stringorint) when the value must interoperate with external systems (JSON, database, API). from()throws on invalid value;tryFrom()returnsnull. Choose based on whether invalid input is a caller
error or expected.
- Enums can implement interfaces and define methods. Use this for behavior tied to the enum's domain.
- Enums cannot have state (no properties), cannot be extended, cannot be
new'd. - Dynamic access:
Status::{$name}(8.3+) for variable-based case resolution.
enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
case Suspended = 'suspended';
public function label(): string
{
return match ($this) {
self::Active => 'Active',
self::Inactive => 'Inactive',
self::Suspended => 'Suspended',
};
}
}
Classes
Properties and Visibility
- Explicit visibility on everything — properties, methods, constants.
- Constructor promotion for data-carrying classes:
``php class User { public function __construct( public readonly string $name, private string $email, protected int $age = 0, ) {} } ``
- Readonly properties (8.1+) for immutable state. Must have a type declaration.
- Readonly classes (8.2+) — all properties implicitly readonly, no dynamic properties.
- Asymmetric visibility (8.4+) —
public protected(set)for publicly readable, internally writable properties. - Property hooks (8.4+) —
get/setlogic on properties. Use instead of trivial getter/setter methods.
Incompatible with readonly.
Inheritance and Composition
- Composition over inheritance. Use inheritance only for true "is-a" relationships.
- Interfaces for contracts. All interface methods are public. As of 8.4, interfaces can declare property
requirements.
- Abstract classes when you need shared implementation alongside a contract.
- Traits for horizontal reuse. Never use traits as a substitute for interfaces. One
usestatement per trait, each
on its own line.
#[Override](8.3+) on every method that overrides a parent or implements an interface method. Catches signature
drift at compile time.
super()equivalent: always useparent::method(). Never hardcode grandparent class names.
Magic Methods
- Avoid property overloading (
__get,__set) in new code. Typed properties with hooks are strictly better. __toString()— define when string conversion has meaningful semantics.__invoke()— for single-method objects that act as callables.__serialize()/__unserialize()— prefer over__sleep()/__wakeup().
Object Patterns
- Value objects —
readonly classwith constructor promotion. Immutable by default. - DTOs — readonly classes with public properties. No behavior.
- Service classes — constructor injection for dependencies, no public state.
- Lazy objects (8.4+) — defer initialization via
ReflectionClass::newLazyGhost().
Functions
- Early return. Guard clauses first, happy path flat. Reduce nesting.
- One function, one job. If the name contains "and", split it.
- Type all parameters and return types on public functions.
- Named arguments for functions with boolean flags or many optional parameters:
createUser(name: 'John', admin: true).
matchoverswitch—matchis an expression, uses strict comparison, and does not fall through.- Pipe operator (8.5+) for functional chaining:
``php $result = $input |> trim(...) |> strtolower(...) |> ucfirst(...); ``
- First-class callables with
...syntax:array_map(strlen(...), $strings). - Arrow functions for short closures:
fn($x) => $x * 2. Arrow functions capture by value, not by reference. - Closures for multi-statement callbacks. Use
useto capture outer variables. Preferstatic function/
static fn when $this is not needed.
- Closures in constants (8.5+) — static closures and first-class callables are valid in constant expressions,
default values, and attributes.
Error Handling
- Be specific. Catch the narrowest exception type:
catch (InvalidArgumentException)notcatch (Exception). - Never bare
catch (\Throwable)at arbitrary depths. Use at application boundaries only (controllers, CLI entry
points, queue workers).
throwis an expression (8.0+):$value ?? throw new InvalidArgumentException().- Chain exceptions.
throw new AppException('context', previous: $e)preserves the original cause. - Custom exception hierarchy:
``php class AppException extends \RuntimeException {} class NotFoundException extends AppException {} class ValidationException extends AppException {} ``
- Prefer
\RuntimeExceptionsubtree for application errors.\LogicExceptionsubtree for programming errors (wrong
arguments, unimplemented methods).
- Error strings: lowercase, no trailing punctuation. They compose in chains:
"user not found: invalid ID format". #[Deprecated]attribute (8.4+) on functions, methods, and constants to emitE_USER_DEPRECATEDwhen called.#[NoDiscard]attribute (8.5+) on functions whose return value must be consumed. Use(void)cast to
intentionally suppress.
finallyfor unconditional cleanup. But prefer RAII-style patterns (destructors, resource wrappers) when
possible.
Strings
- Double-quoted interpolation for simple variables:
"Hello, {$name}". sprintf()for complex formatting:sprintf('Item %d: %s', $id, $name).- Heredoc for multiline strings. Nowdoc (
$value] = $assoc.
Match Expression
$result = match ($status) {
Status::Active => 'active',
Status::Inactive, Status::Suspended => 'inactive',
default => throw new \UnexpectedValueException("Unknown status: {$status->value}"),
};
matchis an expression — it returns a value. Use instead ofswitch.- Strict comparison (
===) — no type coercion. - No fallthrough — each arm is isolated.
- Multiple conditions per arm with commas.
- Exhaustiveness — always include
defaultunless provably exhaustive. Unmatched value throws
UnhandledMatchError.
Closures and Callables
- First-class callable syntax (8.1+):
strlen(...),$obj->method(...),ClassName::method(...). - Arrow functions (
fn) for single-expression closures — auto-captures by value. - Static closures (
static fn,static function) when$thisis not needed — saves memory, prevents accidental
binding.
Closure::bind()andClosure::fromCallable()for advanced callable manipulation.- Type hint callables as
Closurein property types (notcallable).
Packaging and Toolchain
Composer
composer.jsonis the single source of truth for project metadata, dependencies, autoloading, and scripts.- Caret
^constraints for dependencies:"vendor/package": "^2.0". - Lock file: commit for applications, skip for libraries.
- PSR-4 autoloading: map namespace prefixes to directories.
- Separate
autoload-devfor test namespaces.
Project Structure
my-project/
├── composer.json
├── composer.lock
├── src/
│ └── ... (PSR-4: App\)
├── tests/
│ ├── Unit/
│ ├── Integration/
│ └── bootstrap.php
├── config/
├── public/
│ └── index.php
└── var/
├── cache/
└── log/
src/— application code, one class per file, PSR-4 mapped.tests/— mirrorssrc/structure withUnit/andIntegration/separation.public/— web root, single entry point (index.php).var/— generated files (cache, logs). Git-ignored.vendor/— Composer dependencies. Git-ignored.
File Header
method()`.
- **Compound types:** no spaces around `|` and `&`. Parentheses for DNF without internal spaces.
- **Empty exception classes** on one line: `class NotFoundException extends AppException {}`
## Application
When **writing** PHP code: apply all conventions silently — don't narrate each rule. If an existing codebase contradicts
a convention, follow the codebase and flag the divergence once.
When **reviewing** PHP code: cite the specific violation and show the fix inline. Don't lecture — state what's wrong and
how to fix it.
Bad: "According to PHP best practices, you should use stricttypes declaration at the top of every file..." Good: "Missing declare(stricttypes=1)."
## Code Navigation — LSP Required
An Intelephense LSP server is configured for `.php` and `.phtml` files. **Always use LSP tools for code navigation
instead of Grep or Glob.** LSP understands PHP's namespace system, type inference, scope rules, and Composer autoload
boundaries — text search does not.
### Tool Routing
- **Find where a function/class is defined** → `goToDefinition` — resolves `use` statements, aliases, namespace paths
- **Find all usages of a symbol** → `findReferences` — scope-aware, no false positives from string matches
- **Get type signature or docs** → `hover` — instant type info without reading source files
- **List all symbols in a file** → `documentSymbol` — structured output: classes, methods, constants
- **Find a symbol by name across project** → `workspaceSymbol` — searches all namespaces and Composer dependencies
- **Find concrete classes implementing an interface** → `goToImplementation` — knows the type hierarchy
- **Find what calls a function** → `incomingCalls` — precise call graph across namespace boundaries
- **Find what a function calls** → `outgoingCalls` — structured dependency map
**Grep/Glob remain appropriate for:** text in comments, string literals, log messages, TODO markers, config values, env
vars, file name patterns, URLs, error message text — anything that isn't a PHP identifier.
When spawning subagents for PHP codebase exploration, instruct them to use LSP tools. Subagents have access to the same
LSP server.
## Integration
The **coding** skill governs workflow (discovery, planning, verification); this skill governs PHP implementation
choices. The **phpunit** skill governs testing conventions — both are active simultaneously when writing PHP tests.
**Strict types everywhere. Types on everything. If PHP can check it at compile time, make it do so.**
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [xobotyi](https://github.com/xobotyi)
- **Source:** [xobotyi/cc-foundry](https://github.com/xobotyi/cc-foundry)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.