Autor Zpráva
Ecrazit
Profil *
Zdravím,

v souboru connect.php se připojím k databázi takto:

$mysqli = new mysqli($host, $user, $pass, $db);

Potom mám svojí vlastní třídu, určenou k registraci uživatelů a zápisu do databáze. Třída je v samostaném souboru a connect i třídu includuji do souboru s formulářem proregistraci. Když ale ve třídě chci spustit nějaký dotaz na databázi, například ověření, zda-li zadaný nick již existuje tímto stylem:

private function userExist() {
    $sql = $mysqli->query('SELECT nick FROM users WHERE nick = "' . $this->user . '"');
    return $sql->num_rows;
}

... tak mi to hlásí
Notice: Undefined variable: mysqli in D:\Work\www\registrace\oop\class.register.php on line 85
Fatal error: Call to a member function query() on a non-object in D:\Work\www\registrace\oop\class.register.php on line 85

Jak mám tedy udělat, aby bylo připojení aktivní i ve vlastních třídách?
Alphard
Profil
Možností je více, od statických registrů (mírně hezčí global) po Dependency injection.
Ve vašem případě by teď šlo použít třeba metodu

public function setDbConnection($dbConnection)
{
  $this->db = $dbConnection;
}
midlan
Profil
private function userExist() {
    $sql = $GLOBALS['mysqli']->query('SELECT nick FROM users WHERE nick = "' . $this->user . '"');
    return $sql->num_rows;
}
nebo
private function userExist() {
    global $mysqli;
    $sql = $mysqli->query('SELECT nick FROM users WHERE nick = "' . $this->user . '"');
    return $sql->num_rows;
}

Ecrazit:
connect i třídu includuji
Mimochodem raději bych používal require.
Ecrazit
Profil *
Alphard:
Bohužel jsem tyhle způsoby moc nepochopil. S OOP teprve začínám a učím se to tak, že do OOP přepisuji nějaké své starší scripty.

midlan:
Takhle to funguje, ale co se týče globals, tak pokud vím, není zrovna nejšťastnější způsob.
Alphard
Profil
[#3] midlan
Člověk chce poradit něco pěkného a vy takhle :-)

