Jak powinien wyglądać model w MVC? [Zamknięte]

551

Właśnie rozumiem strukturę MVC i często zastanawiam się, ile kodu powinno zostać w tym modelu. Zwykle mam klasę dostępu do danych, która ma takie metody:

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

Moje modele są zazwyczaj klasami jednostek odwzorowanymi na tabelę bazy danych.

Czy obiekt modelu powinien mieć wszystkie właściwości odwzorowane w bazie danych, a także powyższy kod, czy też można oddzielić ten kod, który faktycznie działa w bazie danych?

Czy skończę mieć cztery warstwy?

Dietpixel
źródło
133
Dlaczego wychwytujesz wyjątki, aby je ponownie rzucić?
Bailey Parker,
9
@Elias Van Ootegem: nie trafiłeś w sedno. w tym przypadku nie ma sensu ich łapać.
Karoly Horvath
4
@Elias Van Ootegem: huh? jeśli działa z ponownym rzutem, oznacza to, że górna warstwa przechwytuje wyjątek. Ale jeśli jest taki, to złapałby go bez tego bezsensownego powtórki ... (jeśli nadal go nie dostaniesz, wykpij mały kod testowy)
Karoly Horvath
3
@Elias Van Ootegem: Nie mam pojęcia o czym mówisz, brak obsługi wyjątku na określonej warstwie nie oznacza, że ​​zatrzyma aplikację. proszę skonstruować (a ściślej: nie zbudować) przykład kodu, w którym konieczne jest ponowne zwrócenie. zatrzymajmy tę nietypową rozmowę, proszę
Karoly Horvath,
6
@drrcknlsn: to poprawny argument, ale w takim przypadku przynajmniej złap wyjątek, którego się spodziewasz, ogólny Exceptionnie ma dużej wartości dokumentacji. Osobiście, gdybym poszedł tą drogą, wybrałbym PHPDoc @exceptionlub podobny mechanizm, więc pojawia się w wygenerowanej dokumentacji.
Karoly Horvath

Odpowiedzi:

903

Uwaga: poniżej znajduje się opis tego, jak rozumiem wzorce podobne do MVC w kontekście aplikacji internetowych opartych na PHP. Wszystkie linki zewnętrzne użyte w treści mają na celu wyjaśnienie terminów i pojęć, a nie sugerowanie mojej wiarygodności w tym temacie.

Pierwszą rzeczą, którą muszę wyjaśnić, jest: model jest warstwą .

Po drugie: istnieje różnica między klasycznym MVC a tym, czego używamy do tworzenia stron internetowych. Oto trochę starszej odpowiedzi, którą napisałem, która krótko opisuje, jak się różnią.

Czym NIE jest model:

Model nie jest klasą ani żadnym pojedynczym obiektem. Bardzo często popełniany jest błąd (ja też to zrobiłem, chociaż oryginalna odpowiedź została napisana, gdy zacząłem uczyć się inaczej) , ponieważ większość ram utrwala to błędne przekonanie.

Nie jest to również technika mapowania obiektowo-relacyjnego (ORM) ani abstrakcja tabel baz danych. Każdy, kto mówi inaczej, najprawdopodobniej próbuje „sprzedać” inną zupełnie nową ORM lub całą platformę.

Co to jest model:

Przy odpowiedniej adaptacji MVC, M zawiera całą logikę biznesową domeny, a Warstwa Modelowa składa się głównie z trzech rodzajów struktur:

  • Obiekty Domeny

    Obiekt domeny jest logicznym kontenerem zawierającym wyłącznie informacje o domenie; zazwyczaj reprezentuje logiczny byt w obszarze problemowym. Powszechnie nazywane logiką biznesową .

    W tym miejscu możesz zdefiniować sposób sprawdzania poprawności danych przed wysłaniem faktury lub obliczenia całkowitego kosztu zamówienia. Jednocześnie obiekty domenowe są całkowicie nieświadome miejsca do przechowywania - ani skąd (baza danych SQL, interfejs API REST, plik tekstowy itp.), Ani nawet jeśli zostaną zapisane lub odzyskane.

  • Mapujący dane

    Te obiekty są odpowiedzialne tylko za przechowywanie. Jeśli przechowujesz informacje w bazie danych, to właśnie tam mieszka SQL. A może używasz pliku XML do przechowywania danych, a twoi maperzy parsują zi do plików XML.

  • Usługi

    Możesz myśleć o nich jako o „obiektach domeny wyższego poziomu”, ale zamiast logiki biznesowej, Usługi są odpowiedzialne za interakcję między obiektami domeny a Mapperami . Struktury te tworzą w końcu „publiczny” interfejs do interakcji z logiką biznesową domeny. Możesz ich uniknąć, ale pod groźbą wycieku logiki domeny do kontrolerów .

    W pytaniu dotyczącym implementacji ACL znajduje się odpowiednia odpowiedź na ten temat - może być przydatna.

