如何在 PHP 中正确构造和传递 MVC 结构中的对象

How to properly structure and pass objects in a MVC structure in PHP

在过去的两年里,我对PHP MVC 风格的架构已经相当熟悉,并且从那时起我的所有项目都使用MVC 结构进行开发。

一直困扰我的一个问题是如何对函数和数据库调用进行分组。我 运行 需要跨模型执行相同的操作。我不想在每个模型中重复这些操作和 sql 查询,而是将所有用户操作分组到一个单独的 class.

例如,假设我有一个网站,其中包含论坛、博客和个人资料页面,每个页面都有单独的模型、视图和控制器。但是,假设这些页面中的每一个都需要对用户执行相同的操作 table.

我的模型 class 是使用数据库对象自动构建的。如果我需要从用户 class 调用函数,将 db 对象传递给新用户 class 是否可以? ...做类似下面的事情?我不确定像我这样传递对象是否合适,或者是否有更好的设置方法?我是在浪费资源,还是这种做事方式笨拙?

资料模型

class Profile_Model extends Model{


  public function __construct() {
         parent::__construct();
    }

    public function someFunction(){

         $this->db->insert( "SOME SQL" );

         $user = new User( $this->db ); // OK TO PASS DB OBJECT LIKE THIS?
         $user->setSomething();

    }

    public function anotherFunction(){

        //do something else that does not need a user object

    }

}

用户Class

class User{

    public function __construct($db){
         $this->db = $db; // OK TO SET DB OBJECT AS CLASS VARIABLE AGAIN?
    }

    public function setSomething(){
         $this->db->insert( "SOME SQL" );
    }

}

每当您需要使用来自另一个 class 的对象时,只有一种安全的方法可以做到:依赖注入。

示例:

而不是:

public function myMethod(){
   $anotherObject = new Object();
}

您应该使用构造函数注入对象:

function __construct($dependency) {
   $this->anotherObject = $dependency;
}

一旦你有了这个结构,你就可以使用类型提示和控制反转容器来自动构建东西,例如定义:

function __construct(DependencyInterface $dependency) {
   $this->anotherObject = $dependency;
}

然后设置你的 IoC 容器在你需要使用这个对象时注入正确的依赖关系

您使用任何框架吗?如果没有,请尝试查看一些流行的,例如 Zend Framework 或 Symfony。您会发现它们可以解决您的问题,并且可能会解决更多问题,并且是扩展您的项目结构知识的好方法。

除此之外你很接近。虽然将数据库直接添加到您的用户模型可能不是您想做的。如果您可以获得 Martin Fowler 的企业应用程序架构模式 (PEAA),您会发现整整一章概述了如何将模型连接到数据库。当我自己构建一些东西时,我更喜欢 Gateway-class(搜索 Gateway-pattern 或查看 Zend_Db),因为它相对容易实现和构建。

