2013-05-20 9 views
5

Zdaję sobie sprawę, że ten temat został wielokrotnie zadany i zaadresowany, i chociaż przeczytałem niezliczone podobne pytania i przeczytałem niezliczone artykuły, nadal nie potrafię zrozumieć kilku kluczowych problemów. Próbuję zbudować własne ramy MVC do celów edukacyjnych i lepiej zapoznać się z OOP. To jest do osobistego użytku prywatnego, aby nie sugerować, że jako pretekst do bycia leniwym, ale raczej nie jestem tak zatroskany o wszystkie dzwony i gwizdy bardziej solidnych ram.Zrozumienie/udoskonalanie szkieletowego szkieletu MVC

Moja struktura katalogów jest następująca:

public 
- index.php 
private 
- framework 
    - controllers 
    - models 
    - views 
    - FrontController.php 
    - ModelFactory.php 
    - Router.php 
    - View.php 
- bootstrap.php 

Mam plik .htaccess z przekieruje wszystkie żądania do index.php, plik ten zawiera podstawowe ustawienia konfiguracyjne, takie jak stałe strefę czasową, globalnych, a to wtedy ładuje plik bootstrap.php. Bootstrap zawiera mój autoloader dla klas, rozpoczyna sesję, definiuje globalne funkcje do wykorzystania w całym moim projekcie, a następnie wywołuje router. Router odbiera żądanie z adresu URL, sprawdza poprawność za pomocą klasy ReflectionClass i wykonuje żądanie w postaci example.com/controller/method/params.

Wszystkie moje kontrolerów rozszerzyć FrontController.php:

<?php 
namespace framework; 

class FrontController 
{ 
    public $model; 
    public $view; 
    public $data = []; 

    function __construct() 
    { 
     $this->model = new ModelFactory(); 
     $this->view = new View(); 
    } 

    // validate user input 
    public function validate() {} 

    // determines whether or not a form is being submitted 
    public function formSubmit() {} 

    // check $_SESSION for preserved input errors 
    public function formError() {} 
} 

Ten przedni regulator ładuje ModelFactory:

<?php 
namespace framework; 

class ModelFactory 
{ 
    private $db  = null; 
    private $host  = 'localhost'; 
    private $username = 'dev'; 
    private $password = '********'; 
    private $database = 'test'; 

    // connect to database 
    public function connect() {} 

    // instantiate a model with an optional database connection 
    public function build($model, $database = false) {} 
} 

i podstawa Widok:

<?php 
namespace framework; 

class View 
{ 
    public function load($view, array $data = []) 
    { 
     // calls sanitize method for output 
     // loads header, view, and footer 
    } 

    // sanitize output 
    public function sanitize($output) {} 

    // outputs a success message or list of errors 
    // returns an array of failed input fields 
    public function formStatus() {} 
} 

Wreszcie, tutaj jest przykładowy kontroler, aby pokazać, jak przetwarzane jest żądanie:

<?php 
namespace framework\controllers; 

use framework\FrontController, 
    framework\Router; 

class IndexController extends FrontController implements InterfaceController 
{ 
    public function contact() 
    { 
     // process form if submitted 
     if ($this->formSubmit()) { 
      // validate input 
      $name = isset($_POST['name']) && $this->validate($_POST['name'], 'raw') ? $_POST['name'] : null; 
      $email = isset($_POST['email']) && $this->validate($_POST['email'], 'email') ? $_POST['email'] : null; 
      $comments = isset($_POST['comments']) && $this->validate($_POST['comments'], 'raw') ? $_POST['comments'] : null; 

      // proceed if required fields were validated 
      if (isset($name, $email, $comments)) { 
       // send message 
       $mail = $this->model->build('mail'); 
       $to = WEBMASTER; 
       $from = $email; 
       $subject = $_SERVER['SERVER_NAME'] . ' - Contact Form'; 
       $body = $comments . '<br /><br />' . "\r\n\r\n"; 
       $body .= '-' . $name; 

       if ($mail->send($to, $from, $subject, $body)) { 
        // status update 
        $_SESSION['success'] = 'Your message was sent successfully.'; 
       } 
      } else { 
       // preserve input 
       $_SESSION['preserve'] = $_POST; 

       // highlight errors 
       if (!isset($name)) { 
        $_SESSION['failed']['name'] = 'Please enter your name.'; 
       } 
       if (!isset($email)) { 
        $_SESSION['failed']['email'] = 'Please enter a valid e-mail address.'; 
       } 
       if (!isset($comments)) { 
        $_SESSION['failed']['comments'] = 'Please enter your comments.'; 
       } 
      } 
      Router::redirect('contact'); 
     } 

     // check for preserved input 
     $this->data = $this->formError(); 

     $this->view->load('contact', $this->data); 
    } 
} 