Komunikacja między warstwą modelu a innymi częściami triady MVC powinna odbywać się tylko za pośrednictwem Usług . Wyraźna separacja ma kilka dodatkowych zalet:

  • pomaga egzekwować zasadę jednej odpowiedzialności (SRP)
  • zapewnia dodatkowy „pokój poruszania się” na wypadek zmiany logiki
  • sprawia, że ​​kontroler jest tak prosty, jak to możliwe
  • daje jasny plan, jeśli kiedykolwiek potrzebujesz zewnętrznego interfejsu API

 

Jak wchodzić w interakcje z modelem?

Wymagania wstępne: obejrzyj wykłady „Global State and Singletons” i „Don't Look For Things!” z rozmów na temat czystego kodu.

Uzyskiwanie dostępu do wystąpień usług

Zarówno w przypadku widoków, jak i kontrolerów (które można nazwać „warstwą interfejsu użytkownika”) w celu uzyskania dostępu do tych usług, istnieją dwa ogólne podejścia:

  1. Możesz wstrzyknąć wymagane usługi bezpośrednio do konstruktorów twoich widoków i kontrolerów, najlepiej używając kontenera DI.
  2. Używanie fabryki dla usług jako obowiązkowej zależności dla wszystkich twoich widoków i kontrolerów.

Jak można podejrzewać, pojemnik DI jest o wiele bardziej eleganckim rozwiązaniem (choć nie jest najłatwiejszy dla początkującego). Dwie biblioteki, które polecam rozważyć pod kątem tej funkcjonalności, to samodzielny komponent DependencyInjection firmy Syfmony lub Auryn .

Zarówno rozwiązania wykorzystujące fabrykę, jak i kontener DI pozwoliłyby również na udostępnianie wystąpień różnych serwerów, które mają być współużytkowane przez wybrany kontroler i podgląd dla danego cyklu żądanie-odpowiedź.

Zmiana stanu modelu

Teraz, gdy masz dostęp do warstwy modelu w kontrolerach, musisz zacząć z nich korzystać:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

Twoje kontrolery mają bardzo jasne zadanie: weź dane wejściowe użytkownika i, na podstawie tych danych wejściowych, zmień aktualny stan logiki biznesowej. W tym przykładzie zmieniane są stany: „użytkownik anonimowy” i „użytkownik zalogowany”.

Kontroler nie jest odpowiedzialny za sprawdzanie poprawności danych wejściowych użytkownika, ponieważ jest to część reguł biznesowych, a kontroler zdecydowanie nie wywołuje zapytań SQL, takich jak to, co zobaczysz tutaj lub tutaj (proszę ich nie nienawidzić, są wprowadzane w błąd, nie są złe).

Pokazuje użytkownikowi zmianę stanu.

Ok, użytkownik się zalogował (lub nie powiódł się). Co teraz? Ten użytkownik nadal nie jest tego świadomy. Musisz więc właściwie zareagować i to jest odpowiedzialność za widok.

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

W tym przypadku widok wygenerował jedną z dwóch możliwych odpowiedzi, w oparciu o bieżący stan warstwy modelu. W przypadku innego przypadku użycia widok wybierałby różne szablony do renderowania, na podstawie czegoś takiego jak „aktualnie wybrany artykuł”.

Warstwa prezentacji może być dość skomplikowana, jak opisano tutaj: Zrozumienie widoków MVC w PHP .

Ale właśnie tworzę interfejs API REST!

Oczywiście zdarzają się sytuacje, w których jest to przesada.

