Ar teisingas saugyklos šablono šablonas PHP?

Pratarmė: bandau naudoti atminties šabloną MVC architektūroje su reliacinėmis duomenų bazėmis.

Neseniai pradėjau mokytis TDD PHP, ir suprantu, kad mano duomenų bazė yra pernelyg glaudžiai susijusi su likusia mano paraiška. Aš perskaičiau apie saugyklas ir naudojant „IoC“ konteinerį „įterpti“ ją į savo valdiklius. Labai kietas dalykas. Bet dabar yra keletas praktinių klausimų apie saugojimo dizainą. Apsvarstykite šį pavyzdį.

 <?php class MyController { public function users() { $users = User::select('name, email, status') ->byCountry('Canada')->orderBy('name')->rows(); return View::make('users', array('users' => $users)); } } 

Mano požiūris į saugyklą nenoriu baigti šiuo:

specifikacijas , bet man atrodo, kad tai sumažina tik visą įrašų rinkinį (per „ IsSatisfiedBy() ), kuri akivaizdžiai turi rimtų našumo problemų, jei IsSatisfiedBy() duomenis iš duomenų bazės. 

Padėkite man

Akivaizdu, kad dirbdamas su saugyklomis reikia šiek tiek permąstyti. Ar kas nors gali apšviesti, kaip tai geriausiai tvarkyti?

235
23 апр. Jonathan nustatė balandžio 23 d 2013-04-23 21:50 '13, 9:50 val. 2013-04-23 21:50
@ 8 atsakymai

Maniau, kad norėčiau nulaužti atsakymą į mano klausimą. Toliau pateikiamas tik vienas iš būdų 1-3 klausimams išspręsti savo pradiniame klausime.

Atsakomybės apribojimas: aprašydamas modelius ar metodus, ne visada galiu naudoti tinkamas sąlygas. Atsiprašau už tai.

Tikslai:

  • Sukurkite pilną pagrindinio valdiklio pavyzdį, kad galėtumėte peržiūrėti ir redaguoti Users .
  • Visi kodai turi būti visiškai išbandyti ir prototipuoti.
  • Kontrolieriui nereikia žinoti, kur saugomi duomenys (tai reiškia, kad duomenys keičiami).
  • SQL įgyvendinimo rodymo pavyzdys (dažniausiai pasitaikantis).
  • Siekiant maksimalaus veikimo, valdikliai turėtų gauti tik būtinus duomenis - jokių papildomų laukų.
  • Įgyvendinant turėtų būti naudojamas tam tikras duomenų konverteris, siekiant supaprastinti plėtrą.
  • Įgyvendinimas turėtų turėti galimybę atlikti sudėtingas duomenų paieškas.

Sprendimas

Aš suskirstau sąveiką su nuolatiniu saugojimu (duomenų baze) į dvi kategorijas: R (skaityti) ir CUD (kurti, atnaujinti, ištrinti). Mano patirtis rodo, kad skaitymas tikrai sulėtina taikymą. Ir nors duomenų manipuliavimas (CUD) iš tikrųjų yra lėčiau, tai vyksta daug rečiau ir todėl yra mažiau neramus.

CUD (kurti, atnaujinti, ištrinti) yra paprasta. Tam reikės dirbti su tikrais modeliais , kurie vėliau perduodami mano Repositories saugoti. Pastaba: mano saugyklos vis tiek teiks skaitymo metodą, bet tik sukuria objektą, o ne rodyti. Daugiau apie tai vėliau.

R (skaityti) nėra lengva. Nėra modelių, tik vertybių objektų . Jei norite, naudokite masyvus. Šie objektai gali būti vienas modelis arba daugelio modelių mišinys. Jie nėra labai įdomūs, bet kaip jie kuriami. Aš naudoju tai, ką vadinu Query Objects .

Kodas:

Vartotojo modelis

Pradėkime nuo pagrindinio vartotojo modelio. Atkreipkite dėmesį, kad nėra jokio ORM plėtinio ar duomenų bazės. Tik grynas šlovės modelis. Pridėti savo getters, steigėjai, patikrinti, kas.

 class User { public $id; public $first_name; public $last_name; public $gender; public $email; public $password; } 

Saugyklos sąsaja

Prieš kuriant savo vartotojo saugyklą, noriu sukurti savo saugyklos sąsają. Tai nustatys „sutartį“, kurią saugyklos turėtų naudoti savo valdytojui. Atminkite, kad mano valdiklis nežino, kur saugomi duomenys.

