Czy dobrze rozumiem wzorzec MVC?

Odpowiedz Nowy wątek
2018-03-20 20:38
1

Tak jak w temacie. Chciałbym się tego dowiedzieć. Przygotowałem wstępny kod aby zobrazować tok rozumowania. Proszę o ocenę i jeżeli pojawią się jakieś uwagi to było by fajnie gdyby pojawiło się również uzasadnienie. Zakładamy, że jest włączone przepisywanie adresów url w pliku .htaccess i wszystko trafia do router'a. Zakładamy, że jest to sklep odzieżowy i np pojawia się taki adres url w pasku /szukaj/kalesony/.

 
 <?php
 
function autoloadClass($ClassName)  //wiadomo, automatycznie ładujemy klasy
{
  if(file_exists("php/model/$ClassName.php"))
  {
    require "php/model/$ClassName.php";
  }
  else if(file_exists("php/view/$ClassName.php"))
  {
    require "php/view/$ClassName.php";  
  }
  else if(file_exists("php/controler/$ClassName.php"))
  {
      require "php/controler/$ClassName.php";
  }
}
 
 spl_autoload_register('autoloadClass');
 
 class Router
 {
     public $ModelName;
     public $ViewName;
     public $ControlerName;
     public $Parameter;
 
    function __construct()  //tutaj parsujemy adres url i wyodrębniamy akcję
    {
      //jakieś działania na ciągach znaków
      $this->ModelName = $JakasZmiennaPrzechowujacaNazwe;
      $this->ViewName = $JakasZmiennaPrzechowujacaNazwe;
      $this->ControlerName = $JakasZmiennaPrzechowujacaNazwe;
      $this->Parameter = $TuPewnieJakasTablica
    }   
 }
 
 class SearchByItemModel //klasa modelu
 {
    private $Item;
 
    public function SetItem($Item)
    {
      $this->Item = $Item;
    }
 
    public function FindItemInDataBase()
    {
      //łączenie z bazą danych, pobieranie danych itp cała logika tu pracuje
      return $JakasZmiennaZapewneTablica;
    }
 
 }
 
 class SearchByItemView
 {
   private $ItemFinder;
 
   function __construct($ItemFinder)
   {
     $this->ItemFinder = $ItemFinder;
   }
 
   function Render()
   {
     //tutaj wyświetlane jest wszystko co potrzebne, tutaj jest bezpośredni dostęp do modelu
   }   
 }
 
 class SearchByItemControler
 {
    private $Model;
 
    function __construct($Model)
    {
      $this->Model = $Model;
    }
 
    function Search($Item)
    {
     $this->Model->SetItem($Item);
    }   
 }
 
 //tworzymy instancje naszych obiektów;
 
 $router = new Router();
 $model = new $router->ModelName();
 $controler = new $router->ControlerName($model);
 $parameter = $router->Parameter;
 
 $controler->Search($Parameter);
 $view = new SearchByItemView($model);
 $view->render();
 
?>
 

Pozostało 580 znaków

2018-03-20 21:10
1

Zanim zabierzesz się za pisanie własnego MVC od zera, rzuć okiem np. na Laravela i to, jak jego twórcy podeszli do tematu - da Ci to dobre rozeznanie w kwestii podziału oraz nazewnictwa w PHP (którego w obecnej formie nie przestrzegasz).


edytowany 1x, ostatnio: Patryk27, 2018-03-20 21:10
Ale co konkretnie masz na myśli? - Mostek87 2018-03-20 21:25

Pozostało 580 znaków

2018-03-21 00:46
0

To co sobie wymyslił twórca Laravela nie jest żadnych wyznacznikiem czy standardem.

Bardziej niż Laravelem zajmij się przestrzeganiem wzorców projektowych takich jak SOLID czy DDD. To co masz wyżej to pomieszanie MVC z DDD. Poczytaj o tym.

Jeżeli już obrałeś jakieś nazewnictwo czy strukturę która Ci pasuje, najważniejsze to ściśle się jej trzymać - to powoduje szereg ułatwień od automatyzacji refaktoringu po generalny porządek w kodzie.


