Czy dobrze rozumiem wzorzec MVC?

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();
 
 

?>

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).

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.

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).

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ę.

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.

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;
		}
	}
 }

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.

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ł.

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.

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.

0

Człowieku, umówmy się, że Twoja opinia nic dla mnie nie znaczy. Ja się tutaj wypowiadam na temat, a Ty mnie atakujesz i zasyfiasz wątek, idź być Trollem gdzie indziej.

0

A to, jeśli już chcemy porzucić klasyczny, wydawałoby się (mi) sposób rozdziału na modele, widoki i kontrolery, to nie lepiej by było trzymać pliki w folderach, które będą odpowiadały modułom?
Wtedy jak chcesz użyć, dajmy na to, swojej autoryzacji w następnym projekcie, to łapiesz katalog, a nie szukasz tych czterech plików wśród całej masy innych.

I akurat tutaj wrzucenie wszystkiego na pałę(jeśli taki miał być przekaz) do "classes", bez żadnej wewnętrznej jego struktury faktycznie powoduje bajzel - nie wyobrażam sobie przeszukiwania kopy plików w poszukiwaniu tego jednego, którego potrzebuję.

I tak samo co do loadingu - nigdy nie zauważyłem żeby użycie dowolnie głęboko zagnieżdżonej struktury powodowało jakiekolwiek problemy, dopóty, dopóki była ona spójna. Zatem trzymanie wszystkiego w jednym miejscu traci argument o problematyczności rozwiązania, a niestety, jak wyżej wspomniałem, powoduje problem z odnajdywaniem potrzebnych rzeczy.

Inna rzecz, że z PHP od kilku lat się praktycznie nie widziałem nawet, więc cóż...

0

I akurat tutaj wrzucenie wszystkiego na pałę(jeśli taki miał być przekaz) do "classes", bez żadnej wewnętrznej jego struktury faktycznie powoduje bajzel - nie wyobrażam sobie przeszukiwania kopy plików w poszukiwaniu tego jednego, którego potrzebuję.

Czy jesteś w stanie przedstawić jakiś konkretny przykład kiedy musiałbyś czegoś szukać?

Nie rozumiem gdzie i po co miałoby być to szukanie plików? Przecież takie podejście z jednym katalogiem nie likwiduje podziału MVC, powiem więcej: czyni je bardziej elastycznym.

Posiadając jeden katalog na klasy bardzo łatwo tworzyć elastyczne moduły.

Autoryzacja: autoryzacji nie robi się przez katalogi tylko przez logikę aplikacji która wie do czego ma dostęp użytkownik a do czego nie. Listy ACL i tym podobne sprawy, katalogi nie mają tu nic do rzeczy, poziom katalogów to poziom konfiguracji serwera WWW - głównie podział na katalog(i) publicznie dostepne i publicznie niedostępne.

I tak samo co do loadingu - nigdy nie zauważyłem żeby użycie dowolnie głęboko zagnieżdżonej struktury powodowało jakiekolwiek problemy, dopóty, dopóki była ona spójna. Zatem trzymanie wszystkiego w jednym miejscu traci argument o problematyczności rozwiązania, a niestety, jak wyżej wspomniałem, powoduje problem z odnajdywaniem potrzebnych rzeczy.

Naprawdę tego nie kapuje, jeżeli masz klasę to od razu automatycznie wiesz gdzie jest - po samej nazwie! Tak jak to wyjaśniałm wcześniej, prościej się nie da. Tymczasem przy kilku różnych katalogach tak naprawdę nie wiesz w którym katalogu jest określona klasa i wtedy właśnie musisz czegoś szukać!

0

Zgadzam się co do kwestii rozbicia pod tym względem, że musisz otworzyć folder/katalog "controller(-s)", żeby się dostać do kontrolera, którego potrzebujesz. Natomiast mi osobiście jest zwyczajnie łatwiej stosować podział, właśnie ze względu na to, że mogę sobie niepotrzebne aktualnie pliki "ukryć" w drzewku edytora przez zwinięcie katalogu, który je zawiera. Likwiduje to "szum".

