ccppccpp home

Profiles

Backend PHP

PHP services and APIs (Symfony, Laravel, Doctrine, PDO) with Composer-true autoloading and platforms, explicit dependencies, handled failures, safe input, SQL, shell and secrets, owned transactions and evidenced completion. Rule IDs PHP-001–PHP-080; NEED TO blocks.

Set this profile

ccpp cpo profile set backend-php

Inside Claude Code, prefix the command with ! to run it in the session. Downloading a profile needs a Builder or Team plan; listing them does not.

Policies

78 policies, grouped by what they guard.

Secrets & private data 3

Credentials, keys and personal data stay out of files, logs, commands and the conversation.

  • php036-sensitive-logging

    PHP-036 · Log calls never receive passwords, tokens, secrets, card data, authorization headers or whole request payloads

  • php044-weak-password-hashing

    PHP-044 · Passwords are hashed with password_hash/the framework hasher, never md5, sha1, a plain hash, crypt or reversible encoding

  • php047-credential-literals

    PHP-047 · Real credentials (passwords, API keys, tokens, DSN passwords) are loaded from secret configuration, never written as literals

Authentication & authorization 4

Who may do what, from roles, privileges and permissions to CSRF, CORS and verified tokens.

  • php041-authorization-removed

    PHP-041 · The user approves removing authorization checks or making a route or firewall public

  • php041-unprotected-actions

    PHP-041 · Every public action of a declared protected controller checks authorization (off until protectedEndpointPaths is set)

  • php042-unscoped-object-lookups

    PHP-042 · A controller loading an object by a client-supplied ID checks ownership or permission for that object (review)

  • php043-csrf-disabled

    PHP-043 · The user approves disabling CSRF protection (forms, framework config, middleware exceptions)

Untrusted input 8

Requests, downloads and tool output are validated before they reach SQL, a shell, eval or a deserializer.

  • php025-coercing-raw-input

    PHP-025 · Request values are validated before a cast or intval() turns garbage into a valid-looking scalar

  • php026-assert-as-validation

    PHP-026 · Input and security checks are runtime guards, never assert() (disabled in production by zend.assertions=-1)

  • php039-unvalidated-payloads

    PHP-039 · A controller that reads a raw request payload validates it server-side (validator, form, mapped DTO; review)

  • php040-mass-assignment

    PHP-040 · Request data never populates models wholesale (create/fill/update with all input, empty $guarded, unguard, OBJECT_TO_POPULATE without groups)

  • php045-unserialize-external-input

    PHP-045 · Request, cookie or other external data never reaches unserialize()

  • php046-shell-command-injection

    PHP-046 · Shell commands are never built from runtime values; processes take an argv array (Symfony Process, proc_open array)

  • php050-sql-interpolation

    PHP-050 · SQL and DQL text never interpolates or concatenates runtime values; values are bound parameters

  • php051-dynamic-identifiers

    PHP-051 · Table, column and sort identifiers in SQL come from an allowlist, never from the request or an unchecked variable

Network & TLS 3

Outbound data, verified certificates, destinations taken from requests and services exposed to others.

  • php048-tls-verification-config

    PHP-048 · Production HTTP client configuration keeps TLS peer and hostname verification on (Symfony framework.http_client)

  • php048-tls-verification-disabled

    PHP-048 · Production HTTP clients keep TLS peer and hostname verification on (curl, Guzzle, Symfony HttpClient, streams, Laravel Http)

  • php049-request-controlled-destinations

    PHP-049 · Outbound requests do not go to a URL taken straight from the request without a destination policy (SSRF; review)

Weakened checks 2

Suppressions, disabled lint or CI steps and relaxed strictness need a reason or the user's approval.

  • php077-unexplained-suppressions

    PHP-077 · A new inline suppression names the rule and says why (@phpstan-ignore identifier (reason), @psalm-suppress Issue with a reason, phpcs:ignore Sniff)

  • php077-weakened-checks

    PHP-077 · The user approves changes that lower PHPStan/Psalm strictness, grow baselines or ignores, exclude tests or stop CI from failing on checks

Type safety 5