edytowany 3x, ostatnio: TomRZ, 2018-03-21 01:24
Pokaż pozostałe 13 komentarzy
Wstukaj DDD w Google - wszystkie odnalezione przeze mnie definicje mówią podejście do tworzenia oprogramowania, a żadna nie używa słowa wzorzec. - Patryk27 2018-03-21 21:19
To teraz wstukaj w google "wzorzec" i zobacz jaka jest definicja, DDD praktycznie jest wzorcem tylko bardzo rozszerzonym. Ok ja nie mam zamiaru tracić więcej czasu bo ta dyskusja nigdzie nia prowadzi. Nara. - TomRZ 2018-03-21 21:43
słowo, które może oznaczać wzór jednostki miary, wzór rzeczy, wyrobu albo zalecany wygląd lub zachowanie się osoby (wzór do naśladowania) lub zwierzęcia. No wstukałem. Co to ma wspólnego wzór do wytwarzania oprogramowania? czy w DDD wzorujesz się na czymś? Faktycznie idź lepiej stąd bo się kompromitujesz. - Pabloss 2018-03-21 21:51
myślę, że jeśli chodzi o poglądy, to każdy ma prawo mieć swoje (ja bym nie nazwał DDD wzorcem, raczej pewną filozofią, która korzysta z wzorców jak narzędzi). Z drugiej strony jeśli ktoś się kłóci z kimś, że on ma rację i "moja racja jest najmojsza", to wypadałoby podać bardziej merytoryczne argumenty niż "co za herezje", "nie wierz we wszytko co pisze na wikipedii" albo naginanie logiki do swoich celów (najpierw przykłady o bułkach, a potem "to co piszę wynika wprost z logiki". No nie). - LukeJL 2018-03-21 22:32
z drugiej strony dla mnie bardzo dużo elementów takich bardzo podstawowych jest wzorcem. Nawet jak ktoś pisze klasę, to w pewnym sensie stosuje wzorzec projektowy pod tytułem klasa (może dlatego, że programuję w JS, w którym przez jakiś czas w ogóle nie było klas, więc łatwo mi postrzegać klasy jako design pattern niż jakbym miał programować w języku klasowym, w którym klasy są jak powietrze). - LukeJL 2018-03-21 22:36

Pozostało 580 znaków

2018-03-21 07:11
3

@Mostek87:

  1. Nie pisz własnego autoloadera - wykorzystaj Composera (Composer ma dwie funkcjonalności: zarządzanie paczkami oraz budowanie autoloadera - Tobie potrzebna jest głównie ta druga).

  2. protected $foo; (a nie protected $Foo;; to samo odnośnie private czy public - widoczność określiłem tylko na rzecz przykładu; taka jest konwencja języka).

  3. function doSomething(), a nie function DoSomething() (taka jest konwencja języka).

  4. Nie modelName, tylko modelClassName itd. (niby niewielka różnica, ale widząc modelName możesz zacząć się zastanawiać, czy znajduje się tam ścieżka do pliku, czy może coś innego - a modelClassName jest dosyć jednoznaczne).

Ogólnie nie ma źle - rzucam się głównie o stylistykę i nazewnictwo, ale są to kwestie dosyć istotne ;-)

Sam koncept wydaje mi się, że rozumiesz, chociaż mógłbyś podać jakiś bardziej wymagający przykład (np. ogólny zarys kodu zarządzającego postami w bazie danych bloga).


edytowany 2x, ostatnio: Patryk27, 2018-03-21 07:12

Pozostało 580 znaków

2018-03-21 12:50
1

To co wyżej napisał Patryk to znowu nie jest żaden standard ani wyznacznik.

Możesz robić tak jak robisz "kamelizująć" już od pierwszej litery - tyle, że bądź w tym konsekwentny.

Jeżeli chcesz to równie dobrze Twoje zmienne i fukcje mogą wyglądać tak:

public function do_something, protected $model_name etc - to jest trochę starszy ale nadal bardzo dobry sposób nazewnictwa, który część ludzi preferuje ponad kamelizację.


