tahona/spark-mvc

Web MVC 框架

5.0.5 2019-01-13 01:23 UTC

README

这个仓库是做什么的?

非常快的 PHP 框架,具有自动加载和注解。所有配置都由注解和 beans 处理。

工作原理

框架自动从 app/src 路径加载 beans,只需要用以下注解之一进行注解:@Component, @Service, @Configuration 或 @Repository

可以使用 @Inject 注解实现 bean 注入。

初始化在第一个请求上完成。第二个请求将执行已经注入 beans 的 Controllers 动作。

请求执行时间非常小,因为它只是用户代码执行的时间。

因此,对于真正的生产代码,我们得到了每个请求动态数据 20ms-40ms

快速入门

为了快速开始,下载示例项目: https://github.com/primosz67/spark-mvc-example

Index php - 解释

app/public/index.php

error_reporting(E_ALL);

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

require __VENDOR__ . "/autoload.php";

注意:如果您将使用 doctrine 数据库框架,请在此处添加行 - "AnnotationRegistry::registerLoader('class_exists');"

框架设置

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

配置

app/src/MyAppConfig.php

/**
 * @Configuration()
 * @EnableApcuBeanCache(resetParam="reset")
 */
class MyAppConfig {
}
  • resetParam - 清除缓存的参数。 (GET http://app.com?reset)。它用于开发环境,应该在生产环境中删除。

控制器

1. 带有注解控制器的标准控制器

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"
        ));
    }
}

访问 localhost/get 或 localhost/index;

  • 请求通过类型注入到方法中
  • SomeForm 使用 "spark-form" 模块通过 "POST" 表单参数填写。
  • $beanName - 框架可以通过名称或类型注入任何需要的 bean 或服务。
2. Rest 控制器
/**
* @RestController
*/
class MyRestController {

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

注意:RestController 中的所有方法都将解析为 JSON。

SomeResultDto - 将解析为 json。

3. 创建控制器的旧方法

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"
        ));
     }

}

访问 localhost/get 或 localhost/index;

2. 定义用于自动加载的 bean。

注入

1. 定义用于自动加载的 bean。
/**
* @Service()
*/
class UserService {
    //...some methods
}

注意:要启用 apcu @EnableApcuBeanCache("reset") 以自动加载注入,请调用 http://website?reset

2. 定义其他 beans 并注入 bean。
/**
* @Component()
*/
class AddUserHandler {

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

     /**
     * @PostConstruct()
     */
     public function init() {
        $this->userService->doSomething();
     }
}
3. 在控制器中注入
class UserController extends Controller {

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

    /**
     * @Path("/newView")
     */
     public function showNewViewAction() {
        return new ViewModel(array(
            "users"=>$this->userService->getAllUsers()
        ));
     }
}
4. 在控制器动作方法中注入
/**
* @Controller
*/
class UserController {

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

视图

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

  1. 对于 app/src/MyAppController@showNewViewAction,我们得到:apc/view/myapp/showNewView.tpl
  2. 对于 app/src/some/serious/package/controller/MyAppController@showNewViewAction,我们得到:apc/view/some/serious/package/myapp/showNewView.tpl

默认情况下会删除关键词 action 和 controller。

Smarty 内置路径插件

函数路径可以在 tpl 文件中使用(例如:index.tpl)

{path path="/user/register"}

命令将解析为: www.example.com/user/register

{path path="RegisterController@registerAction"}

命令将解析为: www.example.com/user/register

  • com.example.RegisterController - 可选的控制器类名称
  • registerAction - RegisterController 中的方法名称
{path path="@removeAction@id:4,type:blogpost"}

命令将解析为: www.example.com/blogpost/remove/4

  • {controller} - 当前请求控制器将被使用
  • removeAction - RegisterController 中的方法名称
  • id:4,type:blogpost - 参数将解析为 /{type}/remove/{id} 的最终形式

Apcu Bean Cache

如果添加了 @EnableApcuBeanCache 注解,则通过请求 localhost:80?reset (GET 参数 "reset") 来重置 beans 并重新初始化它们。

邮件发送器

