Autor Zpráva
Suta
Profil
Jelikož začínám s postupným průzkumem OOP v php, rád bych se zeptal, zda-li je začlenění běžně používaných funkcí do třídy dobrý či špatný nápad. Konkrétněji:

Aktuálně používaný styl zápisu:
function codeClean() {};
function imageResize() {};
// jiné běžně používané funkce

Nahradit za:
Class myCoreLibrary {
    public function codeClean() {};
    public function imageResize() {};
    // jiné běžně používané funkce
}

$myCoreLibrary = new myCoreLibrary();
$myCoreLibrary->codeClean();

Přemýšlím totiž nad přehledností mého současného kódu, kdy moje aktuální vlastní knihovna obsahuje několik set funkcí. Převod do objektového modelu by mi přinesl zpřehlednění např. v tom, že můžu jednotlivé metody rozčlenit podle jejich zaměření, tedy můžu vytvořit objekty (knihovny) typu:
$myCoreLibrary = new myCoreLibrary();
$myMysqlLibrary = new myMysqlLibrary();
$myValidateLibrary = new myValidateLibrary();

$myValidateLibrary->validateEmail($email);

Poznámka: uvedené názvy metod jsou vytvořeny pouze jako ukázka.

Druhý dotaz pak směřuje k tomu, zda-li použití druhého způsobu (vytvoření instance a používání metod) je pro server náročnější, než-li definice a volání klasických funkcí. Problém totiž můžu vyřešit i prostým přejmenováním současných funkcí:
function validateLibrary_validateEmail();
function imgLibrary_imageResize();

Předem díky za jakoukoliv radu, jak říkám, s OOP v php se začínám seznamovat.
tiso
Profil
Začnem od konca:
1) s objektami je spojená väčšia réžia ako s obyčajnými funkciami.
2) existujú aj statické triedy, v niektorých prípadoch nepotrebuješ vytvárať nový objekt, stačí si zavolať konkrétnu metódu.
3) ďalšia hierarchická úroveň usporiadania je menný priestor (namespace)
Suta
Profil
Reaguji na tisa:
Můžeš prosím blíže popsat body 2 a 3?
tiso
Profil
2) http://php.net/manual/en/language.oop5.static.php, prípadne http://studium.vos-sps-jicin.cz/oop/index.php?page=staticke a http://www.pcforum.sk/staticke-metody-v-oop-a-kedy-ich-pouzit-vt105702.html
3) http://php.net/manual/en/language.namespaces.php, prípadne http://www.mirin.cz/blog/jmenne-prostory-namespaces-v-php-53
Suta
Profil
Díky, prostuduju.
Suta
Profil
Reaguji na tisa:
Všechny odkazy jsem pečlivě prostudoval, díky informaci o statických metodách jsem se dostal i na jiné tutoriály.

Používání metod zapouzdřených v třídě bez vytváření instance je skvělá věc.
Co mi však ještě chybí je vytváření dalších "podobjektů". Tedy něco na způsob:
MyLibrary::Core::Log::logMessage();

Např. v javascriptu by struktura vypadala následovně:
var MyLibrary = {
    Core: {
        Log: {
            logMessage: function(message) {
                console.log(message);
            }
        },
        nextCoreObjectOrMethod: function(){}
    }
};

Podle dosud načerpaných znalostí nyní dokážu zavolat pouze statickou metodu třídy.
class Commons {
    public static function createMysqlConnection() {
    }
}
class Log {
    public static function logMessage($message) {
    }
}

Commons::createMysqlConnection(); // vytvoří připojení k databázi
Log::logMessage("Logovací zpráva"); // zaloguje zprávu

Nevím však, jak docílit toho, když bych chtěl, aby třída Log byla dostupná pod třídou Commons, tedy volání:
Commons::Log::logMessage();

Předem díky.
martin1312
Profil
Suta:
neviem, či presne rozumiem tomu, čo chceš, ale skús si pozrieť dedičnosť.
Suta
Profil
martin1312:
O dědičnosti vím, dosud jsme se však bavili o volání metod bez toho, aniž bych musel vytvářet instanci objektu.

Jde tedy nějakým způsobem docílit volání Commons::Log::logMessage(); ?
tiso
Profil
Suta: „Jde tedy nějakým způsobem docílit volání...
Nejde, ani to v PHP nedáva zmysel. Ale podobným mechanizmom sú práve tie menné priestory (namespace). - Že voláš metódu zo svojho menného priestoru:
\Commons\Log::logMessage();
Nie je to však to isté.
Suta
Profil
Reaguji na tisa:
Chápu, díky za vysvětlení.