MVC jest tylko konkretnym rozwiązaniem dla zasady separacji problemów . MVC oddziela interfejs użytkownika od logiki biznesowej, aw interfejsie użytkownika oddzielił obsługę danych wejściowych i prezentacji użytkownika. To jest kluczowe. Chociaż często ludzie określają to jako „triadę”, tak naprawdę nie składa się ona z trzech niezależnych części. Struktura jest bardziej taka:

Separacja MVC

Oznacza to, że gdy logika warstwy prezentacji jest prawie nieistniejąca, pragmatyczne podejście polega na zachowaniu jej jako pojedynczej warstwy. Może także znacznie uprościć niektóre aspekty warstwy modelu.

Korzystając z tego podejścia, przykład logowania (dla interfejsu API) można zapisać jako:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

Chociaż nie jest to trwałe, jeśli masz skomplikowaną logikę renderowania treści odpowiedzi, to uproszczenie jest bardzo przydatne w przypadku bardziej trywialnych scenariuszy. Ale uwaga , takie podejście stanie się koszmarem, gdy spróbujesz użyć go w dużych bazach kodowych ze złożoną logiką prezentacji.

 

Jak zbudować model?

Ponieważ nie ma jednej klasy „Model” (jak wyjaśniono powyżej), tak naprawdę nie „buduje się modelu”. Zamiast tego zaczynasz od tworzenia Usług , które są w stanie wykonywać określone metody. A następnie zaimplementuj Obiekty Domeny i Maperów .

Przykład metody usługi:

W obu powyższych podejściach zastosowano tę metodę logowania do usługi identyfikacji. Jak by to faktycznie wyglądało. Używam nieco zmodyfikowanej wersji tej samej funkcjonalności z biblioteki , którą napisałem ... ponieważ jestem leniwy:

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

Jak widać, na tym poziomie abstrakcji nic nie wskazuje na to, skąd dane zostały pobrane. Może to być baza danych, ale może to być również próbny obiekt do celów testowych. Nawet osoby mapujące dane, które są do tego faktycznie używane, są ukryte w privatemetodach tej usługi.

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

Sposoby tworzenia twórców map

Aby wdrożyć abstrakcję trwałości, najbardziej elastycznym podejściem jest stworzenie niestandardowych maperów danych .

Schemat mapowania

Od: Książka PoEAA

W praktyce są one implementowane do interakcji z określonymi klasami lub nadklasami. Powiedzmy, że masz Customer, a Adminw kodzie (zarówno dziedziczenie z Userklasy nadrzędnej). Oba prawdopodobnie miałyby osobne pasujące mapowanie, ponieważ zawierają one różne pola. Ale skończysz także na wspólnych i często używanych operacjach. Na przykład: aktualizacja czasu „ostatni raz online” . I zamiast uczynić obecnych twórców map bardziej skomplikowanymi, bardziej pragmatycznym podejściem jest stworzenie ogólnego „User Mapper”, który aktualizuje tylko ten znacznik czasu.