  • @EnableMailer - TODO
  • spark.mailer.enabled (true/false)- 属性

@Annotations

Spark 框架的核心。

  • @Component,@Service,@Repository,@Configuration - 做的是相同的事情,但关键是目的。
  • @Bean
  • @PostConstruct
  • @Inject
  • @Bean

应用程序参数

$this->config

基本参数:app.path - 到/app目录的第一路径 src.path - 到/app/src目录的路径 app.paths - 在不同上下文中的/app目录的路径或路径集合

获取参数

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

更新或设置参数

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

自定义模块加载

如果您创建了一个用于其他项目的通用模块,请记住使用@Bean注解创建bean。这将更容易一次性添加新模块。

在您的某些应用程序配置中添加其他OtherModuleConfig。

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

OtherModuleConfig中的所有@Bean注解都将被创建并注入到您的类中。

多个数据库连接(示例)

处理多个连接。要创建Doctrine的EntityManager,您可以使用简单的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;
    }

注意:如果您想使用与基本EntityManager不同的实体管理器的CrudDao,请使用@OverrideInject注解。

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

国际化

Bean定义

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

"pl"、"cz"、"en"是带有"lang"键的cookie值;

  • 属性文件/house/house_pl.properties
core.thank.you.message=Thank You {0} and {1}
  • 在PHP中使用
    /**
     * @Inject
     * @var langMessageResource
     */
    private $langMessageResource;


    ...

    $this->langMessageResource->get("core.thank.you.message", array("John", "Trevor"));
  • 在smarty中使用
{lang code="core.thank.you.message" value=["John", "Trevor"]}

结果:感谢John和Trevor

@Path

注解定义

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

在Controller类中获取路径参数;

$this->getParam("test");

通过组件多路径获取

示例是用于动态菜单模块,当添加新项目或类时将弹出。

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

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

拦截器

/**
* @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())
    }
}

PHP cli的命令行为

首先,创建Command实现的类

/**
 * @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!");
    }
}

在控制台执行

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

输出

executing example:exampleCommand

object(...)

finish!

内置缓存服务

对于缓存数据库请求或加载文件数据来说是一件好事。注解可以与不同的缓存一起使用。甚至可以自定义实现spark/cache/Cache接口的缓存bean。

如何使用

在Bean类中添加@Cache注解。

/**
 * @Cache(name="cache", key="user {0}.id", time=10)
 */
public function getLeaderByCompany($company){
    return $this->someDao->getByCompanyId($company->getId())
}
  • "name"是实现spark\cache\Cache接口的bean的名称。
  • 添加应用程序所需ApcCache作为默认名称="cache"
  • "time" - 可选参数,表示分钟(10分钟)
  • "key"参数用于区分缓存值

配置文件

$profileName = "production";
$engine = new Engine("przemek_config",$profileName,  __ROOT__ . "app");
@Configuration
@Profile(name="production")
class SomeProductionConfig(){
..
}

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

在这种情况下,SomeDevelopmentConfig不会被添加到容器中,以及在其中声明的bean(@Bean)。

错误处理 - 示例

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;
    }
}

其中错误处理器具有等于0的顺序,将首先被调用。如果您返回ViewModel,处理将停止,视图将作为响应返回。

事件总线

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

事件定义

class SomeEvent implements Event {...}

订阅

@Component 
class SomeListener {

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

}

AnnotationHandler

添加您自己的注解

class NewAnnotationHandler extends AnnotationHandler{
    ...
}

安装 - Composer - 加速

composer dump-autoload -a

性能 - 一些数字

测试案例:具有小型数据库的实际项目。AB(Apache benchmark)请求10000和1000个并行连接。

  • Smarty编译:在每次请求(开发模式)中渲染Smarty模板。
  • Smarty:生产环境下的smarty。
  • Smarty + @Cache(Redis):使用@Cache注解对数据库请求进行缓存(我们使用Redis进行缓存)。当有更多对数据库的调用时,这可以提高性能。

安装 - Composer

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

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