Z tego, co jestem w stanie zrozumieć, moja logika jest wyłączony z następujących powodów:

  • Walidacja powinna odbywać się w modelu, a nie kontrolera. Jednak Model nie powinien mieć dostępu do zmiennych $ _POST, więc nie jestem całkowicie pewien, czy robię tę część poprawnie? Czuję, że to jest coś, co nazywają "grubym kontrolerem", co jest złe, ale nie jestem pewien, co trzeba zmienić ...
  • Kontroler nie powinien wysyłać danych do widoku; zamiast tego Widok powinien mieć dostęp do Modelu, aby zażądać własnych danych. Czy przeniesienie właściwości $data z FrontController i do ModelFactory, a następnie wywołanie widoku z kontrolera bez przekazywania danych, rozwiązuje ten problem? Technicznie byłoby to zgodne ze schematem blokowym MVC, ale proponowane rozwiązanie wydaje się być tak nieistotnym lub nawet banalnym szczegółem, zakładając, że jest to proste, co prawdopodobnie nie jest.
  • Częścią, która mnie kwestionuje całe moje wdrożenie jest Mam obiekt User, który jest tworzony z odpowiednimi rolami i uprawnieniami użytkowników, i próbowałem dowiedzieć się, w jaki sposób lub bardziej szczegółowo, gdzie utworzyć metodę isAllowed(), którą można wywołać zarówno z kontrolera, jak i widoku. Czy byłoby sensownym zastosowaniem tej metody w Modelu, skoro zarówno Kontroler, jak i Widok powinny mieć dostęp do Modelu?

Ogólnie rzecz biorąc, czy jestem na dobrej drodze, czy też, jakie poważne problemy muszę rozwiązać, aby znaleźć się na właściwej drodze? Naprawdę mam nadzieję na osobistą odpowiedź specyficzną dla moich przykładów, a nie "przeczytaj to". Doceniam wszelkie szczere opinie i pomoc.

+1

To pytanie wydaje się być nie na temat, ponieważ powinno być w http://codereview.stackexchange.com –

Odpowiedz

0

Walidacja powinna być przeprowadzana w modelu, a nie w kontrolerze. Jednak model nie powinien mieć dostępu do zmiennych $ _POST, więc nie jestem całkowicie pewny, czy robię tę część poprawnie? Czuję to co nazywają „regulator tłuszcz”, który jest zły, ale nie jestem pewien, co musi się zmienić ...

To prawda, model powinien wiedzieć nic o zamówienie, więc musisz przekazać $ _POST do modeli, ale nie będzie wiedział, że są to parametry żądania.

Jedna rzecz: walidacja, która nie ma nic wspólnego z logiką biznesową, powinna pozostać w kontrolerze. Załóżmy, że tworzysz token CSRF dla swoich formularzy ze względów bezpieczeństwa, ta walidacja powinna znajdować się wewnątrz kontrolera, ponieważ obsługuje żądania.

Kontroler nie powinien wysyłać danych do widoku; zamiast tego Widok powinien mieć dostęp do Modelu, aby zażądać własnych danych. Czy przeniesienie własności $ data z FrontController do ModelFactory, a następnie wywołanie widoku z kontrolera bez przekazywania danych rozwiązuje ten problem? Technicznie byłoby wówczas stosować się do schematu blokowego MVC, ale proponowane rozwiązanie wydaje się tak znikoma, a nawet trywialne szczegóły, zakładając, że to takie proste, które prawdopodobnie nie jest ..

