openspring/springphp-framework

The SpringPHP framework

Maintainers

Package info

gitlab.com/openspring-projects/springphp-framework

Issues

Forum

pkg:composer/openspring/springphp-framework

Statistics

Installs: 170

Dependents: 0

Suggesters: 0

Stars: 1

7.4.5 2026-03-09 17:46 UTC

This package is not auto-updated.

Last update: 2026-03-23 17:59:51 UTC


README

pipeline status coverage report License: MIT PHP 7.4+

A PHP 7.4+ framework inspired by Java Spring, bringing annotation-driven dependency injection, RESTful controllers, ORM, JWT security, and validation to PHP.

Table of Contents

Features

  • Annotation-driven architecture — stereotypes (@Component, @Service, @Configuration), DI (@Autowired, @Value), and REST mappings parsed from PHP docblocks
  • IoC Container — singleton and prototype scoped beans with lazy loading and cyclic dependency detection
  • RESTful routing@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping with path variables, content negotiation, and automatic parameter binding
  • ORM — typed column mapping, fluent query builder, parameter binding, and state machine entity lifecycle
  • JWT Security — token processing with role-based and group-based access control
  • Validation — constraint-based validation (@NotNull, @NotBlank, @Email, @Min, @Max, @Size, @Regex, @Positive, @Past, @Future, etc.)
  • HTTP layer — request/response abstraction, ResponseEntity, message converters (JSON, HTML), cookies, headers, CORS
  • InterceptorspreHandle / postHandle hooks for cross-cutting concerns
  • Logging — built-in logger with Log4p implementation
  • TestingTestRunner for bootstrapping in TEST profile, Mockito-style mocking with when/thenReturn
  • i18nResourceBundleMessageSource for multi-language message loading

Requirements

  • PHP 7.4 or higher
  • Composer
  • MySQL (primary supported database)

Installation

For existing projects

composer require openspring/springphp-framework

For new projects

We recommend starting from the springphp-sample boilerplate, which provides a ready-to-use project structure with default configurations.

You can also use the springphp-initializer CLI to scaffold new projects, generate the database access layer, request mappings, API documentation and build deliveries.

Quick Start

1. Create the application entry point

<?php

use Openspring\SpringphpFramework\Core\SpringphpApplication;

/**
 * @ComponentScan(baseDirs = ["src/controllers", "src/services"])
 * @PropertySource("config/application.properties")
 */
class Application extends SpringphpApplication
{
    public function __construct()
    {
        parent::__construct(__DIR__, 'config/', 'config/test/');
    }
}

$app = new Application();
$app->execute();

2. Create a REST controller

<?php

use Openspring\SpringphpFramework\Web\RestController;
use Openspring\SpringphpFramework\Http\ResponseEntity;
use Openspring\SpringphpFramework\Http\HttpStatus;

class UserController extends RestController
{
    /**
     * @Autowired(true)
     * @var UserService
     */
    private $userService;

    /**
     * @GetMapping(value = "/users", consumes = "application/json", produces = "application/json")
     */
    public function getUsers()
    {
        $users = $this->userService->findAll();
        return ResponseEntity::ok($users);
    }

    /**
     * @GetMapping(value = "/users/{$id}", consumes = "application/json", produces = "application/json")
     */
    public function getUser($id)
    {
        $user = $this->userService->findById($id);
        return ResponseEntity::ok($user);
    }

    /**
     * @PostMapping(value = "/users", consumes = "application/json", produces = "application/json")
     */
    public function createUser(RequestBody $body)
    {
        $user = $this->userService->create($body);
        return ResponseEntity::status(HttpStatus::CREATED, $user);
    }
}

3. Create a service

<?php

use Openspring\SpringphpFramework\Core\Stereotype\Service;

/**
 * @Service
 */
class UserService extends Service
{
    /**
     * @Autowired(true)
     * @var UserDao
     */
    private $userDao;

    public function findAll(): array
    {
        return $this->userDao->findAll()->getResultList();
    }

    public function findById(int $id)
    {
        return $this->userDao->findById($id);
    }
}

4. Create an entity

<?php

use Openspring\SpringphpFramework\Orm\DaoSupport;
use Openspring\SpringphpFramework\Orm\Column;
use Openspring\SpringphpFramework\Orm\Type\DataType;
use Openspring\SpringphpFramework\Orm\Type\PrimaryKey;

class UserDao extends DaoSupport
{
    public $id;
    public $name;
    public $email;
    public $password;
    public $createdAt;

    public function __construct()
    {
        parent::__construct('#__users');

        $this->id        = new Column($this, 'id', 11, DataType::INT, new PrimaryKey());
        $this->name      = new Column($this, 'name', 100, DataType::VARCHAR);
        $this->email     = new Column($this, 'email', 150, DataType::VARCHAR, null, true);
        $this->password  = new Column($this, 'password', 255, DataType::HASH);
        $this->createdAt = new Column($this, 'created_at', 0, DataType::DATETIME);

        $this->initialize();
    }
}