I tak, z jednej strony masz je w jednym miejscu, więc unika kilku dodatkowych kliknięć, żeby się do potrzebnego pliku dostać. Z drugiej jednak strony, gdy masz, dajmy na to 40-60 różnych plików, to powstaje właśnie powyższy "szum" - nie masz nawet najmniejszego filtrowania komponentów, które przeglądasz, więc musisz wzrokowo przelecieć przez wszystko, co się znajduje w okolicy. I jest to przykład mocno hiperboliczny, bo przy takiej ilości klas projekt jest albo naprawdę spory, albo tworzysz Planetę opierając ją na Plutonie... Czyli masz znacznie poważniejszy problem.

Co do kwestii "MVC a struktura katalogów", to nawet nie mamy co dyskutować. Oczywistym jest, że fizyczny podział tutaj nie ma za dużo do rzeczy i klasy muszą przede wszystkim byc podzielone logicznie. Prawda.
Natomiast już co do ew. przenośności kodu, to zwyczajnie chciałem zwrócić uwagę, że w razie potrzeby wygodniej jest skopiować cały fizyczny katalog wraz z jego strukturą wewnętrzną, zawierający dany moduł, aniżeli szukać pojedynczych plików. Jak chciałbym skopiować fragment odpowiedzialny za obliczanie faktur, to chcę złapać za "Invoice", a nie przeciepywać "Invoice", "InvoiceController", "InvoiceEvents", "InvoiceView" itp.

To jednak nie zmienia faktu, że powyższy post był jedynie sugestią do rozpatrzenia, a nie wielkim objawieniem. Trochę za mody i głupi jeszcze jestem na forsowanie swoich urojeń :P

2

Na co dzień mam do czynienia z czymś takim:

Prosty autoload oparty na nazwach i położeniu plików i PSR-0, nie ma tu żadnych namespaces

index.php w głównym katalogu projektu:

define('DOCROOT', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR);

function autoload($class, $directory = 'classes')
{
    $file = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';

    $path = DOCROOT . $directory . DIRECTORY_SEPARATOR . $file;
    if (realpath($path))
    {
        require $path;
    }
}

spl_autoload_register('autoload');

$controller = new Controller_User;
echo $controller->action_load();

Według tego założenia, to wszystkie klasy mają być w podkatalogu classes i tak kontrolery w podkatalogu Controller, modele w podkatalogu Model oczywiście w classes. Liczą się duże i małe litery.

Kontroler:

defined('DOCROOT') OR die('No direct script access.');

class Controller_User
{
     public function action_load()
     {
        $user = new stdClass;
        $user->name = 'John';
        $user->surname = 'Doe';
        $user->age = 40;

        return View::make('template', compact('user'))
            ->render();
     }
}

Klasa View do renderowania szablonu:

defined('DOCROOT') OR die('No direct script access.');

class View
{
    private $_file;
    private $_data;

    public static function make($file, array $data = array())
    {
        return new View($file, $data);
    }

    public function __construct($file, array $data = array())
    {
        $this->_file = $file;
        $this->_data = $data;
    }

    public function render()
    {
        extract($this->_data);
        ob_start();
        include DOCROOT . 'views' . DIRECTORY_SEPARATOR . $this->_file . '.php';
        return ob_get_clean();
    }
}

i renderowana templatka:

<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <ul style="list-style: none">
            <li>Name: <strong><?= $user->name ?></strong></li>
            <li>Surname: <strong><?= $user->surname ?></strong></li>
            <li>Age: <strong><?= $user->age ?></strong></li>
        </ul>
    </body>
</html>

Struktura katalogów i plików wg. takiego założenia:

classes/
    Controller/
        User.php
    Model/
        User.php        
    View.php
views/
    template.php
index.php  

Prosta implementacja MVP, jako model w tym przypadku jest stdClass. Tutaj sobie mogę tworzyć w katalogu classes różne podkatalogi jak Controller, Model, Middleware, Service, co oczywiście jest związane z odpowiedzialnością danej klasy ale każda musi mieć prefix taki jak nazwa podkatalogu. Proste jak drut. A to że teraz w nowoczesnych frameworkach jest inaczej to już inna sprawa.

0

Widzę ładnie się rozkręciliście w temacie struktury katalogów. Może jakieś uwagi na temat samego kodu? No w sumie najważniejsze jest to czy kod przestrzega reguł wzorca mvc.

0

Natomiast już co do ew. przenośności kodu, to zwyczajnie chciałem zwrócić uwagę, że w razie potrzeby wygodniej jest skopiować cały fizyczny katalog wraz z jego strukturą wewnętrzną, zawierający dany moduł, aniżeli szukać pojedynczych plików. Jak chciałbym skopiować fragment odpowiedzialny za obliczanie faktur, to chcę złapać za "Invoice", a nie przeciepywać "Invoice", "InvoiceController", "InvoiceEvents", "InvoiceView" itp.

Przy rozwiązaniu z jednym katalogiem, dane namespace to jest moduł - to jest właściwie logiczna konsekwencja takiego podejścia.

Kontynuując Twój przykład, cały moduł faktur byłby w namespace np. \mycompany\invoice

Jeżeli chcesz cały moduł kopiować - kopiujesz po prostu jeden caly katalog, zwróć uwagę jaką osiągasz dzięki temu przenośność - w przykładzie z wieloma katalogami musisz kopiować jeden moduł rozproszony na wiele katalogów.

W przypadku mojego podejścia - jest jeden katalog na klasy, plus jeden publiczny i jeden na zasoby takie jak szablony.

W każdym z tych katalogów utrzymujesz namespace, czyli szablon dla pokazania Invoice będzie miał ścieżkę np. resources/mycompany/invoice/show-invoice.phtml, jakiś a skrypt javascript powiązany z tym modułem będzie w public/mycompany/invoice/myscript.js

Takie podejście rozwiązuje przy okazji konflikty nazw z marszu.

Rozwiązanie które proponuje nie jest najlepsze i idealne, ale czy takie istnieje? Dzieląc na katalogi i robiąć sztywną strukturę też coś tracimy, ja chciałem Wam przedstawić coś innego, inne podejście aby poszerzyć spektrum wyboru.

Do OP wątku: to na jaką się strukturę zdecydujesz to dość podstawowa sprawa, taki szkielet który później niewygodnie zmieniać, wydaje mi się, że ta dyskusja w tym sensie może być dla Ciebie wartościowa

Edit: tutaj jeszcze classloader który ładuje wg. namespaces jeżeli katalog vendor na klasy jest w tym samym katalogu co classloader:

<?php
/////////////////////////
// Classloader
spl_autoload_register( function ($className) {
  // Trying namespace...
  $className = ltrim($className, '\\');
  $fileName  = '';
  $namespace = '';
  if ($lastNsPos = strripos($className, '\\')) {
      $namespace = substr($className, 0, $lastNsPos);
      $className = substr($className, $lastNsPos + 1);
      $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
  }
  $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
  
  // Final filename
  $fileName =  __DIR__.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.$fileName;  
  
  if (file_exists( $fileName )) {
      require_once $fileName;
  }
} );
0
TomRZ napisał(a):

Człowieku, umówmy się, że Twoja opinia nic dla mnie nie znaczy. Ja się tutaj wypowiadam na temat, a Ty mnie atakujesz i zasyfiasz wątek, idź być Trollem gdzie indziej.

Trolem? tobie brakuje elementarnej wiedzy. Zastałeś się kilka lat w tył i próbujesz na forum udawać alfę i omegę mimo, że spora część osób zwraca się uwagę, że piszesz głupoty..

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