[#4] Ecrazit
Přepisování starých věcí je problematické, OOP není jen o syntaxi. [#3] je důkaz, to není OOP ani v nejmenším.
Cílem mnou navrhované metody je předání třídě odkazu (reference) na jinou třídu, se kterou může pracovat. Script, který žádá něco od dané třídy, jí předá odkaz na databázi, která se má použít.
Ecrazit
Profil *
Ale v takovém případě stejně budu muset použít pole $GLOBALS, abych proměnnou s instancí třídy mysqli načetl do metody, která je uvnitř mé třídy, nebo ne?

$mysqli = new mysqli($host, $user, $pass, $db);

class Register {
//.....
      public $mysqli;
      
      public function regUser(/*parametry*/) {
                $this->dbConnect($GLOBALS['mysqli']);
       }
      public function dbConnect($db) {
        $this->mysqli = $db;
    }
}
midlan
Profil
Ecrazit:
Tak to udělej takhle:
$mysqli = new mysqli($host, $user, $pass, $db);
 
class Register {
//.....
      public $mysqli;
      
      public function regUser(/*parametry +*/, $db) {
                $this->dbConnect($db);
       }
      public function dbConnect($db) {
        $this->mysqli = $db;
    }
}
Majkl578
Profil
[#6] Ecrazit:
Chybný návrh.

[#7] midlan:
Taktéž chybný návrh.


Závislost by měla být předávána:
a) ve chvíli potřeby (tzn. parametrem metody) a neměla by se starat o věci jako připojení před použitím (v případě [#7] dbConnect), to si má služba řešit sama,
b) konstruktorem, kdy pak bude dostupna celé třídě, zároveň ale také platí, že se nemá starat o věci, do které jí nic není (lazy load, připojení k db on-demand).
c) setterem celé třídě (jako uvědl Alphard v [#2]), to se nicméně nehodí pro služby, které jsou nutné.

Do OOP rozhodně nepatří $GLOBALS, global ani další podobné prasečiny.
_construct
Profil *
možno lepšia cesta je extends...
class Register extends mysqli {

    public function __construct($host, $user, $pass, $db) {
        parent::__construct($host, $user, $pass, $db);
    }
    
    private function userExist() {
        $result = $this->query('SELECT nick FROM users WHERE nick = "' . $this->user . '"');
        return $result->num_rows;
    }
    
    private function __destruct(){
        $result->close();
        $mysqli->close();
    }
}
Ugo
Profil
Majkl578:
na global nevidím rozhodně nic špatného, používá se skrze celý codeigniter a především většina jazyků má globální úložiště normálně dostupné, php ne a tak je tu global, otázkou je jen použití, ale tvrdit že je to prasečina je blbost, co asi vypíše tenhle kód..

test = "ddd"
def a():
    print test
    
a()

mě se nejvíc líbí extends, ale má mouchy kdyby se chtělo předělávat, ale DI to taky nijak neošetřuje, to říká jen chceš-li mě předělat, máš možnost navíc, třídu se stejným interface jaký jsem měla a jelikož to nebejvá reálné a žádoucí (když už nahrazuji tak za něco jiného), tak to stejně znamená všechny parametry změnit, respektive změnit službu, ale to záleží na další implementaci, stejně jako s global .. záleží na použití a ne na slůvku.
Alphard
Profil
[#9] _construct
Majkl tě seřve, modelové třídě nemůžeš předat hesla k databázi.

[#10] Ugo
Výjmečně s Majklem zcela souhlasím, global je prasečina. CI neznám, ale že něco používá, neznamená, že je to správně.
Majkl578
Profil
_construct:
Myslím si, že ani extends není správná cesta. Hlavní problém tu zřejmě je, že se míchá modelová část aplikace přímo do databázového připojení (tj. to extends). Tím se bytečně zesložiťuje třída samotná a jako bonus porušuje SRP, jelikož modelová třída data - modelová třída by rozhodně neměla umožňovat položit jakýkoliv dotaz (takto tuto funkčnost nabízí ve svém veřejném rozhraní).

Alphard:
Majkl tě seřve, modelové třídě nemůžeš předat hesla k databázi.
Vzhledem k tomu, že databázové spojení by mělo být explicitně předáváno, nemusíme takovou otázku ani řešit. Spojení k databázi se inicializuje někde jinde a těmto modelovým třídám je už jen předáno. :)

Ugo:
co asi vypíše tenhle kód
Položme si jinou otázku: Je takový kód transparentní?


Pokud bychom tedy chtěli, aby model využíval databázové připojení (uvažujme PDO) a zároveň působil transparentně (neskrýval závislosti):
namespace Model;

class Some /*extends \Nette\Object*/
{
    /** @var \PDO */
    private $connection;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
    }
    
    /**
     * @return array
     */
    public function getSomething()
    {
        return $this->connection->query('SELECT ... FROM ...')->fetchAll();
    }
}

Použití by pak bylo snadné:
$pdo = new \PDO('mysql:dbname=somedb;host=127.0.0.1', 'someuser', 'somepwd');

// ...

$someModel = new \Model\Some($pdo);
$someModel->getSomething();
Ugo
Profil
já teda nevím v čem, chápu to v některých případech, ale odsuzovat globální úložiště jako celek slovy "drogy jsou špatné, protože jsou špatné, áno?" není šastné, v javascriptu to většině ani nepřijde, stejně tak při používání statických tříd nebo globálního kontejneru, všechno je to to samé. já dlouho přemýšlel jestli u sebe to global nechat, nakonec jsem dospěl k názoru že v jádru programu kam se neleze to nikomu nepřekáží a je malinko rychlejší a přehlednější než DI, samozřejmě oboje má výhody a nevýhody které by daly zabrat jen seznamem na 2 odstavce a bez použití je jeden často neodhalí. S příkladem o CI jsem nechtěl říci že to co je tam je dobře (i když funguje to, ergo je to dobře), ale to že je to velice dobrej a celkem rozšířenej FW a nikomu to že je postavenej na globálních proměnejch nevadí a nikomu to ani vadit nebude, protože se k tomu nedostane, je mu jedno jak to funguje, hlavně že to funguje a funguje to dobře, třeba nette se dalo cestou opačnou (striktně dodržovat všerůzný pravidla) a výsledek je jasnej, výhody v jádru to uživateli nepřináší, ale je to pomalejší. No zas sem zaběh hodně mimo téma, já sem prostě z těhle tvrzení vždycky nadzvedlej když jsou použity tak globálně, v tomhle případě s tím souhlasím že je lepší parametr, ale není to tak vždy.

Majkl578:
transparentní je a to velmi, dokud se nerozšíří a definice proměnný se nedostane někam do 10. includovaného souboru tak je to dobré, ale stejným neduhem trpí vše když se to využije špatně, jestliže tvé $pdo nadefinuju v souboru kde ho neočekávám a je to 10. includovaný soubor v řadě, tak je to stejný neduh, rozdl mezi parametrem a globální proměnou totiž bude znát až při razantnější úpravě a nebo při nahození třídy závislé na global do jiného projektu.

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

je víceméně stejný jako

function __construct() {
  global $pdo;
  $this->connection = $pdo}

záleží čistě na použití, zde by to šikovné nebylo, ale jestliže mám jádro aplikace kde vím že služba je v proměnné $service a vím, že potřebuji právě tuhle službu, ať už je jakákoliv a měnit se nebude, jádro prostě funguje a nesahá se do něj, tak je pro mě výhodnější globální úložiště a nazveme jej jak chceme, statická třída, kontejner, globální proměnná (global)


a ještě zpátky k javascriptu, co třeba takový objekt "document" nebo window, jquery atp. přistupuji k nim odevšad a přitom je nikde nepředávám, zrovna tak $_GET, $_POST .... rozdíl nevidím, globální proměnná jako každá jiná. a taky když jsem zavtíal k pythonu, tak v php ti obecně chybí v objektech zásadní věc pro plné DI a to první parametr jenž se předává automaticky ale k funkci se píše a obsahuje $this
Alphard
Profil
[#13] Ugo
Taky občas v javascriptu některé knihovny nefungují dohromady, prože se vzájemně ovlivňují. Myslím, že je všude doporučovaná definice var myVariable = 8; a ne definice do globálního kontextu.

Jeden z hlavních problémů vidím ve vzájemném ovlivňování. Než přišly namespaces, často jsem narážel na problémy, jak pojmovávat třídy jako Image, User, ... které byly potřeba často a nebylo možné je duplikovat.

jestliže mám jádro aplikace kde vím že služba je v proměnné $service
A pak se objeví nějaký tvůrce pluginu, který to neví, a definuje global $service...

co třeba takový objekt "document" nebo window
zrovna tak $_GET, $_POST
Já v tom trochu rozdíl cítím, tohle jsou nativní (řekněme rezervované názvy) v daném jazyce a žádný programátor by do toho neměl šahat. U vlastních proměnných naopak naprosto nelze zajistit, aby ve dvou různých projektech nebyly stejně pojmenované kontejnery.

Já tvému názoru rozumím, dlouho jsem v Nette používal Environment, zřejmě ho používal i Majkl, protože to bylo normální řešení, ale vývoj jde dál a techniky se mění směrem k robustnosti, spolehlivosti a znovupoužitelnosti. Výpočetní výkon "ztracený" na složitějších konstrukcích je prostě cena.

A ono je pak i jednodušší převzít po někom vývoj projektu. Vypsat si, nebo podívat se do configu, do obsahuje kontejner a vyvíjet dál. U tajemných globálních proměnných a netransparentních vazeb mezi jednotlivými částmi programu člověk ztráví půl dne zjišťováním, co jak funguje. Mám to vyzkoušené :-)

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: