Skip to content

PSR Standards Overview

Introduction

XOOPS 2026 embraces the PHP Standards Recommendations (PSR) published by the PHP Framework Interoperability Group (PHP-FIG). This adoption enables better code quality, improved testability, and seamless integration with the broader PHP ecosystem.

Adopted PSR Standards

Core Standards

PSR Name Purpose in XOOPS
PSR-4 Autoloader Class autoloading standard
PSR-7 HTTP Message Interface Request/Response handling
PSR-11 Container Interface Dependency injection
PSR-15 HTTP Server Middleware Request processing pipeline

Supporting Standards

PSR Name Purpose in XOOPS
PSR-1 Basic Coding Standard Code style foundation
PSR-3 Logger Interface Logging abstraction
PSR-12 Extended Coding Style Comprehensive code style
PSR-14 Event Dispatcher Event system architecture
PSR-16 Simple Cache Caching abstraction
PSR-17 HTTP Factories HTTP object creation
PSR-18 HTTP Client External HTTP requests

PSR-1: Basic Coding Standard

Overview

PSR-1 defines the fundamental coding standards that ensure a high level of technical interoperability between PHP code.

Key Requirements

<?php
// Files MUST use only <?php and <?= tags
// Files MUST use only UTF-8 without BOM for PHP code
// Class names MUST be declared in StudlyCaps
// Class constants MUST be declared in all upper case with underscore separators
// Method names MUST be declared in camelCase

namespace Xoops\Module\Publisher;

class ArticleController
{
    public const MAX_ARTICLES_PER_PAGE = 20;
    public const CACHE_TTL = 3600;

    public function listArticles(): ResponseInterface
    {
        // Method implementation
    }
}

PSR-3: Logger Interface

Overview

PSR-3 describes a common interface for logging libraries, enabling XOOPS to use any PSR-3 compliant logger.

Interface Definition

namespace Psr\Log;

interface LoggerInterface
{
    public function emergency(string|\Stringable $message, array $context = []): void;
    public function alert(string|\Stringable $message, array $context = []): void;
    public function critical(string|\Stringable $message, array $context = []): void;
    public function error(string|\Stringable $message, array $context = []): void;
    public function warning(string|\Stringable $message, array $context = []): void;
    public function notice(string|\Stringable $message, array $context = []): void;
    public function info(string|\Stringable $message, array $context = []): void;
    public function debug(string|\Stringable $message, array $context = []): void;
    public function log(mixed $level, string|\Stringable $message, array $context = []): void;
}

XOOPS Implementation with Monolog

namespace Xoops\Core\Logger;

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Psr\Log\LoggerInterface;

class XoopsLogger extends Logger implements LoggerInterface
{
    public function __construct(string $name = 'xoops')
    {
        parent::__construct($name);

        // Add rotating file handler
        $this->pushHandler(new RotatingFileHandler(
            XOOPS_VAR_PATH . '/logs/xoops.log',
            30,  // Keep 30 days of logs
            Logger::WARNING
        ));

        // Development: add stream handler
        if (getenv('XOOPS_DEBUG') === 'true') {
            $this->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG));
        }
    }

    // Legacy compatibility methods
    public function addQuery(string $sql, ?string $error = null, ?int $errno = null): void
    {
        $this->debug('Database query', [
            'sql' => $sql,
            'error' => $error,
            'errno' => $errno,
        ]);
    }

    public function addDeprecated(string $message): void
    {
        $this->warning('Deprecated: ' . $message);
    }
}

PSR-12: Extended Coding Style

Overview

PSR-12 extends and expands on PSR-1 and PSR-2 to provide comprehensive coding style guidelines.

Key Rules

<?php

declare(strict_types=1);

namespace Xoops\Module\Publisher\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Xoops\Core\View\ViewRendererInterface;
use Xoops\Module\Publisher\Service\ArticleService;

class ArticleController
{
    public function __construct(
        private readonly ArticleService $articleService,
        private readonly ViewRendererInterface $view,
    ) {}

    public function list(
        ServerRequestInterface $request,
        int $page = 1,
    ): ResponseInterface {
        $articles = $this->articleService->getPaginated($page);

        return $this->view->render('@modules/publisher/list', [
            'articles' => $articles,
            'page' => $page,
        ]);
    }
}

PSR-14: Event Dispatcher

Overview

PSR-14 standardizes the way events are dispatched and listeners are attached.

Interface Definitions

namespace Psr\EventDispatcher;

interface EventDispatcherInterface
{
    public function dispatch(object $event): object;
}

interface ListenerProviderInterface
{
    public function getListenersForEvent(object $event): iterable;
}

interface StoppableEventInterface
{
    public function isPropagationStopped(): bool;
}

XOOPS Event System

namespace Xoops\Core\Event;

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;

class EventDispatcher implements EventDispatcherInterface
{
    public function __construct(
        private readonly ListenerProviderInterface $listenerProvider
    ) {}

    public function dispatch(object $event): object
    {
        foreach ($this->listenerProvider->getListenersForEvent($event) as $listener) {
            $listener($event);

            if ($event instanceof StoppableEventInterface
                && $event->isPropagationStopped()
            ) {
                break;
            }
        }

        return $event;
    }
}

