Пожалуйста, критикуйте мою первую попытку MVC в PHP

Ну, я не большой парень, но мне нравилось то, что я слышал обо всем движении MVC, поэтому я подумал, что попытаюсь создать простое приложение на моем языке выбора (PHP)

Итак, я думаю, вопрос: где я ошибся? Я знаю, что есть много споров о том, насколько толстый контроллер/модель должен быть так надежно, что мы можем избежать этого, однако мне особенно любопытно, как вы думаете о том, как я вписываюсь в datatier.

Также я купил домен, чтобы сделать некоторое тестирование, поэтому, если вы хотите увидеть его в действии, вы можете перейти на www.omgmvc.com

Во-первых, здесь моя схема базы данных:

CREATE TABLE `movies` (
  `id` int(11) NOT NULL auto_increment,
  `movie_name` varchar(255) NOT NULL,
  `release_date` date NOT NULL,
  `directors_name` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
);

INSERT INTO `movies` VALUES (1,'Star Wars', '1977-05-25', 'George Lucas');
INSERT INTO `movies` VALUES (2,'The Godfather', '1972-03-24', 'Francis Ford Coppola');
INSERT INTO `movies` VALUES (3,'The Dark Knight', '2008-07-18', 'Christopher Nolan');

И вот файлы:

index.php (контроллер)

<?php

include('datatier.php');
include('models/m_movie.php');

if (isset($_GET['movie']) && is_numeric($_GET['movie']))
{
    $movie = new Movie($_GET['movie']);

    if ($movie->id > 0)
    {
        include('views/v_movie.php');
    }
    else
    {
        echo 'Movie Not Found';
    }
}
else
{
    $movies = Movie::get_all();

    include('views/v_list.php');
}

?>

datatier.php (уровень данных)

<?php

class DataTier
{
    private $database;

    function __construct()
    {
        $this->connect();
    }

    function __destruct()
    {
        $this->disconnect();
    }

    function connect()
    {
        $this->database = new PDO('mysql:host=localhost;dbname=dbname','username','password');
    }

    function disconnect()
    {
        $this->database = null;
    }

    function get_all_from_database($type)
    {
        $database = new PDO('mysql:host=localhost;dbname=dbname','username','password');

        switch ($type)
        {
            case 'movie':
                $query = 'SELECT id FROM movies';
                break;
        }

        $movies = array();

        foreach ($database->query($query) as $results)
        {
            $movies[sizeof($movies)] = new Movie($results['id']);
        }

        $database = null;

        return $movies;
    }

    function get_from_database($type,$id)
    {
        switch ($type)
        {
            case 'movie':
                $query = 'SELECT movie_name,release_date,directors_name FROM movies WHERE id=?';
                break;
        }

        $database_call = $this->database->prepare($query);
        $database_call->execute(array($id));

        if ($database_call->rowCount() > 0)
        {
            return $database_call->fetch();
        }
        else
        {
            return array();
        }
    }
}

?>

models/m_movie.php (модель)

<?php

class Movie extends DataTier
{
    public $id;
    public $movie_name;
    public $release_date;
    public $directors_name;

    function __construct($id)
    {
        parent::connect();

        $results = parent::get_from_database('movie',$id);

        if ($results == array())
        {
            $this->id = 0;
        }
        else
        {
            $this->id = $id;
            $this->movie_name = $results['movie_name'];
            $this->release_date = $results['release_date'];
            $this->directors_name = $results['directors_name'];
        }
    }

    function __destruct()
    {
        parent::disconnect();
    }

    static function get_all()
    {
        $results = parent::get_all_from_database('movie');

        return $results;
    }
}

?>

views/v_list.php (просмотр)

<html>
    <head>
        <title>Movie List</title>
    </head>
    <body>
        <table border="1" cellpadding="5" cellspacing="5">
            <thead>
                <tr>
                    <th>Movie Name</th>
                    <th>Directors Name</th>
                    <th>Release Date</th>
                </tr>
            </thead>
            <tbody>
<?php foreach ($movies as $movie) { ?>
                <tr>
                    <td><a href="/?movie=<?php echo $movie->id; ?>"><?php echo $movie->movie_name; ?></a></td>
                    <td><?php echo $movie->directors_name; ?></td>
                    <td><?php echo $movie->release_date; ?></td>
                </tr>
<?php } ?>
            </tbody>
        </table>
    </body>
</html>

views/v_movie.php (просмотреть)

<html>
    <head>
        <title><?php echo $movie->movie_name; ?></title>
    </head>
    <body>
        <h1><?php echo $movie->movie_name; ?></h1>
        <h2>Directed by <?php echo $movie->directors_name; ?></h2>
        <h3>Released <?php echo $movie->release_date; ?></h3>
    </body>
