首页 > 解决方案 > mvc网站中的静态和非静态方法

问题描述

目前我有一个问题,不允许我继续向我的 mvc 网站添加功能而不做任何类型的意大利面条代码。

我有两个类,一个是 ModModel,另一个是 ModUploadModel。两者都使用 Model 类进行扩展。ModModel 包含有关“mods”的所有方法,如 ModModel->doesModNameExists()、ModModel->getModDetails() 等... ModUploadModel 包含所有上传 mod 的方法,如 ModUploadModel->upload()、ModUploadModel- >isModNameValid() 等...

在某些情况下,我必须从 ModUploadModel 调用一些 ModModel 方法,为此我必须在 ModUploadController 中创建一个新的 ModModel 实例,并将其作为参数传递给 ModUploadModel->upload()。例如:ModUploadController 创建两个新对象,$modModel = new ModModel() 和 $modUploadModel = new ModUploadModel(),然后调用 $modUploadModel->upload($modModel)。

这是 ModUploadController,它创建两个对象并调用 ModUploadModel->upload() 方法

class ModUploadController extends Mvc\Controller {

    public function uploadMod(): void {
        $modUploadModel = new ModUploadModel()
        $modModel = new ModModel();

        // $modModel needs to be passed because the ModUploadModel needs
        // one of its methods
        if ($modUploadModel->upload("beatiful-mod", $modModel)) {
            // success
        } else {
            // failure
        }
    }
}

ModUploadModel->upload() 检查输入是否有效(如果尚未使用模组名称等),最后将模组数据上传到数据库中。显然,这一切都是在更多的子私有方法中出现的,如 ModUploadModel->isModNameValid() 和 ModUploadModel->insertIntoDb()。

问题是我没有使用所有静态方法来构建我的类,并且每次我必须将对象作为参数传递,就像 ModModel 一样(例如我需要它的 isModNameValid() 方法)。我想过让所有的 ModModel 方法都是静态的,但这并不像看起来那么简单,因为它的所有方法都查询数据库,并且它们使用 Model->executeStmt() 方法(请记住,所有 FooBarModel 类都使用 Model 扩展类,其中包含有用的常用方法,如 executeStmt() 和其他),并且从静态方法调用非静态方法在 php 中不是一个好习惯,所以我也应该将模型方法设为静态,因此也应该将 Dbh 方法用于db 连接(模型用 Dbh 扩展)。

ModModel 类:

class ModModel extends Mvc\Model {

    // in reality it queries the db with $this->executeStmt(),
    // which is a Model method
    public function doesModNameExists($name) {
        if (/* exists */) {
            return true;
        }

        return false;
    }

}

ModUploadModel 类:

class ModUploadModel extends Mvc\Model {

    private $modName;

    public function upload($modName, $modModel) {
        $this->modName = $modName;

        if (!$this->isModNameValid($modModel)) {
            return false;
        }
    
        if ($this->insertIntoDb()) {
            return true;
        }
    
        return false;
    }
    
    // this methods needs to use the non static doesModNameExists() method
    // which is owned by the ModModel class, so i need to pass
    // the object as an argument
    private function isModNameValid($modModel) {
        if ($modModel->doesModNameExists($this->modName)) {
            return false;
        }
        
        // other if statements

        return true;
    }
    
    private function insertIntoDb() {
        $sql = "INSERT INTO blabla (x, y) VALUES (?, ?)";
        $params = [$this->modName, "xxx"];
    
        if ($this->executeStmt($sql, $params)) {
            return true;
        }
    
        return false;
    }
}

另一种方法是在 ModModel 方法中创建一个新的 Model 实例,例如 (new Model)->executeStmt()。问题是创建新对象不是模范工作,通常也不是我最喜欢的解决方案。

标签: phpmodel-view-controllermethodsstaticstructure

解决方案


一些观察和建议:

[a]您正在传递一个ModModel对象以ModUploadModel在上传前验证模组名称。ModUploadModel::upload()如果具有提供的名称的模块已经存在,您甚至不应该尝试调用。因此,您应该遵循与此类似的步骤:

class ModUploadController extends Mvc\Controller {

