Block Registration System
Table of Contents
Block Registration System
JSON-Based Block Registration with PHP Scanner
Jankx 2.0 sử dụng hệ thống block registration hiện đại dựa trên JSON, với PHP scanner tự động và global JS config generation.
🏗 Registration Architecture
Registration Flow
┌─────────────────────────────────────┐
│ PHP Block Scanner │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Scan │ │ Parse │ │
│ │ block.json │ │ JSON │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Global Config Gen │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ PHP │ │ JS │ │
│ │ Config │ │ Config │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ WordPress Registration │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ register │ │ Enqueue │ │
│ │ block_type │ │ Assets │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
📋 JSON-Based Registration
Block JSON Structure
{
"apiVersion": 2,
"name": "jankx/testimonial",
"title": "Testimonial",
"category": "jankx",
"icon": "format-quote",
"description": "Display customer testimonials with author information",
"keywords": ["testimonial", "quote", "customer", "review"],
"supports": {
"html": false,
"align": ["wide", "full"],
"spacing": {
"margin": true,
"padding": true
},
"color": {
"background": true,
"text": true
},
"typography": {
"fontSize": true,
"lineHeight": true
}
},
"attributes": {
"author": {
"type": "string",
"default": ""
},
"content": {
"type": "string",
"default": ""
},
"avatar": {
"type": "string",
"default": ""
},
"authorTitle": {
"type": "string",
"default": ""
},
"alignment": {
"type": "string",
"default": "left"
},
"backgroundColor": {
"type": "string",
"default": ""
}
},
"textdomain": "jankx",
"editorScript": "file:./assets/js/editor.js",
"editorStyle": "file:./assets/css/editor.css",
"script": "file:./assets/js/frontend.js",
"style": "file:./assets/css/frontend.css",
"render_callback": "Jankx\\Gutenberg\\Blocks\\TestimonialBlock::render"
}
Block Directory Structure
blocks/
├── testimonial/
│ ├── block.json
│ ├── TestimonialBlock.php
│ ├── assets/
│ │ ├── js/
│ │ │ ├── editor.js
│ │ │ └── frontend.js
│ │ ├── css/
│ │ │ ├── editor.css
│ │ │ └── frontend.css
│ │ └── images/
│ │ └── icon.svg
│ └── templates/
│ ├── editor.html
│ └── frontend.html
├── hero/
│ ├── block.json
│ ├── HeroBlock.php
│ └── assets/
│ ├── js/
│ ├── css/
│ └── images/
└── features/
├── block.json
├── FeaturesBlock.php
└── assets/
├── js/
├── css/
└── images/
🔧 PHP Block Scanner
Block Registry Implementation
<?php
namespace Jankx\Gutenberg;
class BlockRegistry
{
private $blocks = [];
private $globalConfig = [];
private $scanned = false;
public function scanBlocks(): void
{
if ($this->scanned) {
return;
}
$blocksDir = get_template_directory() . '/blocks/';
$blockDirs = glob($blocksDir . '*/block.json');
foreach ($blockDirs as $blockJson) {
$this->processBlockJson($blockJson);
}
$this->generateGlobalConfig();
$this->scanned = true;
}
private function processBlockJson(string $blockJson): void
{
$blockData = json_decode(file_get_contents($blockJson), true);
if (!$blockData) {
error_log("Invalid JSON in block.json: {$blockJson}");
return;
}
$blockName = $blockData['name'] ?? '';
if (empty($blockName)) {
error_log("Missing block name in: {$blockJson}");
return;
}
$this->blocks[$blockName] = $blockData;
$this->registerBlock($blockData);
}
private function registerBlock(array $blockData): void
{
$blockName = $blockData['name'];
$blockPath = dirname($this->findBlockJson($blockName));
$args = [
'editor_script' => $this->getScriptHandle($blockName, 'editor'),
'editor_style' => $this->getStyleHandle($blockName, 'editor'),
'script' => $this->getScriptHandle($blockName, 'frontend'),
'style' => $this->getStyleHandle($blockName, 'frontend'),
];
// Add render callback if specified
if (isset($blockData['render_callback'])) {
$args['render_callback'] = $blockData['render_callback'];
}
// Register block with WordPress
register_block_type($blockPath, $args);
// Register assets
$this->registerBlockAssets($blockData);
}
private function registerBlockAssets(array $blockData): void
{
$blockName = $blockData['name'];
// Register editor script
if (isset($blockData['editorScript'])) {
$this->registerScript($blockName, 'editor', $blockData['editorScript']);
}
// Register editor style
if (isset($blockData['editorStyle'])) {
$this->registerStyle($blockName, 'editor', $blockData['editorStyle']);
}
// Register frontend script
if (isset($blockData['script'])) {
$this->registerScript($blockName, 'frontend', $blockData['script']);
}
// Register frontend style
if (isset($blockData['style'])) {
$this->registerStyle($blockName, 'frontend', $blockData['style']);
}
}
private function registerScript(string $blockName, string $context, string $scriptPath): void
{
$handle = $this->getScriptHandle($blockName, $context);
$dependencies = $this->getScriptDependencies($context);
wp_register_script(
$handle,
get_theme_file_uri($scriptPath),
$dependencies,
\Jankx\Jankx::getFrameworkVersion(),
true
);
}
private function registerStyle(string $blockName, string $context, string $stylePath): void
{
$handle = $this->getStyleHandle($blockName, $context);
wp_register_style(
$handle,
get_theme_file_uri($stylePath),
[],
\Jankx\Jankx::getFrameworkVersion()
);
}
private function getScriptHandle(string $blockName, string $context): string
{
$sanitizedName = sanitize_title($blockName);
return "jankx-block-{$sanitizedName}-{$context}";
}
private function getStyleHandle(string $blockName, string $context): string
{
$sanitizedName = sanitize_title($blockName);
return "jankx-block-{$sanitizedName}-{$context}";
}
private function getScriptDependencies(string $context): array
{
$dependencies = ['wp-blocks', 'wp-element'];
if ($context === 'editor') {
$dependencies[] = 'wp-editor';
$dependencies[] = 'wp-components';
}
return $dependencies;
}
private function findBlockJson(string $blockName): string
{
$blocksDir = get_template_directory() . '/blocks/';
$blockDir = str_replace('jankx/', '', $blockName);
return $blocksDir . $blockDir . '/block.json';
}
}
🌐 Global Config Generation
PHP Config Generator
class GlobalConfigGenerator
{
private $registry;
public function __construct(BlockRegistry $registry)
{
$this->registry = $registry;
}
public function generateConfig(): array
{
$config = [];
foreach ($this->registry->getBlocks() as $blockName => $blockData) {
$config[$blockName] = $this->normalizeBlockData($blockData);
}
return $config;
}
private function normalizeBlockData(array $blockData): array
{
return [
'name' => $blockData['name'],
'title' => $blockData['title'],
'category' => $blockData['category'],
'icon' => $blockData['icon'],
'description' => $blockData['description'],
'keywords' => $blockData['keywords'] ?? [],
'supports' => $blockData['supports'] ?? [],
'attributes' => $blockData['attributes'] ?? [],
'textdomain' => $blockData['textdomain'] ?? 'jankx',
];
}
public function outputJSConfig(): void
{
$config = $this->generateConfig();
$jsonConfig = json_encode($config, JSON_PRETTY_PRINT);
add_action('wp_head', function() use ($jsonConfig) {
echo "<script>window.JankxBlocks = {$jsonConfig};</script>";
});
add_action('admin_head', function() use ($jsonConfig) {
echo "<script>window.JankxBlocks = {$jsonConfig};</script>";
});
}
}
JavaScript Global Config
// Generated global config
window.JankxBlocks = {
'jankx/testimonial': {
name: 'jankx/testimonial',
title: 'Testimonial',
category: 'jankx',
icon: 'format-quote',
description: 'Display customer testimonials with author information',
keywords: ['testimonial', 'quote', 'customer', 'review'],
supports: {
html: false,
align: ['wide', 'full'],
spacing: {
margin: true,
padding: true
},
color: {
background: true,
text: true
},
typography: {
fontSize: true,
lineHeight: true
}
},
attributes: {
author: {
type: 'string',
default: ''
},
content: {
type: 'string',
default: ''
},
avatar: {
type: 'string',
default: ''
},
authorTitle: {
type: 'string',
default: ''
},
alignment: {
type: 'string',
default: 'left'
},
backgroundColor: {
type: 'string',
default: ''
}
},
textdomain: 'jankx'
},
'jankx/hero': {
name: 'jankx/hero',
title: 'Hero Section',
category: 'jankx',
icon: 'align-wide',
description: 'Display hero section with title, description and CTA',
keywords: ['hero', 'banner', 'cta', 'section'],
supports: {
html: false,
align: ['wide', 'full'],
spacing: {
margin: true,
padding: true
},
color: {
background: true,
text: true
}
},
attributes: {
title: {
type: 'string',
default: ''
},
description: {
type: 'string',
default: ''
},
backgroundImage: {
type: 'string',
default: ''
},
ctaText: {
type: 'string',
default: ''
},
ctaUrl: {
type: 'string',
default: ''
}
},
textdomain: 'jankx'
}
};
🎯 Block Registration Flow
Registration Process
class BlockRegistrationManager
{
private $registry;
private $configGenerator;
public function __construct(BlockRegistry $registry, GlobalConfigGenerator $configGenerator)
{
$this->registry = $registry;
$this->configGenerator = $configGenerator;
}
public function registerAllBlocks(): void
{
// 1. Scan all block.json files
$this->registry->scanBlocks();
// 2. Generate global config
$this->configGenerator->outputJSConfig();
// 3. Register with WordPress
$this->registerWithWordPress();
// 4. Enqueue assets
$this->enqueueBlockAssets();
}
private function registerWithWordPress(): void
{
foreach ($this->registry->getBlocks() as $blockName => $blockData) {
$this->registerBlock($blockData);
}
}
private function registerBlock(array $blockData): void
{
$blockName = $blockData['name'];
$blockPath = $this->getBlockPath($blockName);
$args = [
'editor_script' => $this->getScriptHandle($blockName, 'editor'),
'editor_style' => $this->getStyleHandle($blockName, 'editor'),
'script' => $this->getScriptHandle($blockName, 'frontend'),
'style' => $this->getStyleHandle($blockName, 'frontend'),
];
if (isset($blockData['render_callback'])) {
$args['render_callback'] = $blockData['render_callback'];
}
register_block_type($blockPath, $args);
}
private function enqueueBlockAssets(): void
{
add_action('wp_enqueue_scripts', function() {
foreach ($this->registry->getBlocks() as $blockName => $blockData) {
if ($this->isBlockUsed($blockName)) {
$this->enqueueBlockAssets($blockName);
}
}
});
}
private function isBlockUsed(string $blockName): bool
{
global $post;
if (!$post) {
return false;
}
$content = $post->post_content;
return strpos($content, '<!-- wp:' . $blockName) !== false;
}
private function enqueueBlockAssets(string $blockName): void
{
$sanitizedName = sanitize_title($blockName);
// Enqueue frontend styles
wp_enqueue_style("jankx-block-{$sanitizedName}-frontend");
// Enqueue frontend scripts
wp_enqueue_script("jankx-block-{$sanitizedName}-frontend");
}
}
🔧 Block Class Integration
Abstract Block Class
<?php
namespace Jankx\Gutenberg\Blocks;
abstract class AbstractBlock
{
protected $blockName;
protected $blockPath;
protected $blockData;
public function __construct()
{
$this->blockName = $this->getBlockName();
$this->blockPath = $this->getBlockPath();
$this->blockData = $this->loadBlockData();
}
abstract protected function getBlockName(): string;
abstract public function register(): void;
abstract public function render(array $attributes, string $content): string;
protected function getBlockPath(): string
{
$blockDir = str_replace('jankx/', '', $this->blockName);
return get_template_directory() . '/blocks/' . $blockDir;
}
protected function loadBlockData(): array
{
$blockJsonPath = $this->blockPath . '/block.json';
if (!file_exists($blockJsonPath)) {
throw new \Exception("Block JSON not found: {$blockJsonPath}");
}
$blockData = json_decode(file_get_contents($blockJsonPath), true);
if (!$blockData) {
throw new \Exception("Invalid JSON in block.json: {$blockJsonPath}");
}
return $blockData;
}
protected function getScriptHandle(string $context): string
{
$sanitizedName = sanitize_title($this->blockName);
return "jankx-block-{$sanitizedName}-{$context}";
}
protected function getStyleHandle(string $context): string
{
$sanitizedName = sanitize_title($this->blockName);
return "jankx-block-{$sanitizedName}-{$context}";
}
protected function renderTemplate(string $template, array $data = []): string
{
$templatePath = $this->blockPath . '/templates/' . $template . '.html';
if (!file_exists($templatePath)) {
throw new \Exception("Template not found: {$templatePath}");
}
$templateContent = file_get_contents($templatePath);
// Basic template engine
foreach ($data as $key => $value) {
$templateContent = str_replace(' . $key . ', $value, $templateContent);
}
return $templateContent;
}
}
Example Block Implementation
<?php
namespace Jankx\Gutenberg\Blocks;
class TestimonialBlock extends AbstractBlock
{
protected function getBlockName(): string
{
return 'jankx/testimonial';
}
public function register(): void
{
$args = [
'editor_script' => $this->getScriptHandle('editor'),
'editor_style' => $this->getStyleHandle('editor'),
'script' => $this->getScriptHandle('frontend'),
'style' => $this->getStyleHandle('frontend'),
'render_callback' => [$this, 'render'],
];
register_block_type($this->blockPath, $args);
}
public function render(array $attributes, string $content): string
{
$data = [
'author' => $attributes['author'] ?? '',
'content' => $attributes['content'] ?? '',
'avatar' => $attributes['avatar'] ?? '',
'authorTitle' => $attributes['authorTitle'] ?? '',
'alignment' => $attributes['alignment'] ?? 'left',
'backgroundColor' => $attributes['backgroundColor'] ?? '',
];
return $this->renderTemplate('frontend', $data);
}
}
📊 Registration Monitoring
Block Usage Tracking
class BlockUsageTracker
{
private $usage = [];
public function trackBlockUsage(string $blockName): void
{
$this->usage[$blockName] = ($this->usage[$blockName] ?? 0) + 1;
}
public function getBlockUsage(): array
{
return $this->usage;
}
public function getMostUsedBlocks(int $limit = 10): array
{
arsort($this->usage);
return array_slice($this->usage, 0, $limit, true);
}
public function getUnusedBlocks(): array
{
$allBlocks = $this->getAllRegisteredBlocks();
$usedBlocks = array_keys($this->usage);
return array_diff($allBlocks, $usedBlocks);
}
private function getAllRegisteredBlocks(): array
{
$blocks = [];
$blocksDir = get_template_directory() . '/blocks/';
$blockDirs = glob($blocksDir . '*/block.json');
foreach ($blockDirs as $blockJson) {
$blockData = json_decode(file_get_contents($blockJson), true);
if ($blockData && isset($blockData['name'])) {
$blocks[] = $blockData['name'];
}
}
return $blocks;
}
}
Next: Layout System | AJAX System | Frontend Rendering |