PHPStan
The blockstudio/phpstan extension brings static analysis to Blockstudio
projects. It validates block templates, schema files, settings paths, and hook
names at analysis time, catching bugs before they reach runtime.
Installation
composer require --dev blockstudio/phpstanThe extension auto-discovers via PHPStan's extension installer. No manual configuration needed.
What it checks
Template field access
When a PHP file lives next to a block.json, the extension validates every
$a['key'] access against the block's declared attributes.
<?php
/** @var array<string, mixed> $a */
echo $a['title']; // OK
echo $a['subtitle']; // OK
echo $a['typo']; // Error: Field "typo" does not exist in block.jsonAdd /** @var array<string, mixed> $a */ at the top of each PHP template so
PHPStan knows $a exists. The extension handles the rest.
Twig template access
Twig templates are scanned automatically. No annotation needed.
<h1>{{ a.title }}</h1>
<p>{{ a.typo }}</p> {# Error: Field "typo" does not exist in block.json #}Blade template access
Blade templates are scanned the same way.
<h1>{{ $a['title'] }}</h1>
<p>{{ $a['typo'] }}</p> {{-- Error --}}Block tag validation
Both <block> and <bs:> tag syntaxes are validated across PHP, Twig, and
Blade templates.
<bs:mytheme-hero title="Hello" /> <!-- OK -->
<bs:mytheme-nonexistent /> <!-- Error: unknown block -->
<bs:mytheme-hero title="Hi" badattr="" /> <!-- Error: unknown attribute -->
<block name="core/separator" /> <!-- OK (core blocks always valid) -->Attributes with data-* and html-* prefixes are pass-through and never
checked.
Database record typing
Db::get() returns a typed instance based on the db.php schema. Record
shapes are inferred automatically.
return [
'storage' => 'table',
'fields' => [
'email' => ['type' => 'string', 'required' => true],
'name' => ['type' => 'string'],
],
];$db = Db::get('mytheme/subscribers');
$record = $db->create(['email' => 'a@b.com']);
echo $record['email']; // string
echo $record['name']; // string|null (optional field)
echo $record['typo']; // Error: Offset 'typo' does not existRequired fields are non-nullable. Optional fields are type|null. An id
field (int) is always present. The same typing works for the PHP-native
Blockstudio\\Db\\Schema / Blockstudio\\Db\\Field builder syntax.
Settings path validation
Settings::get() paths are checked against the known settings schema.
Settings::get('tailwind/enabled'); // OK, returns bool
Settings::get('tailwind/enabld'); // Error: Did you mean "tailwind/enabled"?Hook name validation
Blockstudio filter and action hook names are validated.
add_filter('blockstudio/render', $cb); // OK
add_filter('blockstudio/rendrr', $cb); // Error: Did you mean "blockstudio/render"?Dynamic settings hooks like blockstudio/settings/tailwind/enabled are always
allowed. Non-blockstudio hooks are ignored.
Schema validation
The extension validates every Blockstudio schema file in the project.
block.json:
- Missing
namefield - Missing field
type - Missing
idon value fields that require one - Unknown field types
select/radio/checkboxwithoutoptionsorpopulate- Duplicate field IDs
Container fields like group and tabs, plus custom/* field references, can
omit id when they only expand or wrap other fields.
field.json (custom reusable fields):
- Missing
name - Missing field
type - Missing
idon value fields that require one - Unknown field types
Extension JSON (block extensions in extensions/):
- Missing
name(target block) - Missing
blockstudiokey - Missing
blockstudio.extendkey
page.json (file-based pages):
- Missing
title - Missing
slug - Invalid
postStatusvalues
db.php:
- Missing
fieldsarray - Invalid field types
- Supports both legacy arrays and
Blockstudio\\Db\\Schema/Blockstudio\\Db\\Field
rpc.php:
- Invalid HTTP methods
- Wrong
publicvalue (must betrue,false, or'open') - Supports both legacy arrays and attributed object returns
cron.php:
- Missing
scheduleon configured tasks - Missing
callbackon configured tasks - Supports both legacy arrays and attributed object returns
blockstudio.json:
- Shorthand booleans like
"tailwind": true(must use the nested object format{"enabled": true, "config": ""})
Field type shapes
The extension maps every Blockstudio field type to a concrete PHP type:
| Field type | PHP type |
|---|---|
text, textarea, richtext, wysiwyg, code | string |
date, datetime, classes, html-tag, unit, gradient | string |
number, range | int|float |
toggle | bool |
select, radio, checkbox (single) | string|int |
select, checkbox (multiple) | list<string|int> |
color | array{value: string, opacity: float|null} |
link | array{href: string, title: string|null, target: string|null, opensInNewTab: bool|null} |
icon | array{set: string, subSet: string, icon: string} |
files (single) | array{id: int, url: string, alt: string|null, mime_type: string|null} |
files (multiple) | list<array{id: int, url: string, ...}> |
group | Flattened with underscore prefix: cta_text, cta_url |
repeater | list<array{...child fields...}> |
tabs | Flattened into parent scope |
API stubs
The extension ships stubs for the full Blockstudio public API:
bs_render_block(),bs_get_group(),bs_get_scoped_class(),bs_db_form()Db::get(),Db::create(),Db::list(),Db::get_record(),Db::update(),Db::delete()Settings::get(),Settings::get_all()Field_Registry::instance(),Field_Registry::all(),Field_Registry::get()Build::blocks(),Build::extensions(),Build::get_build_dir()
These provide autocomplete and type checking without needing the Blockstudio plugin source on your machine.
Requirements
- PHP 8.2+
- PHPStan 2.0+
phpstan/extension-installer(recommended, for auto-discovery)