    public function uploadMod(): void {
        $modUploadModel = new ModUploadModel()
        $modModel = new ModModel();

        $modName = 'beatiful-mod';

        try {
            if  ($modModel->doesModNameExists($modName)) {
                throw new \ModNameExistsException('A mod with the name "' . $modName . '" already exists');
            }

            $modUploadModel->upload($modName);
        } catch (\ModNameExistsException $exception){
            // ...Present the exception message to the user. Use $exception->getMessage() to get it...
        }
    }
}

[b]在类中创建对象是个坏主意(例如在 中ModUploadController)。改用依赖注入。阅读内容并观看内容和此内容。所以解决方案看起来像这样:

class ModUploadController extends Mvc\Controller {

    public function uploadMod(ModUploadModel $modUploadModel, ModModel $modModel): void {
        //... Use the injected objects ($modUploadModel and $modModel ) ...
    }
}

在一个项目中,所有需要注入其他对象的对象都可以通过一个“依赖注入容器”来创建。例如,PHP-DI(我推荐)或其他 DI 容器。因此,DI 容器负责您项目的所有依赖注入。例如,在您的情况下,注入ModUploadController::uploadMod方法的两个对象将由 PHP-DI 自动创建。您只需要在用作应用程序入口点的文件中编写三行代码,可能index.php

use DI\ContainerBuilder;

$containerBuilder = new ContainerBuilder();
$containerBuilder->useAutowiring(true);
$container = $containerBuilder->build();

当然,DI 容器也需要配置步骤。但是,在几个小时内,您就可以了解如何以及在何处进行操作。

通过使用 DI 容器,您将能够专注于项目的逻辑,而不是应该如何以及在何处创建各种组件或类似任务。

[c]使用静态方法是个坏主意。我的建议是摆脱您已经编写的所有静态方法。看这个,读这个这个这个。因此,您遇到的注入问题的解决方案是上述问题:DI,由 DI 容器执行。根本不创建静态方法。

[d]您正在使用这两个组件来查询数据库(ModModelwithdoesModNameExists()ModUploadModelwith insertIntoDb())。您应该只使用一个组件来处理数据库。

[e]你根本不需要Mvc\Model

[f]你根本不需要Mvc\Controller

一些代码:

我写了一些代码,作为你的替代品(我以某种方式“推断”了任务)。也许它会帮助你,看看别人是如何编码的。它可以让你“在不做任何意大利面条代码的情况下向我的 mvc 网站添加功能”。该代码与我不久前写的答案中的代码非常相似。该答案还包含其他重要的建议和资源。

重要提示:请注意,应用程序服务,例如来自 的所有组件Mvc/App/Service/,应与域模型组件通信,例如与来自Mvc/Domain/Model/(主要是接口)的组件,而不是来自的组件Mvc/Domain/Infrastructure/。反过来,您选择的 DI 容器将负责为应用程序服务使用Mvc/Domain/Infrastructure/的接口注入适当的类实现。Mvc/Domain/Model/

注意:我的代码使用 PHP 8.0。祝你好运。

项目结构:

项目结构

Mvc/App/Controller/Mod/AddMod.php:

<?php

namespace Mvc\App\Controller\Mod;

use Psr\Http\Message\{
    ResponseInterface,
    ServerRequestInterface,
};
use Mvc\App\Service\Mod\{
    AddMod As AddModService,
    Exception\ModAlreadyExists,
};
use Mvc\App\View\Mod\AddMod as AddModView;

class AddMod {

    /**
     * @param AddModView $addModView A view for presenting the response to the request back to the user.
     * @param AddModService $addModService An application service for adding a mod to the model layer.
     */
    public function __construct(
        private AddModView $addModView,
        private AddModService $addModService,
    ) {
        
    }