Atkreipkite dėmesį, kad mano saugyklose bus tik trys iš šių metodų. save() metodas yra atsakingas už vartotojų kūrimą ir atnaujinimą, priklausomai nuo to, ar vartotojo objektas turi nustatytą identifikatorių.

 interface UserRepositoryInterface { public function find($id); public function save(User $user); public function remove(User $user); } 

SQL saugojimo diegimas

Dabar, norint sukurti savo sąsajos diegimą. Kaip jau minėta, mano pavyzdys bus SQL duomenų bazė. Atkreipkite dėmesį į duomenų žemėlapio naudojimą, kad išvengtumėte dubliuotų SQL užklausų.

 class SQLUserRepository implements UserRepositoryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function find($id) { // Find a record with the id = $id // from the 'users' table // and return it as a User object return $this->db->find($id, 'users', 'User'); } public function save(User $user) { // Insert or update the $user // in the 'users' table $this->db->save($user, 'users'); } public function remove(User $user) { // Remove the $user // from the 'users' table $this->db->remove($user, 'users'); } } 

Prašyti objekto sąsajos

Dabar su CUD (kurti, atnaujinti, ištrinti), rūpintis mūsų saugykla, mes galime sutelkti dėmesį į R (skaityti). Užklausos objektai paprasčiausiai yra kai kurių paieškos logika. Tai nėra užklausų kūrėjai. Išskyrus ją kaip mūsų saugyklą, galime pakeisti jo įgyvendinimą ir lengviau jį išbandyti. Prašymo objekto pavyzdys gali būti „ AllActiveUsersQuery arba „ AllActiveUsersQuery arba net „ MostCommonUserFirstNames .

Galbūt jūs galvojate: „Ar galiu tik sukurti metodus savo saugyklose šiems prašymams?“ Taip, bet būtent tai nepadarau:

  • Mano saugyklos yra skirtos dirbti su modelio objektais. Realaus pasaulio programoje, kodėl man reikia password lauko, jei ieškau visų mano naudotojų sąrašo?
  • Sandėliai dažnai būdingi modeliui, tačiau prašymuose dažnai yra daugiau nei vienas modelis. Taigi kokią saugyklą įdėjote į savo metodą?
  • Tai saugo mano saugyklas labai paprasta - ne ištinęs metodų klasė.
  • Visi prašymai dabar yra suskirstyti į savo klases.
  • Iš tiesų, šiuo metu saugyklos yra tiesiog abstrakčios mano duomenų bazės lygio.

Mano pavyzdyje sukursiu užklausos objektą, kuris bus ieškomas „AllUsers“. Čia yra sąsaja:

 interface AllUsersQueryInterface { public function fetch($fields); } 

Prašymo objekto įgyvendinimas

Čia mes galime vėl naudoti duomenų rodymo įrenginį, norėdami pagreitinti plėtrą. Atkreipkite dėmesį, kad leisiu vieną grąžintų duomenų rinkinio nustatymą - lauką. Kalbu, kiek aš noriu, manipuliuoti atliktu užklausimu. Atminkite, kad mano užklausos objektai nėra užklausų rinkėjai. Jie tiesiog atlieka konkretų užklausą. Vis dėlto, kadangi žinau, kad tai turėsiu daug, daugelyje skirtingų situacijų aš suteikiu galimybę nurodyti laukus. Aš niekada nenoriu grąžinti laukų, kuriems man nereikia!

 class AllUsersQuery implements AllUsersQueryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function fetch($fields) { return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows(); } } 

Prieš einant į duomenų valdytoją, noriu parodyti kitą pavyzdį, iliustruojantį, kaip tai galinga. Galbūt turiu ataskaitų teikimo mechanizmą ir jums reikia sukurti ataskaitą „ AllOverdueAccounts . Tai gali būti sunku su mano duomenų byla, ir aš galiu rašyti faktinę SQL šioje situacijoje. Jokių problemų, kaip atrodo šis užklausos objektas:

 class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function fetch() { return $this->db->query($this->sql())->rows(); } public function sql() { return "SELECT..."; } } 

Tai puikiai išlaiko mano logiką šiai ataskaitai vienoje klasėje ir ją lengva patikrinti. Aš galiu sunaikinti jį sielos gelmėse arba net visiškai išnaudoti kitą įgyvendinimą.

Valdiklis