Tady je jeden z prvních pokusů (převod do OOP). Pravděpodobně se nikomu nebude chtít kód studovat, za případné letmé proletění a varování před špatným stylem návrhu však budu vděčný.

<?php

session_start();

define("MYSQL_SERVER", "localhost"); // případně "192.168.1.3"
define("MYSQL_USER", "root");
define("MYSQL_PASSWORD", "");
define("MYSQL_DB", "zav_system_3");

class Commons {

    private static $mysqlConnection;

    public static function createMysqlConnection() {

        /**
         * vytvoří připojení k databázi
         */

        // připojení k serveru
        self::$mysqlConnection = @mysql_connect(MYSQL_SERVER,MYSQL_USER,MYSQL_PASSWORD);

        // výběr databáze
        mysql_select_db(MYSQL_DB);

        // nastavení kódování
        if(function_exists("mysql_set_charset")) mysql_set_charset("utf8");
        else mysql_query("SET NAMES utf8");

        // nastavení časového pásma pro MySQL server
        mysql_query("SET time_zone = 'Europe/Bratislava'");
    }
    
    public static function getMysqlConnection() {
        
        // vrátí spojení s databází
        return self::$mysqlConnection;
    }

    public static function getMysqlConnectionStatus() {

        // informace, zda bylo spojení vytvořeno úspěšně
        return self::$mysqlConnection === FALSE ? "error" : "success";
    }

    public static function destroyAllSessions() {

        /**
         * Odstraní veškeré session relace
         */

        // Initialize the session.
        // If you are using session_name("something"), don't forget it now!
            if(!isset($_SESSION)) {
                session_start();
            }

        // Unset all of the session variables.
            $_SESSION = array();

        // If it's desired to kill the session, also delete the session cookie.
        // Note: This will destroy the session, and not just the session data!
            if (ini_get("session.use_cookies")) {
                $params = session_get_cookie_params();
                setcookie(session_name(), '', time() - 42000,
                    $params["path"], $params["domain"],
                    $params["secure"], $params["httponly"]
                );
            }

        // Finally, destroy the session.
            session_destroy();
    }

    public static function get_GET($string) {
        // http://blog.zdenekvecera.cz/item/jak-na-to-sql-injection-magic_quotes_gpc-addslashes-a-stripslashes

        /**
         * Metoda ošetří data předaná metodou GET
         */

            $string = isset($_GET[$string]) ? $_GET[$string] : "";

            if($string === 0) return 0;

            return self::codeClean($string);
    }
    
    public static function get_POST($string) {
        // http://blog.zdenekvecera.cz/item/jak-na-to-sql-injection-magic_quotes_gpc-addslashes-a-stripslashes

        /**
         * Metoda ošetří data předaná metodou POST
         */

            $string = isset($_POST[$string]) ? $_POST[$string] : "";

            if($string === 0) return 0;

            return self::codeClean($string);
    }
    
    public static function get_POST_DATA($string) {
        // http://blog.zdenekvecera.cz/item/jak-na-to-sql-injection-magic_quotes_gpc-addslashes-a-stripslashes
        /**
         * Metoda ošetří data předaná metodou POST a již uložená v objektu $POST_DATA
         */

            global $Zav;

            $string = isset($Zav->POST_DATA->{$string}) ? $Zav->POST_DATA->{$string} : "";

            if($string === 0) return 0;

            return self::codeClean($string);
    }

    public static function codeClean($var) {

        if($var == "") return "";

        if (is_array($var)) {
            foreach($var as $key => $val) {
                $output[$key] = self::codeClean($val);
            }
        }
        else {
            //$var = strip_tags(trim($var));
            $var = trim($var);
            if (get_magic_quotes_gpc()) {
                $var = stripslashes($var);
            }
            $var = mysql_real_escape_string($var);
        }

        return $var;
    }

    public static function getMicrotime() {
        return round(microtime(true) * 1000);
    }
}



class Log{

    private static $info = array();

    public static function reset() {
        self::$info = array();
    }

    public static function logMessage($string) {
        self::$info[] = Commons::getMicrotime() . ": " . $string;
    }

    public static function getLogResult() {
        return self::$info;
    }
}

class Zav {

