dongpu3898 2016-10-04 13:22 采纳率: 100%
浏览 42
已采纳

又一个php MVC设计

So I've read some already existing questions here (like this and this) and also I'm reading a book, but I still can't decide. Sorry for the long post, I've posted a decent amount of code here.

The routing, controller creating and action calling are working right now. Until this time I've just echo'd a simple text in the controller's action to see if it's working.

After this I've introduced a simple View class with a render() function which returns the "renderable" HTML code:

public function render()
{
    if (!isset($this->file)) {
        return "";
    }

    if (!empty($this->data)) {
        extract($this->data);
    }

    // dirty-hack: use output buffering so we can easily check if a file can be included
    // if not, simply reject the output and throw an exception (let the caller handle it)
    // if the inclusion was successfull then return with the buffered content
    ob_start();
    if (!include($this->file)) {
        ob_end_clean();
        throw new \Exception("View file \"{$this->file}\" not found");
    }

    return ob_get_clean();
}

The Controller class also have a render() and a protected setView() functions

public function render()
{
    if (isset($this->renderView)) {
        echo $this->renderView->render();
    }
}

protected function setView($view)
{
    if (!is_object($view) || !($view instanceof View)) {
        throw new \Exception("The \"view\" parameter have to be a View instance");
    }

    $this->renderView = $view;
}

An example controller with an example action:

public function viewHome()
{
    $contentView = new Framework\View("HomeView.php");

    // fill contentView with variables, using $contentView->setData("name", value)

    $this->generateView($contentView);
}

protected function generateView($contentView)
{
    if (!is_object($contentView) || !($contentView instanceof Framework\View)) {
        throw new \Exception("Invalid \"contentView\" parameter");
    }

    $layout = new Framework\View("MainLayout.php");
    $layout->setData("content", $contentView->render());

    $this->setView($layout); 
}

So actually the controller's action is assigning data to the view (Edit: actually it's not a view, more like a template, but it's my naming convention) and the view simply uses the data. This could be "reversed" so I could create View subclasses which would get a controller reference and use the controller to get/set the data to/from the presentation. For some reason I stick with the first (current) solution but I can change my mind. :)

The corresponding MainLayout.php test file is the following

<div style="margin-left: auto; margin-right: auto; padding: 5px; width: 800px; border: 3px solid green;">
    Main header <br />
    <br />
    Content: <br />
    <?php
    if (!isset($content)) {
        die("The \"content\" variable is not available");
    }
    echo $content;
    ?>
</div>

Note that the codes I've posted serve test purposes, things can be different later on. However my real problem comes with the model layer. I've read two different approach about the MVC pattern:

The first says that the model is a "stupid" layer, a simple memory/object representation of the data. The real work (so the DAO access, queries, business logic) is in the controller.

The other one says that the controllers are the "stupid" things, they are just a glue between the model and the view, the work is done by the model. The second one seems to be easier and it fits more to my current design. What do you think, which one is the preferred approach?

And another one: let's say I've choosen the 2nd approach (described above), so I have a model class for eg. a User. This class have different (static) functions for querying things, like "get all users", "add new user", and so on. However I don't want to connect the model with the Database this strong. My first idea is that I should create (at least) 3 classes for a single model:

  • an abstract class/interface which defines the "model" itself
  • at least one subclass (the implementation) for each data-access type
  • and a factory for the model which instantiates the proper class. If we need a mock model for testing, it would return a UserMock instance instead of a UserDB instance.

Do you have any better ideas?

Edit:

Based on the accepted answer, I've decided to use the service design. Here is a simple (sample) class:

abstract class UserService
{
    private static $instance;

    public static function setInstance($instance)
    {
        if (!is_object($instance) || !($instance instanceof UserService)) {
            throw new \Exception("Invalid \"instance\" parameter");
        }

        self::$instance = $instance;
    }

    public static function get()
    {
        if (!isset(self::$instance)) {
            throw new \Exception("Instance is not set");
        }

        return self::$instance;
    }

    public abstract function findAll();

    public abstract function findById($id);

    public function add($user)
    {
        if (!is_object($user) || !($user instanceof User)) {
            throw new \Exception("Invalid \"user\" parameter");
        }

        $id = $this->addAndReturnId($user);
        $user->setId($id);
    }

    protected abstract function addAndReturnId($user);
}

This way I can register a service implementation (which uses the database or just filled with test data). However for each service I should copy-paste the set/get code. I could use a base class for this but the "instanceof" check is different for each subclass.

I know this is a bit off-topic but do you have any good idea for this? Or should I copy-paste that two functions for each service?

  • 写回答

1条回答 默认 最新

  • dongzhan1492 2016-10-04 14:01
    关注

    Note: I'm working a lot with Symfony / Doctrine so my point of view is probably pretty influenced by that. But I think they are pretty well-designed libraries, so I hope this is not a problem.


    Why not use both approaches? Let controllers AND models be dumb. Model classes should only be concerned about holding the data. Basically just a class with some properties and accessor methods for these properties; nothing else. Also the controllers should not contain too much code and leave all the "interesting stuff" too other classes: Services.

    Symfonys documentation describes services as follows:

    Put simply, a service is any PHP object that performs some sort of "global" task. It's a purposefully-generic name used in computer science to describe an object that's created for a specific purpose (e.g. delivering emails). Each service is used throughout your application whenever you need the specific functionality it provides. You don't have to do anything special to make a service: simply write a PHP class with some code that accomplishes a specific task. Congratulations, you've just created a service!

    Now you could just create services like "UserRepository", "BlogRepository", "SomeModelRepository" and these services contain all the code to work with the corresponding models like loading all users, search users, storing users to the database and so on.

    A big advantage: If you also create corresponding interfaces (e.g. UserRepositoryInterface) you can just exchange these repository classes in future if you want to store your model in a different DBS or even in plain text files instead of your old database. You wouldn't need to change your models or controllers at all.

    Maybe you should take a look at how Doctrine handles these things.

    Also take a look at the Dependency Injection design pattern if you like the idea of services.


    Edit (05.10.2016)

    Your solution works, so if the following is too complicated for you right now, just stick with it. But it helped me, so I'll try to advertise it. :P

    Your UserService should only care about the actual user management methods and not about getting/setting instances of itself. Your approach is using a variation of the Singleton pattern and Singletons are not necessary a thing you really want to have (see here).

    If you want to work with "services" you probably should get into "Dependency Injection" (as mentioned above already). It is a bit hard to get into it in the beginning, but once you know how to use it, it improves your code quality a lot (at least it did for me). This introduction seems really good: PHP: The Right Way. Symfony also provides a DI component which you could use in your project to dont bother with the implementation details: Symfony Dependency Injection component

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 求一下解题思路,完全不懂。
  • ¥30 关于#硬件工程#的问题:求一下解题思路
  • ¥15 运筹学对偶单纯行法构造扩充问题
  • ¥20 XP系统的老电脑一开机就提示找不到rundll.exe,付费求解
  • ¥15 milvus查询出来的score怎么转换成0-1之间的相似性
  • ¥15 多ip服务器站群如何搭建l2tp服务器
  • ¥15 lvgl V9移植到linux开发板
  • ¥15 VB.net中在窗体中创建一个button控件来关闭窗体,但是提示错误,我该怎么办
  • ¥15 网上下载好的程序但是arduinoIDE编程报错,运行不了,哪里出错了,能具体给改一下吗
  • ¥15 Sharepoint JS开发 付费技术指导