// Event definition
class ArticleCreatedEvent
{
    public function __construct(
        public readonly int $articleId,
        public readonly int $authorId,
        public readonly \DateTimeImmutable $createdAt
    ) {}
}

// Listener registration
$provider->addListener(ArticleCreatedEvent::class, function(ArticleCreatedEvent $event) {
    // Send notification, update statistics, etc.
});

PSR-16: Simple Cache

Overview

PSR-16 provides a simplified caching interface for basic use cases.

Interface Definition

namespace Psr\SimpleCache;

interface CacheInterface
{
    public function get(string $key, mixed $default = null): mixed;
    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool;
    public function delete(string $key): bool;
    public function clear(): bool;
    public function getMultiple(iterable $keys, mixed $default = null): iterable;
    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool;
    public function deleteMultiple(iterable $keys): bool;
    public function has(string $key): bool;
}

XOOPS Cache Implementation

namespace Xoops\Core\Cache;

use Psr\SimpleCache\CacheInterface;

class XoopsCache implements CacheInterface
{
    private const NULL_SENTINEL = '__XOOPS_NULL__';

    public function __construct(
        private readonly CacheInterface $backend
    ) {}

    public function get(string $key, mixed $default = null): mixed
    {
        $value = $this->backend->get($this->normalizeKey($key), self::NULL_SENTINEL);

        if ($value === self::NULL_SENTINEL) {
            return $default;
        }

        return $value === '__STORED_NULL__' ? null : $value;
    }

    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool
    {
        $storedValue = $value === null ? '__STORED_NULL__' : $value;
        return $this->backend->set($this->normalizeKey($key), $storedValue, $ttl);
    }

    private function normalizeKey(string $key): string
    {
        // Hash long keys to prevent backend issues
        if (strlen($key) > 64) {
            return hash('xxh3', $key);
        }
        return preg_replace('/[^a-z0-9._:-]/i', '_', $key);
    }
}

PSR-17: HTTP Factories

Overview

PSR-17 defines factory interfaces for creating PSR-7 HTTP objects.

Interface Definitions

namespace Psr\Http\Message;

interface RequestFactoryInterface
{
    public function createRequest(string $method, $uri): RequestInterface;
}

interface ResponseFactoryInterface
{
    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
}

interface ServerRequestFactoryInterface
{
    public function createServerRequest(
        string $method,
        $uri,
        array $serverParams = []
    ): ServerRequestInterface;
}

interface StreamFactoryInterface
{
    public function createStream(string $content = ''): StreamInterface;
    public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
    public function createStreamFromResource($resource): StreamInterface;
}

interface UriFactoryInterface
{
    public function createUri(string $uri = ''): UriInterface;
}

interface UploadedFileFactoryInterface
{
    public function createUploadedFile(
        StreamInterface $stream,
        int $size = null,
        int $error = UPLOAD_ERR_OK,
        string $clientFilename = null,
        string $clientMediaType = null
    ): UploadedFileInterface;
}

PSR-18: HTTP Client

Overview

PSR-18 defines a standard interface for sending HTTP requests and receiving responses.

Interface Definition

namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ClientInterface
{
    public function sendRequest(RequestInterface $request): ResponseInterface;
}

XOOPS HTTP Client Usage

namespace Xoops\Core\Http;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;

class HttpClient
{
    public function __construct(
        private readonly ClientInterface $client,
        private readonly RequestFactoryInterface $requestFactory
    ) {}

    public function get(string $url, array $headers = []): string
    {
        $request = $this->requestFactory->createRequest('GET', $url);

        foreach ($headers as $name => $value) {
            $request = $request->withHeader($name, $value);
        }

        $response = $this->client->sendRequest($request);

        return (string) $response->getBody();
    }
}

For Each PSR Standard

PSR Recommended Library Package
PSR-3 Monolog monolog/monolog
PSR-7 Nyholm PSR-7 nyholm/psr7
PSR-11 PHP-DI php-di/php-di
PSR-14 Symfony EventDispatcher symfony/event-dispatcher
PSR-15 Laminas Stratigility laminas/laminas-stratigility
PSR-16 Symfony Cache symfony/cache
PSR-17 Nyholm PSR-7 nyholm/psr7
PSR-18 Guzzle guzzlehttp/guzzle

Composer Requirements

{
    "require": {
        "php": ">=8.2",
        "psr/log": "^3.0",
        "psr/http-message": "^2.0",
        "psr/container": "^2.0",
        "psr/http-server-middleware": "^1.0",
        "psr/simple-cache": "^3.0",
        "psr/http-factory": "^1.0",
        "psr/http-client": "^1.0",
        "psr/event-dispatcher": "^1.0",
        "monolog/monolog": "^3.0",
        "nyholm/psr7": "^1.8",
        "php-di/php-di": "^7.0"
    }
}

Benefits of PSR Adoption

1. Interoperability

  • Use any PSR-compliant library
  • Swap implementations without code changes
  • Integrate with modern frameworks

2. Testability

  • Mock interfaces easily
  • Isolate components for testing
  • Predictable behavior

3. Maintainability

  • Consistent code structure
  • Clear contracts
  • Industry-standard patterns

4. Community

  • Leverage community packages
  • Attract modern PHP developers
  • Future-proof architecture

See Also

External Resources


xoops2026 #psr-standards #php-fig #interoperability #best-practices