Skip to content

🧩 XMF Components Guide

Leveraging XMF (XOOPS Module Framework) components for modern XOOPS 2026 development.

XMF provides a collection of well-tested, framework-agnostic components that form the foundation of modern XOOPS module development.


Overview

flowchart TB
    subgraph XMF["XMF Library"]
        direction TB
        ULID[Ulid]
        SLUG[Slug]
        REQ[Request]
        FILTER[FilterInput]
        META[Metagen]
        JWT[Jwt]
        YAML[Yaml]
        DB[Database]
    end

    subgraph Usage["Common Use Cases"]
        ID[Unique Identifiers]
        URL[URL Generation]
        INPUT[Input Handling]
        AUTH[Authentication]
        CONFIG[Configuration]
    end

    ULID --> ID
    SLUG --> URL
    REQ --> INPUT
    FILTER --> INPUT
    JWT --> AUTH
    YAML --> CONFIG

Xmf\Ulid

Universally Unique Lexicographically Sortable Identifier

ULIDs provide significant advantages over traditional UUIDs for database-centric applications.

Why ULID over UUID?

Feature ULID UUID v4 UUID v7
Length 26 chars 36 chars 36 chars
Sortable ✓ Lexicographic
URL-safe ✓ Native ✗ Needs encoding
Time-extractable
Collision-resistant ✓ (80-bit random)
DB Index Performance Excellent Poor Good
Case-insensitive Depends Depends

ULID Structure

flowchart LR
    subgraph ULID["01ARZ3NDEKTSV4RRFFQ69G5FAV"]
        TS["01ARZ3NDEK<br/>Timestamp<br/>(48-bit, ms)"]
        RND["TSV4RRFFQ69G5FAV<br/>Randomness<br/>(80-bit)"]
    end

    TS --> RND

Basic Usage

<?php

use Xmf\Ulid;

// Generate a new ULID
$ulid = Ulid::generate();
echo $ulid->toString(); // "01HV8X5Z0KDMVR8SDPY62J9ACP"

// Create from string
$ulid = Ulid::fromString('01HV8X5Z0KDMVR8SDPY62J9ACP');

// Validate a ULID string
if (Ulid::isValid($input)) {
    $ulid = Ulid::fromString($input);
}

// Extract timestamp
$timestamp = $ulid->getTimestamp();
echo $timestamp->format('Y-m-d H:i:s.v'); // "2026-01-29 14:30:25.123"

// Compare ULIDs
$ulid1 = Ulid::generate();
usleep(1000); // Wait 1ms
$ulid2 = Ulid::generate();

$ulid1->equals($ulid2);      // false
$ulid1->compareTo($ulid2);   // -1 (ulid1 is older)

// ULIDs are lexicographically sortable
$ids = ['01HV8X5Z0K...', '01HV8X4Y0J...', '01HV8X6A0L...'];
sort($ids); // Sorts chronologically!

Monotonic Generation

Within the same millisecond, XMF ULID ensures monotonic ordering:

<?php

use Xmf\Ulid;

// Generate multiple ULIDs in same millisecond
$ulids = [];
for ($i = 0; $i < 1000; $i++) {
    $ulids[] = Ulid::generate()->toString();
}

// All are unique and sorted within the same millisecond
assert(count(array_unique($ulids)) === 1000);
assert($ulids === array_unique($ulids)); // Already sorted

Database Considerations

<?php

// ULID as primary key - excellent for clustered indexes
// Schema recommendation:
// - CHAR(26) for MySQL (case-insensitive collation)
// - VARCHAR(26) for PostgreSQL
// - Store as binary(16) for optimal storage (decode from Base32)

class Article
{
    #[Column(type: 'string', length: 26)]
    private string $id;

    public function __construct()
    {
        $this->id = Ulid::generate()->toString();
    }
}

Value Object Pattern

<?php

declare(strict_types=1);

namespace Xoops\Module\Domain\ValueObject;

use Xmf\Ulid;