Kilka dodatkowych komentarzy:

  1. Tabele i model bazy danych

    Podczas gdy czasami istnieje bezpośredni związek 1: 1: 1 między tabelą bazy danych, obiektem domeny i maperem , w większych projektach może być mniej powszechny niż się spodziewasz:

    • Informacje używane przez pojedynczy obiekt domeny mogą być mapowane z różnych tabel, podczas gdy sam obiekt nie ma trwałości w bazie danych.

      Przykład: jeśli generujesz raport miesięczny. To zbierałoby informacje z różnych tabel, ale MonthlyReportw bazie danych nie ma magicznej tabeli.

    • Pojedynczy program mapujący może wpływać na wiele tabel.

      Przykład: gdy przechowujesz dane z Userobiektu, ten obiekt domeny może zawierać kolekcję innych obiektów domeny - Groupinstancji. Jeśli je zmienisz i zapiszesz User, program mapujący dane będzie musiał zaktualizować i / lub wstawić wpisy w wielu tabelach.

    • Dane z jednego obiektu domeny są przechowywane w więcej niż jednej tabeli.

      Przykład: w dużych systemach (pomyśl: średniej wielkości sieć społecznościowa) przechowywanie danych uwierzytelniających użytkownika i często używanych danych osobno od większych fragmentów treści może być pragmatyczne, co jest rzadko wymagane. W takim przypadku nadal możesz mieć jedną Userklasę, ale informacje w niej zawarte zależą od tego, czy zostały pobrane pełne szczegóły.

    • Dla każdego obiektu domeny może być więcej niż jeden program odwzorowujący

      Przykład: masz witrynę z wiadomościami ze wspólnym kodem opartym zarówno na oprogramowaniu publicznym, jak i oprogramowaniu do zarządzania. Ale chociaż oba interfejsy używają tej samej Articleklasy, zarządzanie potrzebuje znacznie więcej informacji. W takim przypadku mielibyśmy dwa oddzielne elementy mapujące: „wewnętrzny” i „zewnętrzny”. Każde z nich wykonuje inne zapytania, a nawet korzysta z różnych baz danych (jak w trybie master lub slave).

  2. Widok nie jest szablonem

    Wyświetl instancje w MVC (jeśli nie używasz wariantu wzorca MVP) są odpowiedzialne za logikę prezentacji. Oznacza to, że każdy widok zwykle żongluje co najmniej kilkoma szablonami. Pozyskuje dane z warstwy modelu a następnie na podstawie otrzymanych informacji wybiera szablon i ustawia wartości.

    Jedną z korzyści, jakie z tego zyskujesz, jest możliwość ponownego użycia. Jeśli utworzysz ListViewklasę, to z dobrze napisanym kodem możesz mieć tę samą klasę, która przekazuje listę użytkowników i komentarze poniżej artykułu. Ponieważ oba mają tę samą logikę prezentacji. Po prostu zmieniasz szablony.

    Możesz użyć natywnych szablonów PHP lub innego silnika szablonów. Mogą też istnieć biblioteki innych firm, które mogą całkowicie zastąpić wystąpienia programu View .

  3. Co ze starą wersją odpowiedzi?

    Jedyną istotną zmianą jest to, co nazywa się Modelem w starej wersji jest w rzeczywistości Usługą . Reszta „analogii bibliotecznej” nieźle sobie radzi.

    Jedyną wadą, jaką widzę, jest to, że byłaby to naprawdę dziwna biblioteka, ponieważ zwróciłaby ci informacje z książki, ale nie pozwoliłaby ci dotknąć samej książki, ponieważ w przeciwnym razie abstrakcja zacząłaby „wyciekać”. Być może będę musiał wymyślić bardziej odpowiednią analogię.

  4. Jaki jest związek między widokami a instancjami kontrolera ?

    Struktura MVC składa się z dwóch warstw: interfejsu użytkownika i modelu. Głównymi strukturami w warstwie interfejsu użytkownika są widoki i kontroler.

    Kiedy masz do czynienia ze stronami internetowymi, które używają wzorca projektowego MVC, najlepszym sposobem jest uzyskanie stosunku 1: 1 między widokami a kontrolerami. Każdy widok reprezentuje całą stronę w Twojej witrynie i ma dedykowany kontroler do obsługi wszystkich przychodzących żądań dla tego konkretnego widoku.

    Na przykład, aby przedstawić otwarty artykuł, będziesz mieć \Application\Controller\Documenti \Application\View\Document. Zawierałoby to wszystkie główne funkcje warstwy interfejsu użytkownika, jeśli chodzi o obsługę artykułów (oczywiście możesz mieć niektóre komponenty XHR , które nie są bezpośrednio związane z artykułami) .

