schema-implementation
Step-by-step procedure for implementing a new schema.org type in this library, including class structure, enum creation, test writing, and code style.
SKILL.md.claude/skills/schema-implementation/SKILL.md
GitHubImplement a Schema.org Type
Step-by-step procedure for implementing a new schema.org type in this library.
Step 1: Understand the type
- Read the Google Rich Results docs URL from the issue
- Read the schema.org spec URL from the issue
- Identify required, recommended, and optional properties
- Identify property types: string, int, float, bool, array, enum, or nested schema
Step 2: Check for dependencies
- Does this type reference other TypedSchema classes?
- Are they already implemented in
php/src/v1/Schema/? - If not, they need to be created too (or the issue should specify using a simpler type like
stringtemporarily)
Step 3: Create the schema class
File: php/src/v1/Schema/{TypeName}.php
Template:
<?php
declare(strict_types=1);
namespace EvaLok\SchemaOrgJsonLd\v1\Schema;
use EvaLok\SchemaOrgJsonLd\v1\TypedSchema;
class {TypeName} extends TypedSchema {
public const A_SCHEMA_TYPE = '{TypeName}';
public function __construct(
// Required properties first (no default value)
public string $requiredProp,
// Optional properties last (default to null)
public null|string $optionalProp = null,
) {
}
}
Rules:
- Extend
TypedSchema(or a parent schema class if the type inherits — see below) - Set
A_SCHEMA_TYPEto the exact schema.org type name - Use constructor promotion for ALL properties
- Required properties have no default value
- Optional properties default to
nullwithnull|Typesyntax - For array properties, add
/** @var ElementType[] $propName */doc comment - Do NOT add any methods — no toArray(), no serialize(), nothing
Class inheritance variant
If the schema type extends another schema type (not TypedSchema directly), use the inheritance pattern:
class FoodEstablishment extends LocalBusiness {
public const A_SCHEMA_TYPE = 'FoodEstablishment';
public function __construct(
string $name,
PostalAddress $address,
// ... all parent params passed through ...
public null|bool|string $acceptsReservations = null, // new param
) {
parent::__construct(
name: $name,
address: $address,
// ... pass all parent params via named arguments ...
);
}
}
Key rules:
- Extend the parent class, not
TypedSchema - Override
A_SCHEMA_TYPEwith the child's type name - Pass all parent params through via
parent::__construct()with named arguments - Add child-specific params as promoted properties (with
public) at the end - Parent params are NOT promoted in the child (no
publickeyword)
Check the PHP codebase for existing inheritance: FoodEstablishment extends LocalBusiness, Store extends LocalBusiness, BlogPosting extends Article, NewsArticle extends Article, MobileApplication extends SoftwareApplication, WebApplication extends SoftwareApplication, Restaurant extends FoodEstablishment.
Step 4: Create enum types (if needed)
File: php/src/v1/Enum/{EnumName}.php
Template:
<?php
declare(strict_types=1);
namespace EvaLok\SchemaOrgJsonLd\v1\Enum;
enum {EnumName}: string {
case CaseName = 'https://schema.org/CaseName';
}
Rules:
- Backed string enum with full schema.org URLs as values
- PascalCase for case names matching schema.org names
Step 5: Write tests
File: php/test/unit/{TypeName}Test.php
Must test:
- Minimal construction: Only required properties, verify JSON output has @context, @type, and required fields
- Null omission: Optional properties when null are NOT present in JSON output
- Full construction: All properties set, verify they all appear in JSON output
- Nested schemas: If the type contains other TypedSchema instances, verify they serialize correctly
- Enums: If the type uses enums, verify they serialize to schema.org URLs
Always use JsonLdGenerator::SchemaToJson(schema: $instance) for serialization in tests.
Step 6: Add sample JSON (optional)
File: php/test/samples/{TypeName}.json
Create if the type is complex. Match the expected JSON-LD output format. Used for test assertions via json_decode(file_get_contents(...)).
Step 7: Fix code style
composer run cs-fix
Run the auto-fixer before committing. This handles all formatting (tabs, braces, imports, whitespace). Do NOT manually fix style issues — just run this command.
Step 8: Run tests
composer run test-unit
All existing tests plus new tests must pass. PHP 8.3 and composer dependencies are pre-installed in your environment. You MUST run tests and confirm they pass before finishing your work.
Step 9: Run static analysis
composer run phpstan
PHPStan must pass cleanly before finishing your work.
Common mistakes to avoid
- Adding a
toArray()method — don't do this, JsonLdGenerator uses reflection - Using
?Typeinstead ofnull|Type— the codebase usesnull|Type - Forgetting
A_SCHEMA_TYPE— this determines the @type in JSON-LD output - Adding
@contextmanually — JsonLdGenerator adds this automatically - Manual null filtering — JsonLdGenerator handles this automatically
- Modifying JsonLdGenerator.php or TypedSchema.php — never modify these