    /**
     * Add a mod.
     * 
     * The mod details are submitted from a form, using the HTTP method "POST".
     * 
     * @param ServerRequestInterface $request A server request.
     * @return ResponseInterface The response to the current request.
     */
    public function addMod(ServerRequestInterface $request): ResponseInterface {
        // Read the values submitted by the user.
        $name = $request->getParsedBody()['name'];
        $description = $request->getParsedBody()['description'];

        // Add the mod.
        try {
            $mod = $this->addModService->addMod($name, $description);
            $this->addModView->setMod($mod);
        } catch (ModAlreadyExists $exception) {
            $this->addModView->setErrorMessage(
                $exception->getMessage()
            );
        }

        // Present the results to the user.
        $response = $this->addModView->addMod();

        return $response;
    }

}

Mvc/App/Service/Mod/Exception/ModAlreadyExists.php:

<?php

namespace Mvc\App\Service\Mod\Exception;

/**
 * An exception thrown if a mod already exists.
 */
class ModAlreadyExists extends \OverflowException {
    
}

Mvc/App/Service/Mod/AddMod.php:

<?php

namespace Mvc\App\Service\Mod;

use Mvc\Domain\Model\Mod\{
    Mod,
    ModMapper,
};
use Mvc\App\Service\Mod\Exception\ModAlreadyExists;

/**
 * An application service for adding a mod.
 */
class AddMod {

    /**
     * @param ModMapper $modMapper A data mapper for transfering mods 
     * to and from a persistence system.
     */
    public function __construct(
        private ModMapper $modMapper
    ) {
        
    }

    /**
     * Add a mod.
     * 
     * @param string|null $name A mod name.
     * @param string|null $description A mod description.
     * @return Mod The added mod.
     */
    public function addMod(?string $name, ?string $description): Mod {
        $mod = $this->createMod($name, $description);

        return $this->storeMod($mod);
    }

    /**
     * Create a mod.
     * 
     * @param string|null $name A mod name.
     * @param string|null $description A mod description.
     * @return Mod The newly created mod.
     */
    private function createMod(?string $name, ?string $description): Mod {
        return new Mod($name, $description);
    }

    /**
     * Store a mod.
     * 
     * @param Mod $mod A mod.
     * @return Mod The stored mod.
     * @throws ModAlreadyExists The mod already exists.
     */
    private function storeMod(Mod $mod): Mod {
        if ($this->modMapper->modExists($mod)) {
            throw new ModAlreadyExists(
                    'A mod with the name "' . $mod->getName() . '" already exists'
            );
        }

        return $this->modMapper->saveMod($mod);
    }

}

Mvc/App/View/Mod/AddMod.php:

<?php

namespace Mvc\App\View\Mod;

use Mvc\{
    App\View\View,
    Domain\Model\Mod\Mod,
};
use Psr\Http\Message\ResponseInterface;

/**
 * A view for adding a mod.
 */
class AddMod extends View {

    /** @var Mod A mod. */
    private Mod $mod = null;

    /**
     * Add a mod.
     * 
     * @return ResponseInterface The response to the current request.
     */
    public function addMod(): ResponseInterface {
        $bodyContent = $this->templateRenderer->render('@Templates/Mod/AddMod.html.twig', [
            'activeNavItem' => 'AddMod',
            'mod' => $this->mod,
            'error' => $this->errorMessage,
        ]);

        $response = $this->responseFactory->createResponse();
        $response->getBody()->write($bodyContent);

        return $response;
    }

    /**
     * Set the mod.
     * 
     * @param Mod $mod A mod.
     * @return static
     */
    public function setMod(Mod $mod): static {
        $this->mod = $mod;
        return $this;
    }

}

Mvc/App/View/View.php:

<?php

namespace Mvc\App\View;

use Psr\Http\Message\ResponseFactoryInterface;
use SampleLib\Template\Renderer\TemplateRendererInterface;

/**
 * A view.
 */
abstract class View {

    /** @var string An error message */
    protected string $errorMessage = '';

    /**
     * @param ResponseFactoryInterface $responseFactory A response factory.
     * @param TemplateRendererInterface $templateRenderer A template renderer.
     */
    public function __construct(
        protected ResponseFactoryInterface $responseFactory,
        protected TemplateRendererInterface $templateRenderer
    ) {
        
    }

    /**
     * Set the error message.
     * 
     * @param string $errorMessage An error message.
     * @return static
     */
    public function setErrorMessage(string $errorMessage): static {
        $this->errorMessage = $errorMessage;
        return $this;
    }

}