Documentation

Application Bootstrap

The framework follows a structured boot sequence:

  1. SpringphpApplication constructor loads application.properties and sets up the Environment
  2. ApplicationContext::initialize() reads the .context cache (bean registry + route mappings)
  3. DatabaseContext::initialize() configures data sources
  4. On HTTP request: Router::route() matches the URL, then RestController::runAction() dispatches the action

REST Controllers

Controllers extend RestController and use docblock annotations for routing:

/**
 * @GetMapping(value = "/products/{$id}", produces = "application/json")
 */
public function getProduct($id) { ... }

/**
 * @PostMapping(value = "/products", consumes = "application/json", produces = "application/json")
 */
public function createProduct(RequestBody $body) { ... }

/**
 * @PutMapping(value = "/products/{$id}", consumes = "application/json", produces = "application/json")
 */
public function updateProduct($id, RequestBody $body) { ... }

/**
 * @DeleteMapping(value = "/products/{$id}", produces = "application/json")
 */
public function deleteProduct($id) { ... }

Available mapping annotations: @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping

Parameter binding: RequestBody, RequestHeader, RequestCookie, RequestTemporaryFile

Dependency Injection

The IoC container manages beans as lazy-loaded singletons (default) or prototypes.

/**
 * @Service
 */
class OrderService extends Service
{
    /**
     * @Autowired(true)
     * @var OrderDao
     */
    private $orderDao;

    /**
     * @Autowired(true)
     * @var NotificationService
     */
    private $notificationService;

    /** @Value("app.order.max-items") */
    private $maxItems;
}
  • @Autowired(true) — injects a bean by type (field injection); requires @var on a separate line
  • @Value("property.key") — injects a value from application.properties
  • Stereotypes: @Component, @Service, @Configuration
  • Cyclic dependency detection is built in

ORM & Entities

Entities extend DaoSupport and define typed columns:

class ProductDao extends DaoSupport
{
    public $id;
    public $name;
    public $price;
    public $active;

    public function __construct()
    {
        parent::__construct('#__products');

        $this->id     = new Column($this, 'id', 11, DataType::INT, new PrimaryKey());
        $this->name   = new Column($this, 'name', 200, DataType::VARCHAR, null, false, true);
        $this->price  = new Column($this, 'price', 10, DataType::FLOAT);
        $this->active = new Column($this, 'active', 1, DataType::BOOLEAN);

        $this->initialize();
    }
}

Supported column types: INT, VARCHAR, FLOAT, BOOLEAN, DATE, DATETIME, JSON, GUID, HASH

The #__ prefix in table names is automatically replaced by the configured table prefix from your data source.

Query Builder

Fluent API for building SQL queries with parameter binding:

$results = $productDao->select(['id', 'name', 'price'])
    ->from()
    ->where('active', '=', 1)
    ->and('price', '>', ':minPrice')
    ->setParameter(':minPrice', 25.00)
    ->orderBy('name', 'ASC')
    ->getResultList();

Validation

Constraint-based validation using docblock annotations on method parameters:

ConstraintDescription
@NotNullValue must not be null
@NotBlankString must not be null or empty
@NotEmptyCollection/string must not be empty
@EmailMust be a valid email address
@Min(value)Numeric minimum
@Max(value)Numeric maximum
@Size(min, max)String/array length bounds
@Regex(pattern)Must match the regular expression
@Positive / @PositiveOrZeroMust be positive (or zero)
@Negative / @NegativeOrZeroMust be negative (or zero)
@Past / @PastOrPresentDate must be in the past
@Future / @FutureOrPresentDate must be in the future
@AssertTrue / @AssertFalseBoolean assertions
@URLMust be a valid URL
@InArray(values)Must be one of the specified values

Security (JWT)

JWT-based authentication with role and group access control:

/**
 * @GetMapping(value = "/admin/dashboard", produces = "application/json", groupIn = "1,2", roleIn = "ADMIN")
 */
public function dashboard() { ... }
  • groupIn — comma-separated list of authorized group IDs ("*" for all authenticated users)
  • roleIn — comma-separated list of authorized roles ("*" for all authenticated users)

Implement IJwtProcessor to handle token extraction and validation, and IUser for the current user model.

Configuration

Configuration is managed through properties files:

# application.properties
#################### COMMON CONFIG #########################
# application display name
springphp.application.name=appName
# project mode; supported (values: DEV|QUALIF|VAL|PPROD|REHEARSAL|PROD)
springphp.profile.active=DEV
# relative web home url; put / if the application is on the www root or something like /app1/ in other case
springphp.application.context.path=/relative/path/to/api/
# base is {rootPath}: application absolute public path
springphp.path.public=/relative/path/to/public/directory/
# base is {rootPath}: main sources relative path
springphp.path.sources=src/main/php/
# base is {rootPath}: test sources relative path
springphp.path.tests=src/test/php/
#################### DATABASE CONFIG ####################
# databases config names to connect to on application startup (config names must be separated by commas)
# Exemples: defaultDatabase, customersDatabase, clientsDatabase, commandesDatabase
springphp.database.targets=defaultDatabase
####################### LOG CONFIG #########################
# enable log. If false all logging is disabled !
springphp.log.enable=false
# error logging file path
springphp.log.file=logs\server_%d.log
# log file max size in KO (example: 1024 ; 2048)
springphp.log.file.maxsize=1024
# log level [SQL | DEBUG | INFO | WARN | ERROR]
springphp.log.level=SQL
# log output format: known var names: %d (date) ; %l (log level) ; %m (message)
springphp.log.format=[%d][%ns][%c][%l] %m
# application.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<orm-configuration>
  <datasources>
    <!-- default datasource -->
    <datasource id="defaultDatabase">
      <!-- connection parametrs -->
      <dialect>Openspring\SpringphpFramework\Orm\Driver\Mysql\MysqlDriver</dialect>
      <host>localhost</host>
      <database>db_name</database>
      <port>3306</port>
      <user>root</user>
      <password></password>
      <tableprefix>tbl_</tableprefix>
    </datasource>
  </datasources>
  <!-- show sql when executing queries -->
  <show_sql>true</show_sql>
</orm-configuration>

Profile-based configuration: Use dev-application.properties, test-application.properties, or prod-application.properties for environment-specific overrides.

Access properties in code:

// Via annotation
/** @Value("app.name") */
private $appName;

// Programmatically
$value = Environment::getProperty("app.name");

Interceptors

Implement HttpInterceptor for cross-cutting concerns like logging, auth checks, or metrics:

/**
 * @Component
 */
class AuthInterceptor extends HttpInterceptor
{
    public function preHandle(): bool
    {
        // Return false to halt the request
        return true;
    }

    public function postHandle(): bool
    {
        return true;
    }
}

Logging

Built-in logging with LogFactory:

$logger = LogFactory::getLogger(self::class);

$logger->info('Processing order');
$logger->debug('Order details: ' . $orderId);
$logger->error('Payment failed');

Testing

# Run all tests
vendor/bin/phpunit --configuration phpunit.xml

# Run a specific test class
vendor/bin/phpunit --filter UserServiceTest

# Run a specific test method
vendor/bin/phpunit --filter testCreateUser

# Run with coverage
vendor/bin/phpunit --coverage-text --configuration phpunit.xml

The framework provides a TestRunner that bootstraps the application in TEST profile and allows simulating HTTP requests. A Mockito-style mocking utility is also available:

use Openspring\SpringphpFramework\Mock\Mockito;

$mock = Mockito::mock(UserService::class);
Mockito::when($mock, 'findById')->thenReturn($expectedUser);

Project Structure

springphp-framework/
├── src/
│   ├── main/php/Openspring/SpringphpFramework/
│   │   ├── Annotation/       # Docblock annotation parsing
│   │   ├── Context/          # ApplicationContext, DatabaseContext, Environment
│   │   ├── Core/             # Bean, Component, Service, SpringphpApplication
│   │   ├── Enumeration/      # Framework enums
│   │   ├── Exception/        # Exception hierarchy (HTTP, Bean, Data, IO)
│   │   ├── Http/             # Request, Response, ResponseEntity, converters
│   │   ├── IO/               # File and directory utilities
│   │   ├── Log/              # Logger and Log4p implementation
│   │   ├── Mail/             # Mail sender
│   │   ├── Mock/             # Mockito-style test mocking
│   │   ├── Orm/              # DaoSupport, Column, QueryBuilder, drivers
│   │   ├── Reflection/       # ClassReflection for docblock parsing
│   │   ├── Security/         # JWT processing, access control
│   │   ├── Type/             # ArrayList, Any, DynamicObject
│   │   ├── Utils/            # Strings, Arrays, Dates, Encryption, etc.
│   │   ├── Validation/       # Constraint-based validation
│   │   └── Web/              # RestController, Router, RestTemplate
│   └── test/php/TestCase/    # Unit tests (mirrors main structure)
├── composer.json
├── phpunit.xml
└── LICENSE.txt

The project follows a Maven-style directory layout with PSR-4 autoloading.

Contributing

There are currently a number of outstanding issues that need to be addressed. Please read How to contribute.

You can report bugs and request features on the issue tracker.

License

This project is licensed under the MIT License. See LICENSE.txt for details.