Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to implement mvc in core php

Tags:

php

how is mvc architecture used in php without any framework?

like image 231
asha Avatar asked Dec 23 '09 09:12

asha


2 Answers

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.

like image 77
Patrick Allaert Avatar answered Sep 24 '22 02:09

Patrick Allaert


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.

like image 44
Mark Embling Avatar answered Sep 26 '22 02:09

Mark Embling