【问题标题】:How to properly structure and pass objects in a MVC structure in PHP如何在 PHP 中正确构造和传递 MVC 结构中的对象
【发布时间】:2015-03-06 15:09:28
【问题描述】:

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

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

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

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

个人资料模型

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

}

【问题讨论】:

    标签: php object model-view-controller


    【解决方案1】:

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

    让我们开始快速使用控制器部分,您会收到一些请求。现在你需要一个负责干脏活的人。

    正如您在此处看到的,我正在尝试通过构造函数传递所有“依赖项”。通过这些方式,您应该能够在测试时轻松地将其替换为 Mocks。

    依赖注入是这里的概念之一。

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

    我使用了“服务(或案例)”,它应该可以帮助您与参与此行为的所有参与者(类)组成一组行为。

    识别服务(或案例)应该做的常见行为是这里的概念之一。

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

    请注意方法命名,通常一个坏的或过长的名称(例如我的)表明该类具有多个责任或有不良设计的味道。

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

    现在我们需要创建我们在控制器中使用的这个服务。如您所见,我们并没有直接与“存储或数据层”对话,而是调用了一个抽象层来为我们处理这些问题。

    使用Repository Pattern 从数据层检索数据是这里的概念之一。

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

    通过接口而非具体类进行设计是这里的概念之一。

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

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

    再次解耦存储持久化和实体的知识(例如“可以”与mysql表耦合的东西)是这里的概念之一。

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

    现在?如何注入这些作为依赖项传递的类?嗯,这是一个很长的答案。 指示性地,您可以使用依赖注入,就像我们在这里所做的那样,有一个 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'); 
        ....
    

    fundo 中的 Dulcis 是一些有用的链接(您可以找到大量关于此的文档):

    【讨论】:

      【解决方案2】:

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

      例子:

      而不是:

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

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

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

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

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

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

      【讨论】:

      • 抱歉我的困惑,但这不是我在做什么吗?我正在使用模型中的 DB 对象注入 User 对象。
      【解决方案3】:

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

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

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

      我希望这会有所帮助。

      【讨论】:

        【解决方案4】:

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

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

        话虽如此,这也是依赖注入容器流行的原因之一。它们使您可以在任何地方获取所需的对象和功能,而无需将对象实例化与使用耦合。

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

        【讨论】:

          猜你喜欢
          • 2020-05-24
          • 2014-10-24
          • 2020-07-22
          • 1970-01-01
          • 2013-11-17
          • 2018-07-05
          • 2011-02-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多