基本上你有一个 class 执行查询,然后将数据传递给你的模型。只需查看 Martin Fowler 的模式目录 (http://martinfowler.com/eaaCatalog/) 中的数据源架构模式,即可快速了解如何构建它,一定要阅读本书才能真正了解何时以及如何使用这些模式。

希望对您有所帮助。

我想给你一个非常基本的例子来说明我是如何实现这个架构的;由于它非常基础,而且我只是一个充满热情的开发人员,仅此而已,我可能违反了一些架构规则,所以请将其作为概念证明。

让我们开始 快速处理您收到一些请求的控制器部分。现在你需要有人来处理脏活累活。

如您所见,我正在尝试通过构造函数传递所有 "dependencies"。通过这些方式,您应该能够在测试时轻松地将其替换为 Mocks。

Dependency injection is one of the concepts here.

现在模型(请记住模型是一层而不是单个class)

我使用了 "Services (or cases)",它应该可以帮助您与参与该行为的所有参与者 (类) 组成一组行为。

Idendifying common behaviours that Services (or Cases) should do, is one of the concepts here.

请记住,在开始之前,您应该有一个大局(或其他地方,具体取决于项目),以尊重 KISS, SOLID, DRY 等原则。

并且请注意方法命名,通常一个不好或很长的名字(比如我的名字)表明 class 有多个单一的责任或有不良设计的味道。

//App/Controllers/BlogController.php
namespace App\Controllers;

use App\Services\AuthServiceInterface;
use App\Services\BlogService;
use App\Http\Request;
use App\Http\Response;

class BlogController
{
    protected $blogService;

    public function __construct(AuthServiceInterface $authService, BlogService $blogService, Request $request)
    {
        $this->authService = $authService;
        $this->blogService = $blogService;
        $this->request = $request;
    }

    public function indexAction()
    {
        $data = array();

        if ($this->authService->isAuthenticatedUser($this->request->getSomethingRelatedToTheUser())) {
            $someData = $this->blogService->getSomeData();
            $someOtherData = $this->request->iDontKnowWhatToDo();
            $data = compact('someData', 'someOtherData');
        }

        return new Response($this->template, array('data' => $data), $status);
    }
}

现在我们需要创建我们在控制器中使用的服务。如您所见,我们没有直接与 "storage or data layer" 对话,而是调用一个抽象层来为我们处理。

Using a Repository Pattern to retrieve data from a data layer, is one of the concepts here.

这样我们就可以切换到任何存储库(inMemory、其他存储等)来检索我们的数据,而无需更改控制器正在使用的接口,相同的方法调用但从另一个地方获取数据。

Design by interfaces and not by concrete classes is one of the concepts here.

//App/Services/BlogService.php
<?php

namespace App\Services;

use App\Model\Repositories\BlogRepository;

class BlogService
{
    protected $blogRepository;

    public function __construct(BlogRepositoryInterface $blogRepository)
    {
        $this->blogRepository = $blogRepository;
    }

    public function getSomeData()
    {
        // do something complex with your data, here's just simple ex
        return $this->blogRepository->findOne();
    }
}

此时我们定义了包含持久性处理程序并了解我们的实体的存储库。

Again decoupling storage Persister and knowledge of an entity (what "can" be coupled with a mysql table for example), is one of the concepts here.

//App/Model/Repositories/BlogRepository.php

<?php

namespace App\Models\Respositories;

use App\Models\Entities\BlogEntity;
use App\Models\Persistance\DbStorageInterface;

class DbBlogRepository extends EntityRepository implements BlogRepositoryInterface
{
    protected $entity;

    public function __construct(DbStorageInterface $dbStorage)
    {
        $this->dbStorage = $dbStorage;
        $this->entity = new BlogEntity;
    }

    public function findOne()
    {
        $data = $this->dbStorage->select('*')->from($this->getEntityName());

        // This should be part of a mapping logic outside of here
        $this->entity->setPropA($data['some']);
        return $this->entity;
    }

    public function getEntityName()
    {
        return str_replace('Entity', '', get_class($this->entity));
    }
}

最后是一个带有 Setter 和 Getter 的简单实体:

//App/Model/Entities/BlogEntity.php
<?php

namespace App\Models\Entities;

class BlogEntity
{
    protected $propA;

    public function setPropA($dataA)
    {
        $this->propA = $dataA;
    }

    public function getPropA()
    {
        return $this->propA;
    }
}

现在? 你如何注入这个作为依赖项传递的 classes?好吧,这是一个很长的答案。 指示性地,您可以使用依赖注入,就像我们在这里所做的那样,有一个 init/boot 文件,您可以在其中定义如下内容:

// Laravel Style
App::bind('BlogRepositoryInterface', 'App\Model\Repositories\DbBlogRepository');
App::bind('DbStorageInterface', 'App\Model\Persistence\PDOStorage');

或一些 config/service.yml 文件,例如:

// Not the same but close to Symfony Style
BlogService:
     class: "Namespace\ConcreteBlogServiceClass" 

或者您可能觉得需要 Container Class 从那里您可以询问您需要在控制器中使用的服务。

function indexAction () 
{
    $blogService = $this->container->getService('BlogService'); 
    ....

这里有一些有用的链接(您可以找到大量关于此的文档):

部分答案是使用依赖注入,但不仅如此。从认知上讲,分组始于头脑,并通过头脑风暴和建模更好地梳理出来:实体关系图和 UML 图。

将方法分组到 类 并将任务委托给注入的对象是有意义的,但通常(至少)有一层继承空间。使用抽象超级 类 和子级 类 的策略模式从抽象父级继承基本功能有助于减少代码重复 (DRY)。

综上所述,这就是依赖注入容器流行的原因之一。它们允许您在任何地方获取所需的对象和功能,而无需将对象实例化与使用耦合。

在 Google 中搜索 Pimple。它可能会给你一些想法。