Пожалуйста, критикуйте мою первую попытку 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 дважды.
Это может быть просто из-за того, что это пример кода, отправленного на веб-сайт.