to niekoniecznie prawda. To podejście nazywa się Aktywny model i zwykle używa się wzoru Observer, z modelami obserwowanymi przez widoki. Jeśli jakiś model się zmieni, powiadomi widok, który się zaktualizuje. Takie podejście jest bardziej odpowiednie dla aplikacji desktopowych, a nie internetowych. W aplikacjach internetowych najczęściej stosowanymi kontrolerami są pośrednicy między modelem a widokiem (Pasywny model). Nie ma właściwego podejścia, powinieneś wybrać ten, który najbardziej Ci się podoba.

część, która ma mnie wątpliwość całe realizacja jest, że mam obiektu użytkownika, który jest tworzony z użytkowników odpowiadających role i uprawnienia, a ja już próbuje dowiedzieć się, w jaki sposób lub bardziej konkretnie, gdzie utworzyć Metoda isAllowed(), którą można wywołać zarówno z kontrolera, jak iz widoku. Czy byłoby sensownym zastosowaniem tej metody w Modelu, skoro zarówno Kontroler, jak i Widok powinny mieć dostęp do Modelu?

Cóż, ten jeden nie ma mowy, będę musiał powiedzieć, aby przeczytać o ACL.

+0

kontrolerzy nie są odpowiedzialni za sprawdzanie poprawności ani za kontrolę dostępu. –

+0

Nie powiedziałem, że oni są odpowiedzialni za kontrolę dostępu, ale to w czyn jest odpowiedzialny za walidację, która ma do czynienia z samym żądaniem, ale nie z logiką. –

+0

@Dvvoter, komentarz umysłu? –

1
  • The $_POST superglobals powinny być wydobywane przez przykład żądanie, jak wyjaśniono w this post.

  • Sprawdzanie poprawności danych wejściowych nie jest obowiązkiem kontrolerów. Zamiast tego powinien być obsługiwany przez domain objects w warstwie modelu.

  • Fabryka modelu nie jest modelem.

  • Definiowanie widoczności parametru klasy jako public powoduje przerwanie enkapsulacji obiektu.

  • Nagłówek lokalizacji HTTP (przekierowanie) jest formą odpowiedzi. Tak więc powinna być obsługiwana przez instancję widoku.

  • W swojej obecnej formie kontrolery bezpośrednio manipulują superglobalami. Powoduje to ścisłe sprzężenie ze stanem globalnym.

  • Kontrola autoryzacji powinna być przeprowadzona outside controller. Nie w tym.

  • Twoja "fabryka modelu" powinna być fabryką serwisową, która jest wstrzykiwana zarówno w kontroler, jak i widok. Zapewniłoby to, że każda usługa jest tworzona tylko raz, a zatem pozwala kontrolerom pracować z tym samym stanem warstwy modelu.

+0

Zauważam, że zawsze jesteś jednym z pierwszych, którzy odpowiadają na pytania oznaczone "MVC", i nie mam wątpliwości, że rozumiesz ten schemat bardzo dobrze. Tak długo jak pracuję nad tym, naprawdę nie chciałbym zanurzyć się w dalszy bałagan przez zgadywanie. Nie sądzę, żebym mógł przekonać Cię do zaktualizowania przykładów podanych w moim pytaniu, aby pokazać mi, jak należy to zrobić? –

1

Po pierwsze, myślę, że to świetnie, że próbujesz stworzyć własne ramy. Wielu twierdzi, że każdy powinien to robić, choćby w celach edukacyjnych.

Po drugie, proponuję przeczytać ten Wikipedia article w sprawie ram. Wiele osób nie zdaje sobie sprawy, że istnieją różne wzorce routingu (wysyłania url, traversal) i widoków (push, pull).

Osobiście nie widzę potrzeby wyodrębniania super globali, ponieważ są one już abstrakcjami (przez php) z surowego wejścia (php: // input) i mogą być modyfikowane. Tylko moja opinia.

Masz rację, że walidacja powinna zostać przeprowadzona przez model. Nie zatwierdzasz formularzy, sprawdzasz poprawności danych. Jeśli chodzi o widok uzyskujący dostęp do danych, zależy to od wybranego wzoru. Możesz przekazać dane do widoku lub Widok może pobierać dane.

Jeśli jesteś ciekawy, moja próba na MVC basic framework jest na github. Jego 4 pliki, mniej linii 2K kodu (warstwa DB to 1K linii). Wdraża traversal (komponent) routingu i ściąga dane, istniało już wiele frameworków implementujących alternatywne wzorce.