Mvc/域/基础设施/Mod/PdoModMapper.php:

<?php

namespace Mvc\Domain\Infrastructure\Mod;

use Mvc\Domain\Model\Mod\{
    Mod,
    ModMapper,
};
use PDO;

/**
 * A data mapper for transfering Mod entities to and from a database.
 * 
 * This class uses a PDO instance as database connection.
 */
class PdoModMapper implements ModMapper {

    /**
     * @param PDO $connection Database connection.
     */
    public function __construct(
        private PDO $connection
    ) {
        
    }

    /**
     * @inheritDoc
     */
    public function modExists(Mod $mod): bool {
        $sql = 'SELECT COUNT(*) as cnt FROM mods WHERE name = :name';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':name' => $mod->getName(),
        ]);

        $data = $statement->fetch(PDO::FETCH_ASSOC);

        return ($data['cnt'] > 0) ? true : false;
    }

    /**
     * @inheritDoc
     */
    public function saveMod(Mod $mod): Mod {
        if (isset($mod->getId())) {
            return $this->updateMod($mod);
        }
        return $this->insertMod($mod);
    }

    /**
     * Update a mod.
     * 
     * @param Mod $mod A mod.
     * @return Mod The mod.
     */
    private function updateMod(Mod $mod): Mod {
        $sql = 'UPDATE mods 
                SET 
                    name = :name,
                    description = :description 
                WHERE 
                    id = :id';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':name' => $mod->getName(),
            ':description' => $mod->getDescription(),
        ]);

        return $mod;
    }

    /**
     * Insert a mod.
     * 
     * @param Mod $mod A mod.
     * @return Mod The newly inserted mod.
     */
    private function insertMod(Mod $mod): Mod {
        $sql = 'INSERT INTO mods (
                    name,
                    description
                ) VALUES (
                    :name,
                    :description
                )';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':name' => $mod->getName(),
            ':description' => $mod->getDescription(),
        ]);

        $mod->setId(
            $this->connection->lastInsertId()
        );

        return $mod;
    }

}

Mvc/域/模型/Mod/Mod.php:

<?php

namespace Mvc\Domain\Model\Mod;

/**
 * Mod entity.
 */
class Mod {

    /**
     * @param string|null $name (optional) A name.
     * @param string|null $description (optional) A description.
     */
    public function __construct(
        private ?string $name = null,
        private ?string $description = null
    ) {
        
    }

    /**
     * Get id.
     * 
     * @return int|null
     */
    public function getId(): ?int {
        return $this->id;
    }

    /**
     * Set id.
     * 
     * @param int|null $id An id.
     * @return static
     */
    public function setId(?int $id): static {
        $this->id = $id;
        return $this;
    }

    /**
     * Get the name.
     * 
     * @return string|null
     */
    public function getName(): ?string {
        return $this->name;
    }

    /**
     * Set the name.
     * 
     * @param string|null $name A name.
     * @return static
     */
    public function setName(?string $name): static {
        $this->name = $name;
        return $this;
    }

    /**
     * Get the description.
     * 
     * @return string|null
     */
    public function getDescription(): ?string {
        return $this->description;
    }

    /**
     * Set the description.
     * 
     * @param string|null $description A description.
     * @return static
     */
    public function setDescription(?string $description): static {
        $this->description = $description;
        return $this;
    }

}

Mvc/域/模型/Mod/ModMapper.php:

<?php

namespace Mvc\Domain\Model\Mod;

use Mvc\Domain\Model\Mod\Mod;

/**
 * An interface for various data mappers used to 
 * transfer Mod entities to and from a persistence system.
 */
interface ModMapper {

    /**
     * Check if a mod exists.
     * 
     * @param Mod $mod A mod.
     * @return bool True if the mod exists, false otherwise.
     */
    public function modExists(Mod $mod): bool;

    /**
     * Save a mod.
     * 
     * @param Mod $mod A mod.
     * @return Mod The saved mod.
     */
    public function saveMod(Mod $mod): Mod;
}

推荐阅读