tereško
źródło
4
@Rinzler, zauważysz, że nigdzie w tym linku nie ma nic o Modelu (z wyjątkiem jednego komentarza). To tylko „obiektowy interfejs do tabel bazy danych” . Jeśli spróbujesz uformować to w coś podobnego do modelu, ostatecznie naruszysz SRP i LSP .
tereško
8
@ Hafichuk tylko sytuacje, w których uzasadnione jest zastosowanie wzorca ActiveRecord , służy do prototypowania. Kiedy zaczniesz pisać kod przeznaczony dla produkcji, staje się on anty-wzorcem, ponieważ łączy w sobie pamięć masową i logikę biznesową. A ponieważ Model Layer jest całkowicie nieświadomy innych części MVC. Nie zmienia się to w zależności od odmiany oryginalnego wzoru . Nawet podczas korzystania z MVVM. Nie ma „wielu modeli” i nie są one mapowane na nic. Model jest warstwą.
tereško
3
Wersja skrócona - modele to struktury danych .
Eddie B
9
Widząc, że wynalazł MVC, artykuł może mieć pewne zalety.
Eddie B
3
... lub nawet tylko zestaw funkcji. MVC nie wymaga implementacji w stylu OOP, chociaż jest w większości implementowane w ten sposób. Najważniejsze jest oddzielenie warstw i ustanowienie właściwego przepływu danych i kontroli
hek2mgl
37

Wszystko, co jest logiką biznesową, należy do modelu, niezależnie od tego, czy jest to zapytanie do bazy danych, obliczenia, wywołanie REST itp.

Możesz mieć dostęp do danych w samym modelu, wzorzec MVC nie ogranicza tego. Możesz pokryć go usługami, programami mapującymi i tym podobnymi, ale faktyczna definicja modelu to warstwa, która obsługuje logikę biznesową, nic więcej, nic więcej. Może to być klasa, funkcja lub pełny moduł z obiektami gazillionowymi, jeśli tego chcesz.

Zawsze łatwiej jest mieć osobny obiekt, który faktycznie wykonuje zapytania do bazy danych, zamiast wykonywać je bezpośrednio w modelu: będzie to szczególnie przydatne podczas testowania jednostkowego (ze względu na łatwość wstrzykiwania fałszywej zależności bazy danych w modelu):

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

Ponadto w PHP rzadko trzeba wychwytywać / odrzucać wyjątki, ponieważ ślad jest zachowany, szczególnie w przypadku takim jak twój przykład. Po prostu pozwól, aby wyjątek został zgłoszony i zamiast tego złap go w kontrolerze.

netcoder
źródło
Moja struktura jest bardzo podobna, myślę, że po prostu trochę to oddzielę. Powodem, dla którego omijałem połączenie było to, że musiałem mieć porcje uruchamiane w transakcjach. Chciałem dodać użytkownika, a następnie dodać użytkownika do roli, ale rola z powrotem, jeśli się nie powiedzie. Jedynym sposobem, w jaki mogłem to rozwiązać, było przekazanie połączenia.
Dietpixel
10
-1: zdarza się również, że jest całkowicie błędny. Model nie jest abstrakcją dla stołu.
tereško
1
UserKlasa zasadniczo rozszerza model, ale itsn't obiekt. Użytkownik powinien być obiektem i mieć takie właściwości jak: id, name ... Wdrażasz Userklasę pomocnika.
TomSawyer,
1
Myślę, że rozumiesz MVC, ale nie rozumiesz, co to jest OOP. W tym scenariuszu, jak już powiedziałem, Useroznacza obiekt i powinien on mieć właściwości użytkownika, a nie metody typu CheckUsername: co należy zrobić, jeśli chcesz utworzyć nowy Userobiekt? new User($db)
TomSawyer,
@TomSawyer OOP nie oznacza, że ​​obiekty muszą mieć właściwości. To, co opisujesz, to wzorzec projektowy, który jest nieistotny dla pytania lub odpowiedzi na to pytanie. OOP to model językowy, a nie wzorzec projektowy.
netcoder
20

W sieci - „MVC” możesz robić, co chcesz.

Oryginalna koncepcja (1) opisywała model jako logikę biznesową. Powinien reprezentować stan aplikacji i wymuszać pewną spójność danych. Takie podejście jest często opisywane jako „model tłuszczu”.

Większość frameworków PHP stosuje bardziej płytkie podejście, gdzie model jest tylko interfejsem bazy danych. Ale przynajmniej te modele powinny nadal weryfikować przychodzące dane i relacje.

Tak czy inaczej, nie będziesz bardzo daleko, jeśli oddzielisz SQL lub połączenia z bazą danych na inną warstwę. W ten sposób musisz skupić się na prawdziwych danych / zachowaniu, a nie na faktycznym interfejsie API pamięci. (Jednak przesadne jest nieuzasadnione. Np. Nigdy nie będziesz w stanie zastąpić zaplecza bazy danych magazynem, jeśli nie zostało to wcześniej zaplanowane).