</html>

Ответы

Ответ 1

Во-первых, вы отлично справляетесь с тем, чтобы вещи были разделены. Он окупается в будущем, поэтому не сдавайтесь.

Макет базы данных (или даже сама база данных) не имеет отношения к сути MVC. В большинстве случаев это реляционная база данных, однако MVC не требует ее явно (вы можете использовать хранилище XML или некоторую сетку/облако). Что важно для MVC, так это оставить модель отделенной от остальных, что вы и сделали.

Ваш взгляд также четко отделен от остальных. Подобно M-части MVC, Views может не только представлять HTML, но и любой текстовый вывод (XML, XML + XSL, RSS, обычный текст или даже сообщения электронной почты). Представления могут быть реализованы несколькими способами: PHP включает в себя как ваш, шаблоны (т.е. Smarty) или полноценные объекты, сериализуемые для текста. Я далеко не сужу, какая стратегия лучшая, это вопрос индивидуального стиля кодирования и требований к проекту.

Ваш контроллер запутан (он больше Контроллер страницы, чем Контроллер приложения). Вероятно, это связано с тем, что в MVC-архитектуре есть одна скрытая часть. Он называется Front Controller или Dispatcher. Диспетчер должен анализировать входные данные, запускать контроллер (как в Application Controller) и вызывать запрошенный метод. Если вы хотите продолжить реализацию своей пользовательской реализации MVC, я предлагаю вам использовать обычный способ передачи имени и имени контроллера в URL-адрес, т.е.

index.php/Movies/list
index.php/Movies/details/35

Затем в новом index.php вы просто разбираете $_SERVER ['PATH_INFO'], создаете экземпляр класса Movies и вызываете его метод list, т.е.

$args = explode('/', ltrim($_SERVER['PATH_INFO'], '/'));
$className = array_shift($args);
$method = array_shift($args);
require "$className.php";
call_user_func_array(array(new $className(), $method), $args);

Затем вы просто перемещаете содержимое блока if-else в два отдельных метода в классе Movies.

class Movies { // may extend generic Controller class if you wish

    public function list() {
        $movies = Movie::get_all();
        include 'views/v_list.php';
    }

    public function details($movieId) {
        $movie = new Movie($movieId);
        if ($movie->id > 0) {
            include 'views/v_movie.php';
        } else {
            echo "Movie Not Found";
    }

}

Таким образом, вы можете иметь несколько контроллеров, каждый из которых имеет несколько действий.

Заключительные замечания.

  • На стороне базы данных было бы удобно использовать одну из существующих инфраструктур ORM. Они сэкономят вам дни работы и, вероятно, будут работать лучше, чем ручной дБ. Я бы также предложил обработать экземпляр экземпляра PDO, поскольку экземпляр PDO в каждом объекте модели не является самым чистым способом. Что-то вроде DBFactory::getConnection.

  • Вы можете подумать о возврате HTML, а не о повторении его в контроллере. Это дает вам гибкость, если вы хотите реализовать Intercepting Filters, который будет обертывать контроллер, перехватывать его вывод и предварительно или обрабатывать его. Очень удобно иметь фильтр, который автоматически присоединяет HTML-заголовок и нижний колонтитул.

  • Создание пользовательских фреймворков - большой интересный и ценный образовательный опыт, однако я бы предложил использовать одну из существующих фреймворков для более серьезных задач.

Все самое лучшее.

Ответ 2

Вы делаете хорошо, но у меня есть несколько советов:

  • Поскольку вы используете php5, не забывайте о функции __autoload.
  • Лучше назвать свой уровень данных как Модель.
  • get_all_from_database не объявляется как статический, но вы вызываете его статически, это генерирует предупреждение уровня E_STRICT. Установите error_reporting (E_ALL | E_STRICT); и вы должны увидеть предупреждение.
  • get_all() статическая функция должна быть в классе модели (в вашем случае Datatier), так что вам не придется переписывать ее для каждой другой модели. Единственное изменение, которое вам нужно сделать в этой функции, - это заменить строку:

    $results = parent:: get_all_from_database ('movie');

с

$results = $this->get_all_from_database(get_class($this));

это предполагает, что имя ваших моделей должно соответствовать именам ваших таблиц в базе данных

Ответ 3

Единственное, что сразу скачет ко мне (что еще не упоминалось) как "странное", заключается в том, что вы используете два экземпляра PDO для разговора с одной и той же базой данных. Не так уж плохо, но вы также сохраняете имя пользователя, пароль и остальную часть dsn дважды.

Это может быть просто из-за того, что это пример кода, отправленного на веб-сайт.