how to implement mvc in core php

前端 未结 8 2040
余生分开走
余生分开走 2020-12-29 00:52

how is mvc architecture used in php without any framework?

相关标签:
8条回答
  • 2020-12-29 01:33

    I think generally using one of the common frameworks is probably the way to go. The reason is that many good developers have spent a long time writing, bug-fixing, tweaking and polishing to create something solid for basing your site on. The best thing to do is to find one you like, learn it and stick with it (unless you find a reason not to). When I work with PHP, my choice is generally Zend Framework, but there are also CodeIgniter, Symfony, CakePHP and a bunch of others.

    If you still want to use the MVC pattern without an existing framework, you either have the choice of putting your own together or just logically separating each concern out from each other - this is the core tenet of MVC, the frameworks just help you achieve it.

    Rasmus Lerdorf wrote about his minimal approach to the MVC pattern in PHP in 2006. Might be worth a read. You may also be interested in a mini-framework such as F3::PHP (PHP 5.3+ only) - looks pretty promising.

    0 讨论(0)
  • 2020-12-29 01:36

    Updated 2020-02-11: Refactoring the answer to include a few best practices and being closer to PHP 7.4.

    Simplest PHP MVC approach

    Thousands words does not compete with a clean example, so here is a simple use case:

    Imagine you want to display a page describing a "car" (given a "car id") from an imaginary car vendor: http://example.com/car.php?id=42 (will be http://example.com/car/42 later).

    Very basically, you can structure your code with an hierarchy like:

    A configuration directory (this isn't part of the MVC architectural pattern):

    + config/
      - database.php
            <?php
            return new PDO(getenv("DB_DSN"), getenv("DB_USER"), getenv("DB_PASSWORD"));
    

    A folder for your document root (scripts acting like Controllers):

    + htdocs/
      - car.php
            <?php
            $carService = new CarService(require "config/database.php");
            $car = $carService->getById($_GET["id"]);
            require "car.php";
    

    A folder encapsulating your Model/business logic (hint: "Thin Controllers, Fat model"):

    + src/
      - CarService.php
            <?php
            class CarService {
                private PDO $database;
    
                public function __construct(PDO $database) {
                    $this->database = $database;
                }
    
                public function getById(int $id): CarEntity {
                    return $this->database->query(
                        "SELECT model, year, price " .
                        "FROM car " .
                        "WHERE id = $id"
                    )->fetch(PDO::FETCH_CLASS, CarEntity::class);
                }
            }
    

    A last folder containing all your Views(/templates):

    + views/
      - car.php
            <!DOCTYPE html>
            <html>
            <head>
                <title>Car - <?= htmlspecialchars($car->model) ?></title>
            </head>
            <body>
            <h1><?= htmlspecialchars($car->model) ?></h1>
            Year: <?= htmlspecialchars($car->year) ?>
            Price: <?= htmlspecialchars($car->price) ?>
            </body>
            </html>
    

    For the code above to work, you will need PHP to be configured with:

    include_path="/the/path/to/src:/the/path/to/views"
    

    To go further

    Nice URLs

    You might want nice URLs, if using Apache you can achieve this with:

    RewriteEngine On
    RewriteRule ^/car/(\d+)$ /car.php?id=$1 [L]
    

    This enables writing URLs like http://example.com/car/42 which will be internally converted to http://example.com/car.php?id=42

    Views as classes

    In the above solution, car.php is included from the global scope, that is why $car is directly available, but $carService too!

    One way to restrict what the templates may access is to transform it as a class:

    views/CarView.php:

    <?php
    class CarView {
        private CarEntity $car;
    
        public function __construct(CarEntity $car) {
            $this->car = $car;
        }
    
        public function __invoke(): void {
    ?>
    <!DOCTYPE html>
    <html>
        <head>
            <title>Car - <?= htmlspecialchars($this->car->model) ?></title>
        </head>
        <body>
            <h1><?= htmlspecialchars($this->car->model) ?></h1>
            Year: <?= htmlspecialchars($this->car->year) ?>
            Price: <?= htmlspecialchars($this->car->price) ?>
        </body>
    </html>
    <?php
        }
    }
    

    and then adapting the controller:

    htdocs/car.php:

    <?php
    $carService = new CarService(require "config/database.php");
    $view = new CarView($carService->getById($_GET["id"]));
    $view();
    

    Reusing views

    Using plain PHP files as templates, nothing prevents you from creating headers.php, footers.php, menu.php,... which you can reuse with include()/require() to avoid duplicated HTML.

    Using classes, re-usability can be obtained by combining them, for example, a LayoutView can be responsible for the global layout, and, in turn, calls another View component:

    <?php
    
    class LayoutView {
        protected string $lang;
    
        public function __construct(string $lang) {
            $this->lang = $lang;
        }
    
        // __invoke(): for embracing the "Single Responsibility" principle
        public function __invoke(View $view): void {
            ?>
    <!DOCTYPE html>
    <html lang="<?= $this->lang ?>">
    <head>
        <meta charset="utf-8" />
        <title><?= htmlentities($view->getTitle()) ?></title>
    </head>
    
    <body>
        <?php ($view)(); ?>
    </body>
    </html>
            <?php
        }
    }
    

    and CarView could be implemented like:

    views/CarView.php:

    <?php
    class CarView implements View {
        private CarEntity $car;
    
        public function __construct(CarEntity $car) {
            $this->car = $car;
        }
    
        public function getTitle(): string {
            return $this->car->model;
        }
    
        // __invoke(): for embracing the "Single Responsibility" principle
        public function __invoke(): void {
    ?>
    
    <h1><?= htmlspecialchars($this->car->model) ?></h1>
    Year: <?= htmlspecialchars($this->car->year) ?>
    Price: <?= htmlspecialchars($this->car->price) ?>
    <?php
        }
    }
    

    In turns, the controller would use it like this:

    htdocs/car.php:

    <?php
    $carService = new CarService(require "config/database.php");
    
    (new LayoutView("en"))(
        new CarView($carService->getById($_GET["id"]))
    );
    

    Conclusion

    This is far from being production-ready code, as other aspects aren't addressed by those examples: dependency injection/inversion of control (IoC), input filtering, class autoloading, namespaces,... The goal of this answer is to focus as much as possible on the main aspect of MVC.

    This is very much in the same spirit as Rasmus Lerdorf mentioned on: https://toys.lerdorf.com/the-no-framework-php-mvc-framework.

    One should not forget that MVC remains a pattern. Software Patterns are reusable principles to solve common problems, if they would be reusable code, they would have been named "libraries" instead.

    Frameworks like Zend Framework, Symfony, Laravel, CakePHP and the likes proposes a structure to adopt an MVC approach but can't enforce it, MVC, as a special case of Separation of concerns needs to be learned and understood to be achieved.

    0 讨论(0)
提交回复
热议问题