edytowany 2x, ostatnio: TomRZ, 2018-03-21 12:51
Pokaż pozostałe 2 komentarze
PSRy są w mniejszej lub większej części uznane wśród większości społeczności i praktycznie każdym frameworku - nawet jeśli nie są standardem wprost, są de facto standardem i trzymanie się ich przyniesie z całą pewnością więcej pożytku niż zła. - Patryk27 2018-03-21 14:23
Co do nazewnictwa jestem w skłonny się zgodzić, podobnie sposobu ładowania klas, co do pozostałych rzeczy które rekomenduje PSR już tak nie jest. Natomiast to trochę sztuczny problem, jak już wspominałem - najbardziej w nazewnictwie liczy się konsekwencja a nie sposób, używanie konstrukcji nazwa_zmiennej nie stanowi problemu jeżeli wszędzie ktoś tak robi w swoim kawałku kodu / bibliotece. - TomRZ 2018-03-21 15:31
Tak - a potem łączysz kilka bibliotek czyjegoś autorstwa i piszesz $bibliotekaA->something_do($bibliotekaB->makeOBJECT(true))->then_and($bibliotekaC->fetch_____object(false));, bo każdy postanowił robić po swojemu (ale grunt, że konsekwentnie w obrębie własnego kodu!). Nazewnictwo jest istotne - nie tylko trzymanie się jednolitego standardu, ale również pisanie tak, jak reszta. Nie bez powodu niektóre języki wręcz narzucają standardy (np. Go). - Patryk27 2018-03-21 16:22
Tyle, że PHP nie jest takim językiem, to nie Java. Twój przykład jest jakimś absurdem, w dobie zaawansowanych IDE nazewnictwo to najmniejeszy problem. Kiepski algorytm nawet idealnie zgodny z PSR będzie gorszy od lepszego algorytmu bez względu na nazewnictwo. - TomRZ 2018-03-21 16:52
Ad 1: w jakim sensie PHP nie jest takim językiem jak Java? Ad 2: ależ oczywiście, że lepiej mieć kod, który działa od takiego, który nie jest przydatny w ogóle. Byłem przekonany, że pomijamy takie podstawowe założenia. - Patryk27 2018-03-21 18:56

Pozostało 580 znaków

2018-03-21 15:35
0

Super, dzięki bardzo za uwagi. Faktycznie co do stylistyki powinienem co najmniej być konsekwentny - czego u mnie zabrakło. Sam się na tym przyłapałem, ale szczerze mówiąc zwyczajnie z lenistwa nie chciało mi się poprawiać ;) Teraz i tak muszę poprawić cały kod - popełniłem błąd i kontroler najpierw pobierał dane z modelu a potem przekazywał je widokowi. Co ciekawe w sieci jest pełno tutoriali, które błędnie stwierdzają, że tak właśnie powinno być. Jak się potem okazało w MVC widok ma bezpośredni dostęp do modelu. Polecam dwa artykuły, które mi pomogły

https://r.je/views-are-not-templates.html
https://r.je/mvc-tutorial-real-application-example.html

Ja czmycham poprawiać kod, jak skończę to wrzucę już bardziej praktyczny przykład do oceny.

Pozostało 580 znaków

2018-03-22 14:56
0

Poprawiłem kod swej aplikacji. Jeśli komuś będzie się chciało analizować moje wypociny to właśnie one:
Router

function autoloadClass($className) //no dobra wiem, że composer byłby lepszy ale póki co nie chce mi się tego ogarniać :p
{
  if(file_exists("php/model/$className.php"))
  {
    require "php/model/$className.php";
  }
  else if(file_exists("php/view/$className.php"))
  {
    require "php/view/$className.php";  
  }
  else if(file_exists("php/controler/$className.php"))
  {
     require "php/controler/$className.php"; 
  }
  else if(file_exists("php/extra/$className.php"))
  {
     require "php/extra/$className.php";   
  }
}
 
spl_autoload_register('autoloadClass');
 
class Router
{
  public $parameters = null;
  public $modelName;
  public $viewName;
  public $controlerName;
 
  private function getClassName(&$action)
  {
    $classes = array(
    "kategorie" => "Categories" );
 
    return $classes[$action];
  }
 