final readonly class EntityId
{
    private function __construct(
        private Ulid $ulid,
    ) {}

    public static function generate(): self
    {
        return new self(Ulid::generate());
    }

    public static function fromString(string $id): self
    {
        if (!Ulid::isValid($id)) {
            throw new InvalidEntityId("Invalid ID format: {$id}");
        }
        return new self(Ulid::fromString($id));
    }

    public function toString(): string
    {
        return $this->ulid->toString();
    }

    public function getCreatedAt(): \DateTimeImmutable
    {
        return $this->ulid->getTimestamp();
    }

    public function equals(self $other): bool
    {
        return $this->ulid->equals($other->ulid);
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}

Xmf\Slug

URL-Friendly String Generator

XMF Slug provides robust slug generation with proper Unicode handling.

Features

  • Multi-language transliteration (CJK, Cyrillic, Arabic, Hebrew, Greek)
  • Configurable separators and length limits
  • Word-boundary aware truncation
  • Built-in uniqueness suffix support
  • SEO-friendly output

Basic Usage

<?php

use Xmf\Slug;

// Create a slug from text
$slug = Slug::create('Hello World!');
echo $slug->toString(); // "hello-world"

// With options
$slug = Slug::create('My Article Title', [
    'separator' => '-',      // Separator character
    'maxLength' => 50,       // Maximum length
    'lowercase' => true,     // Convert to lowercase
]);

// From existing slug string
$slug = Slug::fromString('existing-slug');

// Validate a slug
if (Slug::isValid($input)) {
    $slug = Slug::fromString($input);
}

// Normalize a slug (clean up)
$normalized = Slug::normalize('  My--Slug--  ');
echo $normalized; // "my-slug"

Unicode Transliteration

<?php

use Xmf\Slug;

// Chinese
$slug = Slug::create('你好世界');
echo $slug; // "ni-hao-shi-jie"

// Japanese
$slug = Slug::create('こんにちは');
echo $slug; // "konnichiha"

// Russian
$slug = Slug::create('Привет мир');
echo $slug; // "privet-mir"

// German
$slug = Slug::create('Größe');
echo $slug; // "groesse"

// French
$slug = Slug::create('Café résumé');
echo $slug; // "cafe-resume"

// Arabic
$slug = Slug::create('مرحبا');
echo $slug; // "mrhba"

// Mixed content
$slug = Slug::create('PHP 8.3 新機能');
echo $slug; // "php-8-3-xin-ji-neng"

Handling Duplicates

<?php

use Xmf\Slug;

class SlugGenerator
{
    public function __construct(
        private readonly ArticleRepository $repository,
    ) {}

    public function generateUniqueSlug(string $title): Slug
    {
        $baseSlug = Slug::create($title, ['maxLength' => 80]);
        $slug = $baseSlug;
        $counter = 1;

        while ($this->repository->slugExists($slug->toString())) {
            $slug = $baseSlug->withSuffix($counter);
            $counter++;
        }

        return $slug;
    }
}

// Usage:
// "my-article" → "my-article"
// "my-article" → "my-article-2"
// "my-article" → "my-article-3"

Integration with Value Objects

<?php

declare(strict_types=1);

namespace Xoops\Module\Domain\ValueObject;

use Xmf\Slug;

final readonly class ArticleSlug
{
    private const MAX_LENGTH = 100;

    private function __construct(
        private Slug $slug,
    ) {}

    public static function fromTitle(string $title): self
    {
        return new self(Slug::create($title, [
            'maxLength' => self::MAX_LENGTH,
            'separator' => '-',
            'lowercase' => true,
        ]));
    }

    public static function fromString(string $slug): self
    {
        if (!Slug::isValid($slug)) {
            throw new InvalidSlug("Invalid slug format: {$slug}");
        }
        return new self(Slug::fromString($slug));
    }

    public function withSuffix(int $number): self
    {
        return new self($this->slug->withSuffix($number));
    }

    public function toString(): string
    {
        return $this->slug->toString();
    }

    public function equals(self $other): bool
    {
        return $this->slug->equals($other->slug);
    }
}

Xmf\Request

HTTP Request Handling

Secure, type-safe access to request data.

<?php

use Xmf\Request;

// Get request method
$method = Request::getMethod(); // "GET", "POST", etc.

// Get typed values with defaults
$page = Request::getInt('page', 1);
$search = Request::getString('q', '');
$active = Request::getBool('active', true);
$price = Request::getFloat('price', 0.0);

// Get arrays
$ids = Request::getArray('ids', [], 'int');
$tags = Request::getArray('tags', [], 'string');

// Specific request types
$postData = Request::getVar('field', '', 'POST');
$cookie = Request::getVar('session', '', 'COOKIE');

// Check if request is AJAX
if (Request::isAjax()) {
    // Handle AJAX request
}

// Get client IP
$ip = Request::getClientIp();

Request Validation

<?php

use Xmf\Request;

class ArticleController
{
    public function store(): Response
    {
        // Get and validate input
        $title = Request::getString('title', '');
        $categoryId = Request::getInt('category_id', 0);
        $content = Request::getString('content', '');
        $tags = Request::getArray('tags', [], 'string');

        // Validate
        $errors = [];

        if (mb_strlen($title) < 3) {
            $errors['title'] = 'Title must be at least 3 characters';
        }

        if ($categoryId <= 0) {
            $errors['category_id'] = 'Please select a category';
        }

        if (count($tags) > 10) {
            $errors['tags'] = 'Maximum 10 tags allowed';
        }

        if (!empty($errors)) {
            return $this->validationError($errors);
        }

        // Process valid data...
    }
}

Xmf\FilterInput

Input Sanitization

Clean and sanitize user input safely.

<?php

use Xmf\FilterInput;

// Create filter instance
$filter = FilterInput::getInstance();

// Clean various types
$cleanString = $filter->clean($input, 'STRING');
$cleanInt = $filter->clean($input, 'INT');
$cleanFloat = $filter->clean($input, 'FLOAT');
$cleanBool = $filter->clean($input, 'BOOLEAN');
$cleanEmail = $filter->clean($input, 'EMAIL');
$cleanUrl = $filter->clean($input, 'URL');
$cleanHtml = $filter->clean($input, 'HTML');
$cleanCmd = $filter->clean($input, 'CMD');
$cleanWord = $filter->clean($input, 'WORD');
$cleanAlnum = $filter->clean($input, 'ALNUM');
$cleanPath = $filter->clean($input, 'PATH');
$cleanBase64 = $filter->clean($input, 'BASE64');

// Clean arrays
$cleanArray = $filter->clean($inputArray, 'ARRAY');

// Custom regex pattern
$clean = $filter->clean($input, 'REGEX', '/^[A-Z]{2,3}-\d{4}$/');

XSS Prevention

<?php

use Xmf\FilterInput;

class CommentService
{
    private FilterInput $filter;

    public function __construct()
    {
        $this->filter = FilterInput::getInstance();
    }

    public function sanitizeComment(string $content): string
    {
        // Remove potentially harmful HTML, keep safe tags
        return $this->filter->clean($content, 'HTML', [
            'allowedTags' => ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li'],
            'allowedAttributes' => ['a' => ['href', 'title']],
        ]);
    }

    public function sanitizePlainText(string $content): string
    {
        // Strip all HTML
        return $this->filter->clean($content, 'STRING');
    }
}

Xmf\Metagen

Meta Tag Generation

Generate SEO-friendly meta tags from content.

<?php

use Xmf\Metagen;

// Generate meta description from content
$description = Metagen::generateDescription($htmlContent, 160);

// Generate keywords from content
$keywords = Metagen::generateKeywords($htmlContent, 10);

// Generate both
$meta = Metagen::generate($htmlContent, [
    'descriptionLength' => 160,
    'keywordCount' => 10,
    'minWordLength' => 4,
    'stopWords' => ['the', 'and', 'for', 'with'],
]);

echo $meta['description'];
echo implode(', ', $meta['keywords']);

Integration with Articles

<?php

use Xmf\Metagen;

class ArticleSeoService
{
    public function generateMetaTags(Article $article): array
    {
        $content = $article->getContent()->value;

        // Use custom description if set, otherwise generate
        $description = $article->getMetaDescription()
            ?? Metagen::generateDescription($content, 160);

        // Use custom keywords if set, otherwise generate
        $keywords = $article->getMetaKeywords()
            ?? Metagen::generateKeywords($content, 10);

        return [
            'title' => $article->getTitle() . ' | My Site',
            'description' => $description,
            'keywords' => implode(', ', $keywords),
            'og:title' => $article->getTitle(),
            'og:description' => $description,
            'og:type' => 'article',
        ];
    }
}

Xmf\Jwt

JSON Web Token Handling

Secure JWT creation and validation.

<?php

use Xmf\Jwt;

// Create a JWT
$token = Jwt::create([
    'sub' => $userId,
    'name' => $userName,
    'role' => 'editor',
], $secretKey, [
    'expiresIn' => 3600, // 1 hour
    'issuer' => 'my-app',
]);

echo $token; // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

// Validate and decode
try {
    $payload = Jwt::verify($token, $secretKey, [
        'issuer' => 'my-app',
    ]);

    echo $payload['sub'];  // User ID
    echo $payload['name']; // User name
} catch (JwtException $e) {
    // Token invalid or expired
}

// Check if token is valid (no exception)
if (Jwt::isValid($token, $secretKey)) {
    // Token is valid
}

API Authentication

<?php

use Xmf\Jwt;
use Psr\Http\Message\ServerRequestInterface;

class JwtAuthMiddleware
{
    public function __construct(
        private readonly string $secretKey,
    ) {}

    public function process(ServerRequestInterface $request, $handler)
    {
        $header = $request->getHeaderLine('Authorization');

        if (!str_starts_with($header, 'Bearer ')) {
            return $this->unauthorized('Missing token');
        }

        $token = substr($header, 7);

        try {
            $payload = Jwt::verify($token, $this->secretKey);

            // Add user info to request
            return $handler->handle(
                $request->withAttribute('user', $payload)
            );
        } catch (JwtExpiredException $e) {
            return $this->unauthorized('Token expired');
        } catch (JwtException $e) {
            return $this->unauthorized('Invalid token');
        }
    }
}

Xmf\Yaml

YAML Configuration Parsing

Read and write YAML configuration files.

<?php

use Xmf\Yaml;

// Parse YAML string
$config = Yaml::parse($yamlString);

// Parse YAML file
$config = Yaml::parseFile('/path/to/config.yml');

// Dump array to YAML
$yaml = Yaml::dump($configArray, [
    'inline' => 2,      // Inline depth
    'indent' => 4,      // Indentation
]);

// Write to file
Yaml::dumpFile('/path/to/config.yml', $configArray);

Module Configuration

<?php

use Xmf\Yaml;

class ModuleConfig
{
    private array $config;

    public function __construct(string $modulePath)
    {
        $configFile = $modulePath . '/config/module.yml';

        if (!file_exists($configFile)) {
            throw new ConfigurationException('Module config not found');
        }

        $this->config = Yaml::parseFile($configFile);
    }

    public function get(string $key, mixed $default = null): mixed
    {
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (!isset($value[$k])) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }
}

// config/module.yml:
// database:
//   table_prefix: gs_
//   charset: utf8mb4
// cache:
//   enabled: true
//   ttl: 3600

$config = new ModuleConfig($modulePath);
echo $config->get('database.charset'); // "utf8mb4"
echo $config->get('cache.ttl');        // 3600

Xmf\Database

Database Utilities

Helper utilities for database operations.

<?php

use Xmf\Database\Tables;
use Xmf\Database\Migrate;

// Table prefix handling
$tables = new Tables();
$fullName = $tables->prefix('articles'); // "xoops_articles"

// Check if table exists
if ($tables->exists('articles')) {
    // Table exists
}

// Migration support
$migrate = new Migrate($connection);
$migrate->addTable('new_table', [
    'id' => 'int(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY',
    'name' => 'varchar(255) NOT NULL',
    'created_at' => 'datetime NOT NULL',
]);

$migrate->addColumn('articles', 'featured', 'tinyint(1) DEFAULT 0');
$migrate->addIndex('articles', 'idx_status', ['status']);

$migrate->execute();

Component Comparison

When to Use Each Component

flowchart TD
    TASK{What do you need?}

    TASK -->|Unique Identifier| ULID[Use Xmf\Ulid]
    TASK -->|URL-friendly string| SLUG[Use Xmf\Slug]
    TASK -->|Request data| REQ[Use Xmf\Request]
    TASK -->|Sanitize input| FILTER[Use Xmf\FilterInput]
    TASK -->|SEO meta tags| META[Use Xmf\Metagen]
    TASK -->|Token auth| JWT[Use Xmf\Jwt]
    TASK -->|Config files| YAML[Use Xmf\Yaml]

    ULID --> WHEN_ULID[Primary keys, timestamps,<br/>sortable identifiers]
    SLUG --> WHEN_SLUG[URLs, permalinks,<br/>readable identifiers]
    REQ --> WHEN_REQ[Form handling,<br/>API input]
    FILTER --> WHEN_FILTER[User content,<br/>XSS prevention]
    META --> WHEN_META[Article SEO,<br/>search optimization]
    JWT --> WHEN_JWT[API auth,<br/>stateless sessions]
    YAML --> WHEN_YAML[Module config,<br/>settings files]

Best Practices

1. Use Value Objects

Wrap XMF components in domain-specific value objects:

// Good
final readonly class ArticleId
{
    public static function generate(): self { ... }
}

// Instead of direct usage everywhere
$id = Ulid::generate();

2. Centralize Configuration

// config/xmf.php
return [
    'slug' => [
        'maxLength' => 100,
        'separator' => '-',
    ],
    'jwt' => [
        'secret' => $_ENV['JWT_SECRET'],
        'expiry' => 3600,
    ],
];

3. Type Safety

// Good - strongly typed
public function find(ArticleId $id): ?Article
{
    // $id is guaranteed to be valid ULID
}

// Avoid - stringly typed
public function find(string $id): ?Article
{
    // $id could be anything
}


xmf #ulid #slug #components #library #xoops-2026