Mario
źródło
8
link jest nieprawidłowy (404)
Kyslik
6

Więcej oftenly większość aplikacji będzie miał danych, wyświetlanie i przetwarzanie części i po prostu umieścić wszystkie te, w listach M, Vi C.

Model ( M) -> Ma atrybuty utrzymujące stan aplikacji i nie wie nic o Vi C.

View ( V) -> Ma format wyświetlania aplikacji i wie tylko o tym, jak na nim przetrawić model, i nie przejmuje się tym C.

Controller ( C) ----> Ma część przetwarzania aplikacji i działa jak połączenie między M i V i zależy od obu M, w Vprzeciwieństwie MdoV .

Zasadniczo istnieje oddzielny problem. W przyszłości wszelkie zmiany lub rozszerzenia mogą być bardzo łatwo dodawane.

czuć się dobrze i programować
źródło
0

W moim przypadku mam klasę bazy danych, która obsługuje wszystkie bezpośrednie interakcje z bazą danych, takie jak zapytania, pobieranie itp. Więc gdybym musiał zmienić bazę danych z MySQL na PostgreSQL , nie byłoby żadnego problemu. Dlatego dodanie tej dodatkowej warstwy może być przydatne.

Każda tabela może mieć własną klasę i określone metody, ale aby uzyskać dane, klasa bazy danych może sobie z tym poradzić:

Plik Database.php

class Database {
    private static $connection;
    private static $current_query;
    ...

    public static function query($sql) {
        if (!self::$connection){
            self::open_connection();
        }
        self::$current_query = $sql;
        $result = mysql_query($sql,self::$connection);

        if (!$result){
            self::close_connection();
            // throw custom error
            // The query failed for some reason. here is query :: self::$current_query
            $error = new Error(2,"There is an Error in the query.\n<b>Query:</b>\n{$sql}\n");
            $error->handleError();
        }
        return $result;
    }
 ....

    public static function find_by_sql($sql){
        if (!is_string($sql))
            return false;

        $result_set = self::query($sql);
        $obj_arr = array();
        while ($row = self::fetch_array($result_set))
        {
            $obj_arr[] = self::instantiate($row);
        }
        return $obj_arr;
    }
}

Klasa obiektu tabeliL

class DomainPeer extends Database {

    public static function getDomainInfoList() {
        $sql = 'SELECT ';
        $sql .='d.`id`,';
        $sql .='d.`name`,';
        $sql .='d.`shortName`,';
        $sql .='d.`created_at`,';
        $sql .='d.`updated_at`,';
        $sql .='count(q.id) as queries ';
        $sql .='FROM `domains` d ';
        $sql .='LEFT JOIN queries q on q.domainId = d.id ';
        $sql .='GROUP BY d.id';
        return self::find_by_sql($sql);
    }

    ....
}

Mam nadzieję, że ten przykład pomoże ci stworzyć dobrą strukturę.

Ibu
źródło
12
„Więc jeśli musiałbym zmienić moją bazę danych z MySQL na PostgreSQL, nie byłoby żadnego problemu.” Uhhhmmm z powyższym kodem miałbyś ogromny problem ze zmianą czegokolwiek imo.
PeeHaa,
Widzę, że moja odpowiedź ma coraz mniej sensu po edycji, a wraz z upływem czasu. Ale powinno tu zostać
Ibu,
2
Databasew tym przykładzie nie ma klasy. To tylko opakowanie dla funkcji. Jak możesz mieć „klasę obiektów tabeli” bez obiektu?
tereško
2
@ tereško Przeczytałem wiele Twoich postów i są świetne. Ale nigdzie nie mogę znaleźć kompletnych ram do nauki. Czy znasz takiego, który „robi to dobrze”? Lub przynajmniej taki, który podoba się tobie i niektórym innym tutaj na TAK, aby to zrobić? Dzięki.
Johnny
Mogę się spóźnić, ale chciałbym zauważyć, że PDO prawie rozwiązuje problem konieczności utworzenia „warstwy” DB, aby ułatwić przyszłe zmiany.
Matthew Goulart