Dabar linksma dalis - sudėti visus gabalus. Atkreipkite dėmesį, kad naudoju priklausomybės injekciją. Paprastai į konstruktorių yra įtrauktos priklausomybės, bet aš iš tikrųjų norėčiau jas tiesiogiai įvesti į savo valdiklio metodus (maršrutus). Tai sumažina valdiklio objekto grafiką, ir aš iš tikrųjų jį aiškiau aiškinu. Atkreipkite dėmesį, kad jei jums tai nepatinka, tiesiog naudokite tradicinį konstruktoriaus metodą.

 class UsersController { public function index(AllUsersQueryInterface $query) { // Fetch user data $users = $query->fetch(['first_name', 'last_name', 'email']); // Return view return Response::view('all_users.php', ['users' => $users]); } public function add() { return Response::view('add_user.php'); } public function insert(UserRepositoryInterface $repository) { // Create new user model $user = new User; $user->first_name = $_POST['first_name']; $user->last_name = $_POST['last_name']; $user->gender = $_POST['gender']; $user->email = $_POST['email']; // Save the new user $repository->save($user); // Return the id return Response::json(['id' => $user->id]); } public function view(SpecificUserQueryInterface $query, $id) { // Load user data if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) { return Response::notFound(); } // Return view return Response::view('view_user.php', ['user' => $user]); } public function edit(SpecificUserQueryInterface $query, $id) { // Load user data if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) { return Response::notFound(); } // Return view return Response::view('edit_user.php', ['user' => $user]); } public function update(UserRepositoryInterface $repository) { // Load user model if (!$user = $repository->find($id)) { return Response::notFound(); } // Update the user $user->first_name = $_POST['first_name']; $user->last_name = $_POST['last_name']; $user->gender = $_POST['gender']; $user->email = $_POST['email']; // Save the user $repository->save($user); // Return success return true; } public function delete(UserRepositoryInterface $repository) { // Load user model if (!$user = $repository->find($id)) { return Response::notFound(); } // Delete the user $repository->delete($user); // Return success return true; } } 

Galutinės mintys:

Svarbu pažymėti, kad keičiant (kuriu, atnaujinu ar ištrinu) objektus, dirbau su tikrais modelio objektais ir atlieku atkaklumą per savo saugyklas.

Tačiau, kai rodau (parenkant duomenis ir išsiunčiant juos į vaizdus), aš neveikiu modelio objektų, o senų senų vertybių objektų. Renku tik tuos laukus, kuriems man reikia, ir ji sukuriama taip, kad galėčiau maksimaliai padidinti duomenų paieškos rezultatus.

Mano saugyklos išlieka labai švarios, o vietoj to „netvarka“ yra suskirstyta į mano modelio užklausas.

Aš naudoju duomenų žemėlapį, kad padėtų vystytis, nes juokinga rašyti pasikartojančius SQL bendroms užduotims. Tačiau, jei reikia, galite visiškai parašyti SQL (sudėtingos užklausos, ataskaitos ir tt). O kai tai padarysite, jis gražiai išvalė klasę atitinkamu pavadinimu.

Aš tikrai norėčiau išgirsti, kaip laikosi mano požiūrio!


2015 m. Liepos mėn. Atnaujinimas:

Mane buvo paprašyta komentarų, kur baigiau viską. Na, ne taip toli. Sąžiningai, vis dar nemėgstu saugyklų. Manau, kad jie yra pernelyg svarbūs pagrindinei paieškai (ypač jei jau naudojate ORM) ir atsitiktinai dirbate su sudėtingesnėmis užklausomis.

Paprastai dirbau su „ORM ActiveRecord“, todėl dažniausiai tiesiog kreipiuosi į šiuos modelius visame programoje. Tačiau situacijose, kai turiu sudėtingesnes užklausas, naudosiu užklausos objektus, kad juos būtų galima naudoti pakartotinai. Taip pat turėčiau atkreipti dėmesį į tai, kad visuomet pridedu savo modelius prie savo metodų, todėl mano bandymuose jie tampa lengviau naikinami.

174
26 апр. Atsakyti į Jonathan balandžio 26 d 2013-04-26 16:45 '13, 16:45, 2013-04-26 16:45

Remdamiesi savo patirtimi, pateikiame keletą atsakymų į jūsų klausimus:

Klausimas. Kaip susidoroti su laukų, kurių mums nereikia, sugrįžimu?

A: Mano patirtis rodo, kad jis tikrai dirba su visais subjektais, palyginti su specialiais prašymais.

Visas subjektas yra kažkas panašaus į User objektą. Ji turi savybių ir metodus ir tt Jis yra pirmos klasės pilietis jūsų kodo bazėje.

Ad-hoc prašymas pateikia tam tikrus duomenis, bet nieko nežinome. Kadangi duomenys perduodami aplink programą, tai daroma be konteksto. Ar šis User ? User turintis tam tikrą Order informaciją? Mes tikrai nežinome.

Norėčiau dirbti su visais objektais.

Jūs esate teisus, kad dažnai grąžinsite duomenis, kurių nenaudosite, tačiau galite ją išspręsti įvairiais būdais:

border=0
  • Agresyviai talpinami objektai, kad mokėtumėte tik už vieną kartą skaitytą kainą iš duomenų bazės.
  • Praleiskite daugiau laiko savo objektų modeliavimui, kad jie turėtų gerų skirtumų tarp jų. (Apsvarstykite didelės įmonės suskirstymą į du mažus objektus ir tt).
  • Apsvarstykite kelias subjektų versijas. Jūs galite turėti User galiniam galui ir galbūt „ UserSmall už AJAX skambučius. Vienas iš jų gali turėti 10 savybių ir vienas turi 3 savybes.

Trūkumai dirbant su ad hoc prašymais:

  • Todėl daugeliui užklausų iš esmės gaunate tuos pačius duomenis. Pavyzdžiui, naudodami User daugeliui skambučių jūs iš esmės rašote tą patį select * . Vienas skambutis gaus 8 iš 10 laukų, vienas gaus 5 iš 10, vienas gaus 7 iš 10. Kodėl gi ne visi pakeisti vienu skambučiu, kuris gauna 10 iš 10? Priežastis tai yra bloga yra tai, kad jis yra nužudymas dėl pakartotinio veiksnio / testo / išdėstymo.
  • Laikui bėgant labai sunku kalbėti apie jūsų kodą. Vietoj teiginių, pvz., „Kodėl User taip lėtas?“ galų gale stebite vienkartinius prašymus, todėl klaidų taisymai yra maži ir lokalizuoti.
  • Labai sunku pakeisti pagrindinę technologiją. Jei dabar viską išsaugosite „MySQL“ ir norite persijungti į „MongoDB“, yra daug sunkiau pakeisti 100 specialių skambučių nei keli subjektai.

Klausimas: Mano saugykloje bus per daug metodų.

A: Nematau nieko panašaus, bet konsoliduoti skambučius. Jūsų saugykloje vadinamas metodas tikrai atitinka jūsų programos funkcijas. Kuo daugiau funkcijų, tuo daugiau skambučių susieti su konkrečiais duomenimis. Galite grįžti į funkcijas ir pabandyti sujungti panašius skambučius į vieną.

Sunkumai dienos pabaigoje turi egzistuoti kažkur. Naudojant saugyklų šabloną, mes perkėlome jį į saugyklos sąsają, o ne sukūrėme saugomų procedūrų krūva.

Kartais aš turiu pasakyti sau: „Na, tai turėjo duoti jums kažkur! Jokių sidabro kulkų“.

42
24 апр. atsakymas pateikiamas ryan1234 24 balandžio. 2013-04-24 01:33 '13 ne 1:33 2013-04-24 01:33

Naudoju šias sąsajas:

  • Repository - įkelia, įterpia, atnaujina ir ištrina objektus
  • Selector - suranda objektus, pagrįstus filtruose esančiais filtrais
  • Filter - apima filtravimo logiką

Mano Repository - duomenų bazės agnostikas; iš tiesų, tai nenurodo pastovumo; tai gali būti bet kas: SQL duomenų bazė, xml failas, nuotolinė paslauga, svetima iš kosmoso ir kt. Paieškos talpyklose Repository sukuria Selector , kurį galima filtruoti, apriboti, surūšiuoti ir suskaičiuoti. Galų gale selektorius pasirenka vieną ar daugiau Entities iš konstantos.

Štai pavyzdinis kodas:

13
18 авг. Constantin Galbenu atsakymas 18 rug . 2016-08-18 12:45 '16 at 12:45 2016-08-18 12:45

Aš šiek tiek pridėsiu apie tai, nes šiuo metu stengiuosi jį išsiaiškinti.

# 1 ir 2

Tai puiki vieta, kad ORM galėtų atlikti sunkų keltuvą. Jei naudojate modelį, įgyvendinantį tam tikrą ORM, galite paprasčiausiai naudoti savo metodus šiems dalykams rūpintis. Kurkite pasirinktines OrderBy funkcijas, kurios, jei reikia, įgyvendina „Eloquent“ metodus. Naudojant „Eloquent“, pavyzdžiui:

 class DbUserRepository implements UserRepositoryInterface { public function findAll() { return User::all(); } public function get(Array $columns) { return User::select($columns); } 

Tai, ko ieškote, yra ORM. Nėra jokios priežasties, kodėl jūsų saugykla negali būti pagrįsta vienu dalyku. Tam reikia vartotojo iškalbingumo, bet aš asmeniškai nemanau, kad tai yra problema.