    public $ACTION_TYPE;
    public $POST_DATA;
    public $RETURN_ARRAY = array();

    public $NOW;

    public $isConnectionSuccessful = false;
    public $rootPath;                                       // cesta ke kořenu webu
    public $isServer;                                       // informace o umístění stránek (server vs localhost

    public function __construct() {

        //server
        //$jsonStrData = isset($_POST['data']) ? stripslashes ($_POST['data']) : "";
        //localhost
        // viz  POST data a metoda json_decode
        $jsonStrData = isset($_POST['data']) ? $_POST['data'] : "";

        $this->ACTION_TYPE = Commons::get_POST("actionType");
        $this->POST_DATA = json_decode($jsonStrData); // převod na objekt, uvedením true v druhém parametru by proběhl převod na array
        $this->RETURN_ARRAY["data"] = array();

        $this->NOW = strtotime("now");

        // cesta ke kořenu webu
        $this->rootPath = $_SERVER['DOCUMENT_ROOT']; // C:/wamp/www/

        // informace o umístění stránek (server vs localhost)
        $this->isServer = strtoupper(SubStr($this->rootPath,0,3)) == "C:/" ? false : true;
    }
}


Commons::createMysqlConnection();

$Zav = new Zav();

Log::logMessage("Připojení k databázi - " . Commons::getMysqlConnectionStatus());

$Zav->RETURN_ARRAY["textStatus"] = "testStatus";
$Zav->RETURN_ARRAY["data"]["postData"] = $Zav->POST_DATA;
$Zav->RETURN_ARRAY["data"]["log"] = Log::getLogResult();

echo json_encode($Zav->RETURN_ARRAY);
exit();

?>
span
Profil *
Je zbytočne vytvárať objekt ak z neho bude nakoniec použitá len jedna metóda.
K metódam tried sa dá pristupovať ako k statickým aj v obyčajných triedach nie len v statických (scope operator ::).
Hierarchicky sa dajú písať aj rozšírenia tried (extends).
A veľmi užitočné metódy sú konstruktory (_costruct()).
Konštanty sa dajú deklarovať aj v rámci tried (const RESET = 'Reset';).
Someone
Profil
Suta:
Když už je to celé takto objektově, tak proč místo mysql_* nepoužiješ objekt Mysqli ?
mysql_* funkce jsou od PHP 5.5.0 DEPRECATED a budou se odstraňovat.
Tori
Profil
Nejvíc mi padlo do oka:
Nelíbí se mi zabalení všeho do jedné třídy jen proto, abyste to mohl volat přes tuto třídu - na tohle jsou vhodnější jmenné prostory: Zav\Common\Log::logMessage('text'); Uvedený příklad v JS by se tak zapsal hlavně proto, že v JavaScriptu jmenné prostory neexistují (pokud vím) a simulují se například tímhle způsobem. V PHP ale takový návrh porušuje princip jediné zodpovědnosti (tj. že třída / metoda / funkce se stará plně a výhradně o jednu věc). U vás se to projevilo např. vzájemně nesouvisejícími metodami třídy Common, anebo používání databázového escapování řetězce v kontextu čtení dat z $_POST.

S tím souvisí další důležitá věc v OOP - zapouzdření. Metody by měly být takové černé skříňky; možná dostanou nějaký vstup, něco udělají (okolní kód nepotřebuje vědět jakým způsobem) a možná vrátí nějakou hodnotu. Rozhodně by neměly spoléhat na existenci nějakých globálních proměnných (používání global) nebo konstant (mysql_connect - přihlašovací údaje předávejte databázové třídě transparentně, např. jako parametr konstruktoru).
Suta
Profil
Reaguji na uživatele span:
Hierarchicky sa dajú písať aj rozšírenia tried (extends).
I v těch třídách, které nebudu používat pro vytváření instancí, ale budu pouze volat jejich metody?

Reaguji na uživatele Someone:
Když už je to celé takto objektově, tak proč místo mysql_* nepoužiješ objekt Mysqli ?
Ano, to je pravda, přechod na mysqli plánuji.

Reaguji na uživatele Tori:
na tohle jsou vhodnější jmenné prostory: Zav\Common\Log::logMessage('text');
Když já jsem jmenným prostorům předevčírem věnoval několik hodin, a vůbec se mi nepodařilo tuto funkcionalitu rozchodit. Můžeš mi, prosím, napsat úplně jednoduchý příklad definice tříd a definici jmenného(jmenných) prostorů tak, abych docílit např. tebou popsaného volání? (Zav\Common\Log::logMessage('text');)

