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

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

报告相同问题?

悬赏问题

  • ¥15 求差集那个函数有问题,有无佬可以解决
  • ¥15 【提问】基于Invest的水源涵养
  • ¥20 微信网友居然可以通过vx号找到我绑的手机号
  • ¥15 寻一个支付宝扫码远程授权登录的软件助手app
  • ¥15 解riccati方程组
  • ¥15 display:none;样式在嵌套结构中的已设置了display样式的元素上不起作用?
  • ¥15 使用rabbitMQ 消息队列作为url源进行多线程爬取时,总有几个url没有处理的问题。
  • ¥15 Ubuntu在安装序列比对软件STAR时出现报错如何解决
  • ¥50 树莓派安卓APK系统签名
  • ¥65 汇编语言除法溢出问题