No escapes from the type checker (any, unchecked casts, non-null assertions) and explicit contracts.

  • php021-missing-native-types

    PHP-021 · New functions, methods and properties declare native parameter, return and property types

  • php022-broad-types

    PHP-022 · New public contracts narrow mixed and object when the accepted or returned values are known (review)

  • php023-untyped-collections

    PHP-023 · New public methods taking or returning array/iterable document element types or the shape (list<T>, array<K, V>, array{…})

  • php024-misleading-var-annotations

    PHP-024 · An inline @var does not drop the null a call may return (intelephense resolves the call's type)

  • php031-loose-comparisons

    PHP-031 · New comparisons with null, booleans, '' or 0 and new in_array/array_search calls are strict

Error handling 3

Failures are propagated or handled, never swallowed or turned into fake success.

  • php032-swallowed-failures

    PHP-032 · No empty catch block and no catch that reports success; a failure is recovered, translated or propagated

  • php033-lost-failure-causes

    PHP-033 · A catch that throws a new exception passes the caught one as previous; a broad catch rethrows or logs (review)

  • php034-fabricated-results

    PHP-034 · A failed write, payment or lookup does not return a fabricated success, ID or default (review)

Async & concurrency 2

Owned tasks, awaited promises, preserved cancellation and no blocking on event loops.

  • php062-rich-message-payloads

    PHP-062 · Async messages and jobs carry identifiers and serializable data, not entities, requests, resources or services

  • php065-discarded-failed-messages

    PHP-065 · Async transports keep exhausted messages (a failure transport or failed-job store), instead of discarding them

Performance & resource bounds 2

Timeouts, bounded retries and fan-out, reused clients, closed resources, no N+1 queries.

  • php060-unbounded-outbound-io

    PHP-060 · Outbound HTTP calls have a time limit (Guzzle timeout, curl CURLOPT_TIMEOUT, stream context timeout; review)

  • php061-unbounded-retries

    PHP-061 · Retry loops are bounded (attempt counter, maximum, backoff) instead of retrying forever (review)

Architecture & boundaries 9

Declared layers and dependency directions, no import cycles, small modules, explicit dependencies.

  • php007-namespace-boundaries

    PHP-007 · Imports respect the project's declared module dependency directions (off until namespaceRules is set)

  • php008-framework-in-domain

    PHP-008 · Declared domain code does not depend on HTTP, routing, container or controller types (off until domainPaths is set)

  • php009-side-effects-in-business-code

    PHP-009 · Business code reaches databases, files, the network and processes through injected adapters, not raw sinks

  • php010-hidden-dependencies

    PHP-010 · Production code receives its collaborators through DI, not globals, singletons or container helpers

  • php011-container-injection

    PHP-011 · Business services take specific dependencies, not the general service container

  • php013-infrastructure-clients-in-code

    PHP-013 · Production classes inject database, HTTP, cache, queue and mail clients instead of constructing them

  • php018-application-bundles

    PHP-018 · An application organizes features with namespaces; a new bundle class needs a reuse purpose (review)

  • php020-interface-per-class

    PHP-020 · A turn does not generate an interface for every new class (two or more <Name>Interface each with one new implementation; review, once per stop)

  • php067-wall-clock-in-business-code

    PHP-067 · Business time decisions read an injected clock (PSR-20 ClockInterface), not time(), date() or new DateTime()

Language idioms 4

The conventions of the language that keep code predictable for its readers.

  • php015-final-value-objects

    PHP-015 · (suggestion) A new readonly class that is not designed for extension is final

  • php027-strict-types-convention

    PHP-027 · New PHP files follow the project's declare(strict_types=1) convention (strictTypes parameter or the coding-standard config)

  • php028-promotable-constructor

    PHP-028 · (suggestion) A constructor that only copies parameters into properties can promote them (PHP 8.0+)

  • php059-float-money

    PHP-059 · New monetary amounts are not floats; they use minor units (int) or exact decimals with a currency and rounding rule (review)

HTTP & APIs 3

Request and response contracts, schemas, status codes, error bodies and published API surfaces.

  • php038-exception-details-in-responses

    PHP-038 · HTTP responses never carry stack traces, file paths or the raw message of an unexpected exception

  • php078-public-contract-changes

    PHP-078 · The user approves removing or changing public signatures of @api code or declared public API paths

  • php079-api-schema-drift

    PHP-079 · A turn that adds, removes or changes HTTP routes also updates the published API schema (review, once per stop)

Databases & queries 5

Schemas, queries, transactions and the ORM used as intended.

  • php052-multi-write-without-transaction

    PHP-052 · A method making several writes that must succeed together owns a transaction (review)

  • php053-rollback-without-propagation

    PHP-053 · A catch that rolls back a transaction rethrows (or translates) the failure

  • php053-transaction-without-rollback

    PHP-053 · A method that begins a transaction rolls it back on the exceptional path (catch or finally)

  • php054-flush-in-message-handlers

    PHP-054 · Message handlers do not flush or commit when the bus's doctrine_transaction middleware owns the unit of work

  • php066-side-effects-in-transactions

    PHP-066 · Messages, e-mails and remote calls inside a database transaction wait for the commit (after-commit, outbox; review)

Schema migrations 4

Migrations come from the tool, applied history stays immutable and risky changes are rolled out safely.

  • php055-deleted-migrations

    PHP-055 · The user approves deleting or renaming a committed migration

  • php055-migration-edit-ledger

    PHP-055 · Records migrations Claude wrote with the user's approval this session, so the edit is asked once per file

  • php055-released-migrations

    PHP-055 · The user approves changing a committed migration (it may already have run somewhere); new changes go in a new migration

  • php056-destructive-schema-changes

    PHP-056 · The user approves a migration that drops, renames or retypes schema in its up step (expand/contract for rolling deploys)

Framework conventions 1

Routing, server and client boundaries, hooks and plugins the way the framework expects them.

  • php001-autoload-mapping

    PHP-001 · A class, interface, trait or enum sits where Composer's PSR-4 mapping expects it (namespace and file name)

Versions & deprecated APIs 1

Code matches the installed version, without removed, renamed or deprecated APIs.

  • php002-platform-syntax

    PHP-002 · New code uses no syntax newer than the PHP version composer.json declares (config.platform.php, else require.php)

Logging & observability 1

Structured logs, bounded metric labels, no debug prints left in production code.

  • php035-debug-output

    PHP-035 · Reusable backend code has no var_dump/dd/dump/print_r, echo/print or exit/die; only entrypoints print or terminate

Dependencies & toolchain 7

Packages change through the package manager, lockfiles stay consistent and the project's tools are used.

  • php002-undeclared-extensions

    PHP-002 · New calls into a PHP extension (mbstring, intl, bcmath, gmp, redis…) need ext-* or a polyfill in composer.json

  • php004-dev-dependencies-in-production

    PHP-004 · Production code does not import namespaces that only require-dev packages or autoload-dev provide

  • php005-hand-edited-lockfile

    PHP-005 · composer.lock is written by Composer, never edited by hand (its content-hash must match composer.json)

  • php005-stale-lockfile

    PHP-005 · A turn that changes composer.json dependencies also updates composer.lock (application projects; once per stop)

  • php005-unlocked-deploys

    PHP-005 · Build and deployment scripts install from composer.lock; they never run an unconstrained composer update

  • php006-full-dependency-updates

    PHP-006 · composer update names the packages it updates instead of re-resolving every dependency

  • php080-new-packages

    PHP-080/PHP-006 · A new Composer package names the changed feature or tooling that needs it (review)

Tests 6

Tests assert behavior, stay deterministic and are not removed or skipped silently.

  • php068-untested-changes

    PHP-068/PHP-069/PHP-073 · A turn that changes production PHP code changes tests too, or says why none applies (review, once per stop)

  • php072-sql-choreography-mocks

    PHP-072 · Tests assert outcomes, not the SQL text a mocked connection or statement receives (review)

  • php074-tests-terminating-the-runner

    PHP-074 · Tests never call exit or die (they end the PHPUnit run with a false result) unless run in a separate process

  • php075-meaningless-assertions

    PHP-075 · Tests make assertions that can fail (no assertTrue(true), assertSame($x, $x) or bare assertion counters)

  • php077-removed-tests

    PHP-077 · The user approves edits that remove test methods or assertions from a test file

  • php077-skipped-tests

    PHP-077 · The user approves newly skipped, incomplete or todo tests

Completion evidence 5

A turn ends with its checks run and passing; claims of success need proof after the last edit.

  • php-turn-baseline

    Records the work tree when a prompt starts, the baseline of the end-of-turn checks (PHP-003, PHP-005, PHP-068, PHP-079)

  • php003-unresolved-symbols

    PHP-003 · Claude does not end a turn with syntax errors, undefined types, functions or variables, or calls with too many arguments it introduced (intelephense)

  • php075-placeholders

    PHP-075 · Production code ships implemented behavior, not "not implemented" exceptions, TODO-only bodies or TODO stub returns

  • php076-completion-claims

    PHP-076 · A final message claiming passing tests or checks needs a successful PHPUnit/Pest/PHPStan/Psalm/… run after Claude's last edit

  • php076-evidence-ledger

    PHP-076 · Records, per session, Claude's edits and the PHP test, static-analysis and architecture runs that succeeded after them