  function __construct()
  {
        $arr = explode("/",$_SERVER['REQUEST_URI']);      //tu wyodrębniam sekcję jaką chce odwiedzić użytkownik
        $userAction = $arr[1];
        $userAction = ($userAction === "" || $userAction === "strona") ? "MainPage" : $this->getClassName($userAction); 
 
        $arrLength = count($arr);
 
        $this->modelName = $userAction."Model";
        $this->viewName = $userAction."View";
        $this->controlerName = $userAction."Controler";
 
        if($arrLength > 3)
        {
            $this->parameters = array();  //tu będę pakował parametry - 
 
            for($i = 2; $i < $arrLength - 1; ++$i)
            {
              array_push($this->parameters, $arr[$i]);  //ładuję parametry do tablicy aby potem przekazać je do kontrolera
            }       
 
        } 
  }
 
}
 
$router = new Router;
$model = new $router->modelName();
$controler = new $router->controlerName($model);
$controler->initiate($router->parameters);
$view = new $router->viewName($model);
$view->render();
 

klasa abstrakcyjna kontrolera

abstract class Controler
{
  private $model;
  abstract function initiate($parameters);
}
 

kontroler dla strony głównej

 
class MainPageControler extends Controler 
{
    function __construct($model)
    {
      $this->model = $model;    
    }
 
    function initiate($parameters)
    {
      $pageNumber = (empty($parameters)) ? 1 : $parameters[0];
      $this->model->currentPage = Intval($pageNumber);    
    }
}
 

model dla strony głównej

class MainPageModel
{
    public $currentPage;
 
    private function createQuery()  //w zależności od numeru strony tworzę odpowiednie zapytanie do bazy danych
   {
      $pageNumber = $this->currentPage - 1;
 
      if($pageNumber == 0)
      {
        return "SELECT * FROM Pornusy ORDER BY klucz DESC LIMIT 50";   
      }
      else
      {
        $offset = $pageNumber * 50;
        return "SELECT * FROM Pornusy ORDER BY klucz DESC LIMIT 50 OFFSET $offset";     
      }
 
    }
 
    private function getRandomImages()
    {
        $id1 = rand(1,20);   //wybieram dwa losowo wybrane zdjęcia
        $id2 = rand(1,20);
 
        while($id1 === $id2)
       {
         $id1 = rand(1,20);
            $id2 = rand(1,20);  
        }
 
       $this->randomImage1 = "decoration-image-".$id1;
       $this->randomImage2 = "decoration-image-".$id2;  
    }
 
    private function calculatePageControls(&$total)   //obliczam wartości dla kontrolek sterujących podstronami
    {
      $maxPageIndex = ceil($total / 50);
      $pageNumber = &$this->currentPage;
      $pageControls = new PageControls();
 
      if($maxPageIndex - 5 >= $pageNumber)
      {
        $pageControls->pageControlIndex1 = $pageNumber;
        $pageControls->activePagePosition = 1;
      }
      else
      {
        $pageControls->pageControlIndex1 = $maxPageIndex - 4;
        $pageControls->activePagePosition = 5 + $pageNumber - $maxPageIndex;
      }
 
      $pageControls->topIndex = ++$total - (--$pageNumber * 50); 
      $pageControls->maxPageIndex = $maxPageIndex;
 
      $id1 = rand(1,20);   //wybieram dwa losowo wybrane zdjęcia
      $id2 = rand(1,20);
 
        while($id1 === $id2)
       {
         $id1 = rand(1,20);
         $id2 = rand(1,20); 
        }
 
       $pageControls->randomImage1 = "decoration-image-".$id1;
       $pageControls->randomImage2 = "decoration-image-".$id2;
 
      return $pageControls;
 
    }
 
    function getMoviesData()
    {
        $dataBase = mysqli_connect();
 
        if(!$dataBase)
        {
          return null;
        }
        else
        {
          $result = mysqli_query($dataBase,'SELECT COUNT(*) as total FROM Pornusy');
          $row = mysqli_fetch_assoc($result);
          $totalMovies = $row['total'];
          mysqli_query($dataBase,"SET CHARSET utf8");
          $query = $this->createQuery();
          $result = mysqli_query($dataBase, $query);
          $movies = array();
 
          while($row = mysqli_fetch_assoc($result))
          {
            array_push($movies, new Movie($row['opis'], $row['gwiazdy'],$row['wyswietlenia'], $row['sekundy'],$row['minuty'],$row['godziny'], $row['lektorpl']));
          }
 
          mysqli_close($dataBase);
          $pageControls = $this->calculatePageControls($totalMovies);
          return new PagePackage($movies, $pageControls);
 
        }
    }
}
 