Rozhodně by neměly spoléhat na existenci nějakých globálních proměnných (používání global) nebo konstant (mysql_connect - přihlašovací údaje předávejte databázové třídě transparentně, např. jako parametr konstruktoru).
S první částí souhlasím (v kódu skutečně používám v metodě základní třídy globální proměnnou, přepracuji..). S tou druhou si však již nejsem jistý.. Vždy jsem byl zvyklý definovat připojení k databázi zcela nahoře v kódu, při použití na jiném projektu vždy stačilo přepsat jinými údaji.. Opravdu si myslíš, že je chybné používat pro připojení k databázi údaje uložené pomocí define?

Jde mi totiž i o to, že bych byl rád, kdyby třída Commons obsahovala základní důležité funkce (metody) a proměnné (vlastnosti). A např. spojení s databází nebudu vytvářet vždy.. Pouze tehdy, když budu vědět, že budu později s databází pracovat.. Tudíž např. volání Commons:createMysqlConnection() uskutečním právě pouze v této situaci. A abych nemusel zadávat jako parametry údaje pro připojení k databázi, použije si je třída sama z definované konstanty.
span
Profil *
[#14] Suta
<?php
class Common {
    const VERSION = '0.0.1';
    public $MemoryLimit = 1048576;
    public $PostMaxSize = 1048576;
    public $UploadMaxFilesize = 1048576;
    
    public function __construct() {
        $this->UploadMaxFilesize = (integer)ini_get('upload_max_filesize')*1048576;
        $this->PostMaxSize = (integer)ini_get('post_max_size')*1048576;
        $this->MemoryLimit = (integer)ini_get('memory_limit')*1048576;
        
    }
}

class Ui extends Common {
    const VERSION = '0.0.1';
    
    public function __construct() {
        Common::__construct();
        Ui::Render();
    }
    
    public function Render() {
        ob_start();
        print ' Upload Max Filesize: '.$this->UploadMaxFilesize;
        print ' Post Max Size: '.$this->PostMaxSize;
        print ' Memory Limit: '.$this->MemoryLimit;
        print ' Common version: '.Common::VERSION;
        print ' Ui version: '.Ui::VERSION;
        ob_end_flush();
    }
}

$Ui = new Ui();
?>
Tori
Profil
Suta:
Opravdu si myslíš, že je chybné používat pro připojení k databázi údaje uložené pomocí define?
Jak je ukládáte, to je (v tomhle kontextu) jedno. Šlo mi o odstranění závislosti: pokud změníte způsob ukládání přístupových údajů, musíte v [#10] změnit i DB třídu - to není dobré. Třída má být nezávislá na svém okolí. Porovnejte tyhle dva případy:
class DB {
    public static function createMysqlConnection() {
        self::$mysqlConnection = @mysql_connect(MYSQL_SERVER,MYSQL_USER,MYSQL_PASSWORD);
        mysql_select_db(MYSQL_DB);

        if(function_exists("mysql_set_charset")) mysql_set_charset("utf8");
        else mysql_query("SET NAMES utf8");

        mysql_query("SET time_zone = 'Europe/Bratislava'");
    }
}
vs.
class DB {
    protected $options = array();
    protected $connection;

    public function __construct(array $opts) {
        // nějaké výchozí hodnoty:
        if (!isset($opts['charset'])) $opts['charset'] = 'utf8';

        $this->options = $opts;
    }

    protected function connect() {
        $this->connection = mysql_connect($this->options['server'], $this->options['user'], $this->options['password']);
        if ($this->connection === false)
            throw new RuntimeException("Nelze připojit k databázi. Popis chyby: " . mysql_error());
        // U ostatních volání mysql_* by taky bylo ošetření chyb. Teď ho ale vynechávám, zjednodušený příklad stačí.
        mysql_select_db($this->options['database']);

        if (function_exists("mysql_set_charset")) mysql_set_charset($this->options['charset']);
        else mysql_query("SET NAMES ".$this->options['charset']);

        mysql_query("SET time_zone = '" . $this->options['timezone'] . "'");
    }
}
Co když se rozhodnu třídu DB použít pro jiný web, kde nepoužiju konstanty, ale budu přihlaš.údaje číst třeba z .ini souboru? S každou další třídou, která by tím prvním způsobem četla nějak odněkud údaje, se rychle zmenšuje vaše šance udržet si v těch všech závislostech přehled.

A např. spojení s databází nebudu vytvářet vždy.. Pouze tehdy, když budu vědět, že budu později s databází pracovat
Další možnost je nechat to úplně na třídě DB. Připojení se vytvoří automaticky při prvním volání metody query:
// ... pokračování předchozího příkladu
public function query($sql) {
    if (!$this->connection) $this->connect();

    // provedení SQL dotazu ...
}
Suta
Profil
Reaguji na uživatele Tori:
Ano, v obou případech máte pravdu (dobrá, budeme si vykat), vámi uvedený způsob je lepší. Z příkladu se jistě poučím a vlastní kód poupravím. V mém konkrétním případě jde však spíše o to, že knihovnu používám pouze já sám a nepředpokládám, že bych kdy považoval za nutné měnit zaběhlý způsob definování přístupových údajů přes define (používám stále stejně již několik let ve všech projektech, které jsem kdy dělal).
Tori
Profil
nepředpokládám, že bych kdy považoval za nutné měnit zaběhlý způsob definování přístupových údajů přes define
Co když budeš někdy potřebovat pracovat se dvěma databázemi paralelně?

Já jsem zvyklá vykat, ale nečekám to od ostatních; takže si klidně tykejme, jestli ti to víc vyhovuje. :)
Suta
Profil
Reaguji na uživatele Tori:
Dobře. Můžu ještě poprosit o pomoc s definicí jmenných prostorů? (citace z [#14])

Suta:
„na tohle jsou vhodnější jmenné prostory: Zav\Common\Log::logMessage('text');“
Když já jsem jmenným prostorům předevčírem věnoval několik hodin, a vůbec se mi nepodařilo tuto funkcionalitu rozchodit. Můžeš mi, prosím, napsat úplně jednoduchý příklad definice tříd a definici jmenného(jmenných) prostorů tak, abych docílit např. tebou popsaného volání? (Zav\Common\Log::logMessage('text');)
Tori
Profil
Soubor Log.php:
<?php
namespace Zav\Commons;

class Log {
    public static function message() { }
}

Volání ve skriptu bez namespace:

Zav\Commons\Log::message();
// nebo
\Zav\Commons\Log::message();
Funguje obojí, ale s lomítkem na začátku je to trochu méně čitelné.

Možnosti volání ve skriptu s namespace:
<?php
namespace NějakýNS;

\Zav\Commons\Log::message();

// bez úvodního lomítka nefunguje, přeložilo by se na NějakýNS\Zav\Commons\Log
Zav\Commons\Log::message();

<?php
namespace NějakýJiný;
use \Zav\Commons;

Commons\Log::message();

<?php
namespace NějakýJiný;
use \Zav\Commons\Log;

Log::message();

<?php
namespace Zav\Commons;
use \A\B\CokolivJiného;

Log::message();

// tohle nebude fungovat, přeložilo by se na Zav\Commons\Commons\Log
Commons\Log::message();

<?php
namespace Zav\Commons;
use Zav\Commons;

Log::message();
// ale tady už to funguje, použije se "Commons" z části use
Commons\Log::message();

<?php
namespace NějakýJiný;
use \Zav\Commons;
use \Zav\Commons as Foo;

// importovaný NS má alias
Foo\Log::message();
// pokud není alias zadaný, použije se jako alias název poslední části
Commons\Log::message();

Je to hodně podobné způsobu, jakým přistupuješ k souborům v jiných složkách, akorát nemůžeš použít adresáře . a .., ale buď
a) adresovat "od kořene" (tj. s lomítkem na začátku), nebo
b) pokud cílový NS je potomkem aktuálního, adresovat relativně k aktuálnímu NS
c) importovat cílový NS - nejdřív napíšeš alias importovaného NS, pak relativní cestu ke třídě


U všech příkladů použití chybí nějaké require './Log.php';, předpokládala jsem autoloading.
U těch importů záleží na tom, jaké další třídy a odkud budeš v tom skriptu potřebovat (myslím tím rozdíl v tom, jestli použiješ use \Zav\Commons nebo use \Zav\Commons\Log nebo obojí). Odkazuju na zajímavé vlákno na fóru Nette.
Suta
Profil
Tori:
Díky moc, hodně jsi pomohla.

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: