PHPackages                             tahona/spark-mvc - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. tahona/spark-mvc

ActiveLibrary[Framework](/categories/framework)

tahona/spark-mvc
================

Web MVC Framework

5.0.5(7y ago)537021MITPHPPHP &gt;=7.1

Since Jan 29Pushed 7y ago1 watchersCompare

[ Source](https://github.com/primosz67/tahona-spark-mvc)[ Packagist](https://packagist.org/packages/tahona/spark-mvc)[ Docs](https://github.com/primosz67/spark-spark)[ RSS](/packages/tahona-spark-mvc/feed)WikiDiscussions master Synced 2mo ago

READMEChangelogDependencies (2)Versions (82)Used By (1)

README
======

[](#readme)

### What is this repository for?

[](#what-is-this-repository-for)

Very fast PHP framework with autoloading and annotations. All configuration is handled by annotations and beans.

#### How it works

[](#how-it-works)

Framework automatically load beans from app/src path it just need to be annotated with one annotation of: **@Component, @Service, @Configuration or @Repository**.

Bean injection can be achieved with **@Inject** annotation.

Initialization is done on the first request. The second request execute Controllers action, that has already injected beans in it.

Request execution time is very small because it's time of user code execution only.

Thanks to this, for real production code we got **20ms-40ms** per request for dynamic data.

### Quick Start

[](#quick-start)

For quick start donwload example project:

### Index php - explained

[](#index-php---explained)

app/public/index.php

```
error_reporting(E_ALL);

define("__ROOT__", __DIR__ . "/../../");
define("__VENDOR__", "../../vendor");

require __VENDOR__ . "/autoload.php";
```

Note: If you will use doctrine db framework add here line - "AnnotationRegistry::registerLoader('class\_exists');"

Framework setup:

```
$profileName ="someNameProfile";
$engine = new Engine("przemek_config", $profileName, __ROOT__ . "app");
$engine->run();
```

### Configuration

[](#configuration)

app/src/MyAppConfig.php

```
/**
 * @Configuration()
 * @EnableApcuBeanCache(resetParam="reset")
 */
class MyAppConfig {
}
```

- resetParam - parameter for clearing cache. (GET ). It's for development environment and should be deleted for production.

### Controller

[](#controller)

##### 1. Standard Controller with annotation controller

[](#1-standard-controller-with-annotation-controller)

app/src/MyAppController.php

```
/**
* @Controller
*/
class MyAppController {

    /**
     * @Path("/index", method="GET")
     */
    public function indexAction(Request $request) {
        return new PlainViewModel("Hello World");
    }

   /**
     * @Path("/save", method="POST")
     */
    public function saveAction(SomeForm $form, Request $req, $beanName) {
        return new JsonViewModel(array(
            "user"=>"TODO"
        ));
    }
}
```

Go to localhost/get or localhost/index;

- Request is injected to method by Type
- SomeForm is filled by "POST" form params using "spark-form" module.
- $beanName - framework can inject any bean or service needed for action by name or type.

##### 2. Rest Controller

[](#2-rest-controller)

```
/**
* @RestController
*/
class MyRestController {

    /**
     * @Path("/index")
     */
    public function indexAction(Request $request) {
        return new SomeResultDto();
    }
}
```

Note: All methods in RestController will resolve to JSON.

SomeResultDto - will resolve to json.

##### 3. Old method of creating Controller

[](#3-old-method-of-creating-controller)

app/src/MyAppController.php

```
class MyAppController extends Controller {

    /**
     * @Path("/index")
     */
    public function indexAction() {
        return new PlainViewModel("Hello World");
    }

    /**
     * @Path("/get")
     */
    public function getAction() {
        return new JsonViewModel(array(
            "user"=>"TODO"
        ));
    }

    /**
     * @Path("/newView")
     */
     public function showNewViewAction() {
        return new ViewModel(array(
            "user"=>"TODO"
        ));
     }

}
```

Go to localhost/get or localhost/index;

##### 2. Define bean for autoload.

[](#2-define-bean-for-autoload)

### Injection

[](#injection)

##### 1. Define bean for autoload.

[](#1-define-bean-for-autoload)

```
/**
* @Service()
*/
class UserService {
    //...some methods
}
```

Note: for enabled apcu @EnableApcuBeanCache("reset") to autoload injection, call

##### 2. Define other beans and inject out bean.

[](#2-define-other-beans-and-inject-out-bean)

```
/**
* @Component()
*/
class AddUserHandler {

    /**
    * @Inject()
    * var UserService
    */
    private $userService;

     /**
     * @PostConstruct()
     */
     public function init() {
        $this->userService->doSomething();
     }
}
```

##### 3.Inject in controller

[](#3inject-in-controller)

```
class UserController extends Controller {

    /**
    * @Inject()
    * var UserService
    */
    private $userService;

    /**
     * @Path("/newView")
     */
     public function showNewViewAction() {
        return new ViewModel(array(
            "users"=>$this->userService->getAllUsers()
        ));
     }
}
```

##### 4.Inject in action method of controller

[](#4inject-in-action--method-of-controller)

```
/**
* @Controller
*/
class UserController {

    /**
     * @Path("/newView")
     */
     public function showNewViewAction(Request $request, UserService $userService ) {
        return new ViewModel(array(
            "users"=>$userService->getAllUsers()
        ));
     }
}
```

### View

[](#view)

apc/view/{controller package}/{controllerName (without "Controller")}/{action}.tpl

1. For app/src/MyAppController@showNewViewAction we get: apc/view/myapp/showNewView.tpl
2. For app/src/some/serious/package/*controller*/MyAppController@showNewView*Action* we get: apc/view/some/serious/package/myapp/showNewView.tpl

Keywords action and controller are deleted by default.

#### Smarty built-in path plugins

[](#smarty-built-in-path-plugins)

Function path can be used in tpl file (example: index.tpl)

```
{path path="/user/register"}

```

Command will resolve to : [www.example.com/user/register](http://www.example.com/user/register)

```
{path path="RegisterController@registerAction"}

```

Command will resolve to : [www.example.com/user/register](http://www.example.com/user/register)

- **com.example.RegisterController** - optional Controller class Name
- **registerAction** - method name in RegisterController

```
{path path="@removeAction@id:4,type:blogpost"}

```

Command will resolve to : [www.example.com/blogpost/remove/4](http://www.example.com/blogpost/remove/4)

- **{controller}** - current request controller will be used
- **removeAction** - method name in RegisterController
- **id:4,type:blogpost** - params will resolve /{type}/remove/{id} to it's final form

### Apcu Bean Cache

[](#apcu-bean-cache)

if @EnableApcuBeanCache annotation is added with @Configuration the only way to reset beans and init them once more is by requestin localhost:80?reset (GET parameter "reset").

### Mailer

[](#mailer)

- @EnableMailer -TODO
- spark.mailer.enabled (true/false)- property

### @Annotations

[](#annotations)

The heart of Spark Framework.

- @Component,@Service,@Repository,@Configuration - do the same thing, but purpose is the key.
- @Bean
- @PostConstruct
- @Inject
- @Bean

### Application Parameters

[](#application-parameters)

$this-&gt;config

Base parameters: app.path - pierwsza ścieżka do katalogu /app src.path - ścieżka do katalogu /app/src app.paths - ścieżka albo ścieżki do katalogu /app w różnych kontekstach

to fetch parameters:

```
 $appPath = $this-config->getProperty("app.path");
```

update or set Params

```
 $this-config->setProperty("customModule.some.property.", "/my/new/path");
```

### Custom module loading

[](#custom-module-loading)

If you create common module to use in other project remember to create beans by @Bean annotation. It will be easier to add new module in one go.

In some your app configuration add others OtherModuleConfig.

```
/**
* @Bean
*/
public function otherBeanConfiguration () {
    return new OtherModuleConfig();
}
```

All @Bean annotation in OtherModuleConfig will be created and inject to your classes.

### Multiple DataBase connection (example)

[](#multiple-database-connection-example)

Handle multiple connections. To create Doctrine's EntityManager you can use simple EntityManagerFactory.

```
    /**
     * @Inject()
     * @var EntityManagerFactory
     */
    private $entityManagerFactory;

    /**
    * @Bean()
    */
    public function entityManager() {
        return $this->entityManagerFactory->createEntityManager($this->getDataSource());
    }

    /**
    * @Bean()
    */
    public function superEntityManager() {
        return $this->entityManagerFactory->createEntityManager($this->getDataSourceSuper());
    }

    public function getDataSource() {
        $dbConfig = new DataSource();
        $dbConfig->setDbname("my-db");
        $dbConfig->setHost("127.0.0.1");
        $dbConfig->setUsername("root");
        $dbConfig->setPassword("test");
        $dbConfig->setPackages([
            "com/myapp/user/domain" //path to doctrine entity
        ]);
        return $dbConfig;
    }

    public function getDataSourceSuper() {
        $dbConfig = new DataSource();
        $dbConfig->setDbname("super");
        $dbConfig->setHost("127.0.0.1");
        $dbConfig->setUsername("root");
        $dbConfig->setPassword("test");
        return $dbConfig;
    }
```

Note: for using CrudDao with other enityManager than basic use @OverrideInject annotation

```
/**
* @OverrideInject(oldName="entityManager", newName="houseManager")
*/
class MySuperDao extends CrudDao {
}
```

### Internalization

[](#internalization)

Bean definition

```
    /**
    * @Bean
    */
    public function anyPrefixNameMessageResource() {
        return new LangResourcePath(array(
            "pl"=>array(
                "/house/house_pl.properties"
            ),
            "cz"=> array(...),
            "en"=>array(...),
        ));
    }
```

Where "pl","cz","en" are cookie value with key "lang";

- Property file /house/house\_pl.properties

```
core.thank.you.message=Thank You {0} and {1}

```

- Use in php

```
    /**
     * @Inject
     * @var langMessageResource
     */
    private $langMessageResource;

    ...

    $this->langMessageResource->get("core.thank.you.message", array("John", "Trevor"));
```

- Use in smarty

```
{lang code="core.thank.you.message" value=["John", "Trevor"]}

```

Results: Thank You John and Trevor

### @Path

[](#path)

Annotation Defonition

```
@Path(path="/login/{test}", method="get")
```

Fetch path paramether in Controller class;

```
$this->getParam("test");
```

### MULTI fetch by component

[](#multi-fetch-by-component)

Example is for dynamic menu module that will popup when new project or classes are added.

```
    /**
    * @Inject
    */
    private $beanProvider;

    public function getMenuModules() {
        return $beanProvider->getByType(MenuModule::class);
    }
```

### Interceptors

[](#interceptors)

```
/**
* @Component
*/
class UserInterceptor implements HandlerInterceptor {

    /**
    * @Inject
    */
    private $someUserHolder;

    public function preHandle(Request $request) {
        //Do something like redirect or add values to request or auto-conversion(id to entity);
    }

    public function postHandle(Request $request, ViewModel $viewModel) {
        $viewModel->add("loggedUser", $someUserHolder->getUserFromSession())
    }
}
```

### Command behaviour for PHP cli

[](#command-behaviour-for-php-cli)

First, create class Command implementation

```
/**
 * @Component()
 */
class ExampleCommand implements Command {

    /**
     * @Inject()
     * @var SomeBean
     */
    private $someBean;

    public function getName() :string{
        return "example:exampleCommandCommand";
    }

    public function execute(InputInterface $in, OutputInterface $out) : void {
        $out->writeln("executing " . $this->getName());

        //Example ....
        $this->someBean->doSomething()
        $out->writeObject($this->someBean->getSomething());

        $out->writeln("finish!");
    }
}
```

in console execute:

```
php app/public/index.php command=example:exampleCommand profile=production

```

Output:

```
executing example:exampleCommand

object(...)

finish!

```

### Built-in cache service

[](#built-in-cache-service)

Great thing for caching DB request or loading files data. Annotation can be used with different cache. Even custom cache bean that implement spark/cache/Cache

##### How To

[](#how-to)

In Bean class add @Cache annotation.

```
/**
 * @Cache(name="cache", key="user {0}.id", time=10)
 */
public function getLeaderByCompany($company){
    return $this->someDao->getByCompanyId($company->getId())
}
```

- "name" is a name of bean that implement spark\\cache\\Cache interface.
- ApcCache needed for application is added as default name="cache"
- "time" - optional parameter that is in minutes(10 minutes)
- "key" parameter is for distinguish cached values

### Profiles

[](#profiles)

```
$profileName = "production";
$engine = new Engine("przemek_config",$profileName,  __ROOT__ . "app");
```

```
@Configuration
@Profile(name="production")
class SomeProductionConfig(){
..
}

@Configuration
@Profile(name="development")
class SomeDevelopmentConfig(){
..
}
```

In this case SomeDevelopmentConfig won't be added to container and bean declared in it (@Bean) as well.

### Error handling - example

[](#error-handling---example)

```
class NotFoundErrorHandler extends ExceptionResolver {

    public function doResolveException($ex) {
        if ($ex instanceof RouteNotFoundException || $ex instanceof EntityNotFoundException) {
            ResponseHelper::setCode(HttpCode::$NOT_FOUND);

            $viewModel = new ViewModel();
            $viewModel->setViewName("/house/web/error/notFound");
            return $viewModel;
        }
        return null;
    }

    public function getOrder() {
        return 400;
    }
}
```

where error handler with order equal 0 , will be first to invoke. If you return *Viewmodel* the handling will stop and the view will be return as response.

### Event Bus

[](#event-bus)

```
/**
* @Inject
*/
private $eventBus;
...
$this->eventBus->post(new SomeEvent());
```

Event definition :

```
class SomeEvent implements Event {...}
```

Subscription

```
@Component
class SomeListener {

  /**
  * @Subscribe
  */
  public function handleSomeCase(SomeEvent $event) {
     //...logic
  }

}
```

### AnnotationHandler

[](#annotationhandler)

Add Your own annotation

```
class NewAnnotationHandler extends AnnotationHandler{
    ...
}

```

### Installation - Composer - Speed up

[](#installation---composer---speed-up)

```
composer dump-autoload -a

```

### Performance - Some numbers

[](#performance---some-numbers)

**Tested Case**: Real project with small database. AB(Apache benchmark) requests 10000 and 1000 parallel connections.

- **Smarty compile**: Smarty template rendering for each request (Development mode).
- **Smarty**: smarty with production setup.
- **Smarty + @Cache (Redis)**: request to database are cached with @Cache annotation (we use Redis for caching). It can give big improvement to performance when there is more calls to DB.

ModeRequest time (per request)Smarty compile~130msSmarty~45msSmarty + @Cache (Redis)**~25ms**### Installation - Composer

[](#installation---composer)

```
{
    "autoload": {
        "psr-4": {"": "app/src/"}
	},

	"require": {
        "smarty/smarty": "3.1.27",
        "tahona/spark-mvc": "*"
    }
}

```

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity18

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity73

Established project with proven stability

 Bus Factor1

Top contributor holds 93.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~9 days

Recently: every ~0 days

Total

80

Last Release

2676d ago

Major Versions

2.0.21 → 3.0.02017-09-10

3.1.17 → 4.0.02018-07-20

4.2.7 → 5.0.02019-01-11

PHP version history (4 changes)2.0.0PHP &gt;=5.2

2.0.2PHP &gt;=5.3

3.0.7PHP &gt;=7.0

4.0.0PHP &gt;=7.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/4ed36e133c0568c8e1f24f88bcc7f087521fd25ee63fe09ec0a894376e33ff0b?d=identicon)[crownclown67](/maintainers/crownclown67)

---

Top Contributors

[![tahonaPL](https://avatars.githubusercontent.com/u/19371479?v=4)](https://github.com/tahonaPL "tahonaPL (135 commits)")[![PrzemyslawZajac88](https://avatars.githubusercontent.com/u/41331729?v=4)](https://github.com/PrzemyslawZajac88 "PrzemyslawZajac88 (9 commits)")

---

Tags

auto-scanframeworkphpspring-bootmvc

### Embed Badge

![Health badge](/badges/tahona-spark-mvc/health.svg)

```
[![Health](https://phpackages.com/badges/tahona-spark-mvc/health.svg)](https://phpackages.com/packages/tahona-spark-mvc)
```

###  Alternatives

[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[laminas/laminas-mvc

Laminas's event-driven MVC layer, including MVC Applications, Controllers, and Plugins

17224.4M365](/packages/laminas-laminas-mvc)[letsdrink/ouzo

Ouzo PHP MVC framework

7210.5k1](/packages/letsdrink-ouzo)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