no i widok

 
class MainPageView extends View
 {
     private $dataPackage;
 
    function __construct($model)
    {
        $this->model = $model;
    }
 
    function render()
    {
      if(file_exists("php/template/MainPage.php"))
      {
        $this->dataPackage = $this->model->getMoviesData();
        require "php/template/MainPage.php"; 
      }
    }
 
    private function createBackForwardControls()              //wyświetlanie kontrolek sterujących podstronami
    {
      $pageControls = &$this->dataPackage->pageControls;
      $LeftControlNumber = &$pageControls->PageControlIndex1;
      $MaxPageIndex = &$pageControls->MaxPageIndex;
      $ActivePagePosition = &$pageControls->CurrentPage;
 
         if($LeftControlNumber > 1)
        {
          $PreviousPageNumber = $ActivePagePosition - 1;
          $LeftAnchor = 'href="/strona/'.$PreviousPageNumber.'/"';
        }
        else
        {
          $LeftAnchor = "";
        }
 
        if($ActivePagePosition >= $MaxPageIndex)
        {
          $RightAnchor = "";
        }
        else
        {
          $NextPageNumber = $ActivePagePosition + 1;
          $RightAnchor = 'href="/strona/'.$NextPageNumber.'/"';
        }
 
         echo '<li class="controls-element controls-element-bottom">
          <a '.$LeftAnchor.' class="page-selection page-selection-second-layer">wstecz</a>
          </li>';
 
        echo '<li class="controls-element controls-element-bottom">
          <a '.$RightAnchor.' class="page-selection page-selection-second-layer">dalej</a>
        </li>';
 
    }
 
     function createPageNumbers()  //wyświetlanie kontrolek sterujących podstronami
    {
     $pageControls = &$this->dataPackage->pageControls;
     $LeftNumber = &$pageControls->pageControlIndex1;
     $ActiveControl = &$pageControls->activePagePosition;
 
        for($I = 0; $I < 5; ++$I)
        {
            $Class = ($I + 1 == $ActiveControl) ? "active" : "";
            $PageNumber = $I + $LeftNumber;
 
            echo '<li class="controls-element">
                   <a href="/strona/'.$PageNumber.'/" id="PageControl-'.$I.'" class="page-selection page-selection-first-layer '.$Class.'">'.$PageNumber.'</a>
                 </li>';
        }
    }
 
    private function createTimeLabel($movie)   //tworzenie etykiety z czasem trwania filmu np. 00:34
    {
 
      $seconds = &$movie->seconds;
      $hours = &$movie->hours;
      $minutes = &$movie->minutes;
 
      $seconds = $seconds < 10 ? "0$seconds" : $seconds;
      $hours = $hours != null ? "0$hours:" : "";
 
      if($minutes == null)
      {
         $minutes = "00:";  
      }
      else
      {
        $minutes = ($minutes < 10) ? "0$minutes:" : "$minutes:";
      }  
 
      return $hours.$minutes.$seconds;
    }
 
    function showMovies()  //wyświetlanie filmów
    {
        $currentID = &$this->dataPackage->pageControls->topIndex;
 
        foreach($this->dataPackage->moviesData as $movie)
        {
            $time = $this->createTimeLabel($movie);
            $description = &$movie->tittle;
            $views = &$movie->views;
            $stars = &$movie->stars;
 
            if($stars != null)
            {
              $stars = 'data-stars="'.$stars.'"';   
            }
            else
            {
              $stars = "";  
            }
 
           echo '<div class="movie-complete">
               <div class="image-and-duration"><img class="movie-small-image" src="/images/movie/main/'.$currentID.'.jpg" /><time class="duration">'.$time.'</time></div>
               <div class="movie-description">'.$description.'</div>
                  <div class="additional-options">
                     <span class="views">'.$views.' odsłon</span>
                     <img alt="podgląd filmu" title="kiliknij aby podejrzeć film" class="magnifier-icon" '.$stars.' data-id="'.$currentID.'" src="/images/controls/magnifier.png"/>
                  </div>
          </div>';  
 
          --$currentID;
        }
    }
 }
 

Pozostało 580 znaków

2018-03-22 16:55
0

Przepraszam nie analizowałem wszystkiego z braku czasu, chciałbym jedynie zwrócić uwagę na umiejscowienie klas.

Popełniasz moim zdaniem tutaj błąd, który niestety masowo jest powielany, można go też zobaczyć w większości frameworków.

Błąd polega na przydzielaniau osobnych katalogów na klasy widoków, klasy kontrolerów, i inne.

Może się wydawać, że zrobienie czegoś takiego ułatwia programowanie, ale to jest złudne, na dłuższą metę utrudnia.

  • stosująć taki autoloading musisz sprawdzać kilka katalogów

  • kiedy chciałbyś tworzyć aplikację z części lub modułów, te modułu muszą być dostosowane do takiej struktury katalogów

  • różne frameworki mają różne struktury katalogów, dlatego najlepsze co można zrobić w celu poprawienia przenosności jest maksymalne uproszczenie tej kwestii bez straty np. na bezpieczeństwie

Takim wyjściem jest określenie jednego jedynego katalogu który zawiera klasy, to może być np. katalog class, i ładowania klas wg. namespaces i nazwy klasy.

Np. klasa \myshop\controller\CartController, czyli kasa CartController znajdująca się w przestrzeni nazw myshop\controller, będzie mieć ścieżkę: class\myshop\controller\CartController.php, a klasa \myothermodule\dupamaryna\CheckPrivileges będzie mieć ścieżkę class\myothermodule\dupamaryna\CheckPrivileges

To jest bardzo proste i elastyczne.

Nie trzeba mieć specjalnego osobnego katalogu na kotroler, w routingu po prostu określa się pełną ścieżkę do klasy, podobnie z innymi sprawami. To bardzo elastyczne i dające dużo wolności rozwiązanie w duchu PHP.


edytowany 3x, ostatnio: TomRZ, 2018-03-22 16:56

Pozostało 580 znaków

2018-03-22 19:30
0
TomRZ napisał(a):

Przepraszam nie analizowałem wszystkiego z braku czasu, chciałbym jedynie zwrócić uwagę na umiejscowienie klas.

Popełniasz moim zdaniem tutaj błąd, który niestety masowo jest powielany, można go też zobaczyć w większości frameworków.

Błąd polega na przydzielaniau osobnych katalogów na klasy widoków, klasy kontrolerów, i inne.

Może się wydawać, że zrobienie czegoś takiego ułatwia programowanie, ale to jest złudne, na dłuższą metę utrudnia.

  • stosująć taki autoloading musisz sprawdzać kilka katalogów

Co Ty znowu bredziesz człowieku ? Co musi sprawdzać? każesz mu robić syf w katalogach pchając wszystko w jeden folder a potem szukanie jednego pliku wśród widoków/modelów i innych?
Wystarczy użyć psr-4 przez composera i może mieć nawet 200 folderów z podfolderami a autoloading i tak będzie działał.

Pozostało 580 znaków

2018-03-22 19:47
0

Wypraszam sobie do mnie teksty w stylu "co Ty bredzisz". Jeżeli nie umiesz się zachować, to wyjdź.

I jeszcze jedno: w PSR4 nie ma nic na temat struktury katalogów która rozdziela osobno kotrolery i inne rzeczy, może najpierw poczytaj zanim czymś się podeprzesz.


edytowany 3x, ostatnio: TomRZ, 2018-03-22 19:58

Pozostało 580 znaków

2018-03-22 20:07
0

A czy ty kiedykolwiek widzialeś narzędzie które oparte jest o nazewnictwo katalogów ? Drugi raz wypowiadasz się na temat PHP i drugi raz nie wiesz o czym piszesz.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0