Si të konfiguroni telefonat inteligjentë dhe PC. Portali informativ

Krijimi i një objekti në php. Objektet

Klasat dhe objektet në PHP

Klasa është një koncept bazë në programimin e orientuar drejt objektit (OOP). Klasat formojnë bazën sintaksore të OOP. Ato mund të mendohen si një lloj "kontejnerësh" për të dhëna dhe funksione të lidhura logjikisht (të quajtura zakonisht - shih më poshtë). Për ta thënë thjesht, një klasë është një lloj lloji i të dhënave.

Një shembull i një klase është nje objekt. Një objekt është një koleksion i të dhënave () dhe funksioneve (metodave) për përpunimin e tyre. Vetitë dhe metodat quhen. Në përgjithësi, një objekt është çdo gjë që mbështet kapsulimin.

Nëse një klasë mund të konsiderohet si një lloj i të dhënave, atëherë një objekt mund të konsiderohet si një variabël (për analogji). Skripti mund të punojë njëkohësisht me disa objekte të së njëjtës klasë, si me disa variabla.

Brenda një objekti, të dhënat dhe kodi (anëtarët e klasës) mund të jenë publike ose jo. Të dhënat publike dhe anëtarët e klasës janë të aksesueshme për pjesë të tjera të programit që nuk janë pjesë e objektit. Por të dhënat private dhe anëtarët e klasës janë të disponueshme vetëm brenda këtij objekti.

Përshkrimet e klasave në PHP fillojnë me një fjalë funksioni klasës:

Emri i klasës (
// përshkrimi i anëtarëve të klasës - vetitë dhe metodat për përpunimin e tyre
}

Për të deklaruar një objekt duhet të përdorni operatorin i ri:

Objekti = Emri i ri i Klases;

Të dhënat përshkruhen duke përdorur një fjalë funksioni var. Metoda përshkruhet në të njëjtën mënyrë si një funksion i zakonshëm i përcaktuar nga përdoruesi. Ju gjithashtu mund t'i kaloni parametrat metodës.

Le të përmbledhim: deklarata e klasës duhet të fillojë me fjalën kyçe klasës(ngjashëm me mënyrën se si një deklaratë funksioni fillon me fjalën kyçe funksionin). Çdo deklaratë pronësie e përfshirë në një klasë duhet të paraprihet nga një fjalë kyçe var. Vetitë mund të jenë çdo lloj të dhënash të mbështetur nga PHP dhe mund të mendohen si variabla me dallime të vogla. Deklaratat e pronësisë pasohen nga deklaratat e metodave, të cilat janë shumë të ngjashme me deklaratat tipike të funksioneve të përcaktuara nga përdoruesi.

Sipas rregullave të pranuara përgjithësisht, emrat e klasave OOP fillojnë me një shkronjë të madhe dhe të gjitha fjalët në emrat e metodave, përveç të parës, fillojnë me shkronja të mëdha (fjala e parë fillon me shkronjë të vogël). Sigurisht, mund të përdorni çfarëdo shënimi që ju duket i përshtatshëm; Gjëja kryesore është të zgjidhni një standard dhe t'i përmbaheni atij.

Shembull i klasës në PHP:

// Krijo një klasë të re Coor:
Klasa Coor (
// të dhënat (vetitë):
var$emri;
var $addr;

// Metodat:
Emri i funksionit () (
jehonë"

Gjoni

" ;
}

}


$objekt = newCoor;
?>

Qasja në klasa dhe objekte në PHP

Ne shikuam se si përshkruhen klasat dhe si krijohen objektet. Tani duhet të hyjmë tek anëtarët e klasës, për këtë qëllim në PHP ekziston një operator -> . Ja një shembull:

// Krijo një klasë të re Coor:
Klasa Coor (
// të dhënat (vetitë):
var$emri;

// Metodat:
Funksioni Getname() (
jehonë"

Gjoni

" ;
}

}

// Krijo një objekt të klasës Coor:
$objekt = newCoor;
// Merrni akses te anëtarët e klasës:
$object -> name = "Alex" ;
echo $object -> emri ;
// Printon "Alex"
// Tani le të hyjmë në metodën e klasës (në fakt, një funksion brenda klasës):
$object -> Getname();
// Shtyp "Gjoni" me shkronja të mëdha
?>

Për të hyrë në anëtarët e klasës brenda një klase, duhet të përdorni një tregues $kjo, që i referohet gjithmonë objektit aktual. Metoda e modifikuar Emri i marrë ():

Funksioni Getname() (
echo $this->emri;
}

Në të njëjtën mënyrë, ju mund të shkruani një metodë Emri i grupit ():

funksioni Setname ($name) (
$this->emri = $emri;
}

Tani mund të përdorni metodën për të ndryshuar emrin Emri i grupit ():

$object->Setname("Pjetër");
$object->Getname();

Dhe këtu është lista e plotë e kodit:

// Krijo një klasë të re Coor:
Klasa Coor (
// të dhënat (vetitë):
var$emri;

// Metodat:
Funksioni Getname() (
echo $this -> emri ;
}

funksioni Setname ($name) (
$this -> emri = $emri ;
}

}

// Krijo një objekt të klasës Coor:
$objekt = newCoor;
// Tani për të ndryshuar emrin ne përdorim metodën Setname():
$object -> Setname("Nick");
// Dhe për qasje, si më parë, Getname():
$object -> Getname();
// Skenari printon "Nick"
?>

Treguesi $kjo mund të përdoret gjithashtu për të aksesuar metodat, jo vetëm për aksesin e të dhënave:

funksioni Setname ($name) (
$this->emri = $emri;
$this->Getname();
}

Konstruktorët

Shumë shpesh, kur krijoni një objekt, duhet të vendosni vlerat e disa vetive. Për fat të mirë, zhvilluesit e teknologjisë OOP e morën parasysh këtë rrethanë dhe e zbatuan atë në koncept. Një konstruktor është një metodë që vendos vlerat e disa vetive (dhe gjithashtu mund të thërrasë metoda të tjera). Konstruktorët thirren automatikisht kur krijohen objekte të reja. Për ta bërë të mundur këtë, emri i metodës së konstruktorit duhet të përputhet me emrin e klasës që e përmban atë. Shembull i konstruktorit:

Uebfaqja e klasës (
var $bgcolor;
Uebfaqja e funksionit ($color)(
$this -> bgcolor = $color ;
}
}

// Thirrni konstruktorin e klasës së Uebfaqes
$page = uebfaqe e re ("kafe");
?>

Më parë, krijimi i objektit dhe inicializimi i pronës kryheshin veçmas. Konstruktorët ju lejojnë t'i kryeni këto veprime në një hap.

Një detaj interesant: në varësi të numrit të parametrave të kaluar, mund të thirren konstruktorë të ndryshëm. Në shembullin e konsideruar, objektet e klasës Faqe interneti mund të krijohet në dy mënyra. Së pari, mund të telefononi një konstruktor, i cili thjesht krijon objektin, por nuk inicializon vetitë e tij:

$page = uebfaqe e re;

Së dyti, një objekt mund të krijohet duke përdorur një konstruktor të përcaktuar në një klasë - në këtë rast, ju krijoni një objekt të klasës Webpage dhe i caktoni një vlerë pronës së tij bgcolor:

$page = uebfaqe e re ("kafe");

Destruktorë

Nuk ka mbështetje të drejtpërdrejtë në PHP. Sidoqoftë, mund të simuloni lehtësisht një destruktor duke thirrur funksionin PHP unset (). Ky funksion shkatërron përmbajtjen e ndryshores dhe kthen burimet që ka zënë në sistem. Me objekte unset () funksionon në të njëjtën mënyrë si me variablat. Le të themi se jeni duke punuar me një objekt $Uebfaqe. Pas përfundimit të punës me këtë objekt të veçantë, funksioni quhet:

unset ($Webpage);

Kjo komandë fshin të gjitha përmbajtjet nga memoria $Uebfaqe. Në frymën e kapsulimit, mund të bëni një telefonatë unset () në një metodë të quajtur shkatërroj () dhe më pas thirreni:

$Website->destroy();

Nevoja për të thirrur destruktorë lind vetëm kur punoni me objekte që përdorin një sasi të madhe burimesh, pasi të gjitha variablat dhe objektet shkatërrohen automatikisht kur skripti përfundon.

Inicializimi i objekteve

Ndonjëherë ekziston nevoja për të inicializuar një objekt - për të caktuar vetitë e tij në vlerat fillestare. Le të supozojmë se emri i klasës është Coor dhe përmban dy prona: emrin e personit dhe qytetin e vendbanimit të tij. Ju mund të shkruani një metodë (funksion) që do të inicializojë një objekt, për shembull Init ():

// Krijo një klasë të re Coor:
Klasa Coor (
// të dhënat (vetitë):
var$emri;
var $qytet;

// Metoda e inicializimit:
Funksioni Init($name) (
$this -> emri = $emri ;
$ this -> qytet = "Londër" ;
}

}

// Krijo një objekt të klasës Coor:
$objekt = newCoor;
// Për të inicializuar objektin, thirrni menjëherë metodën:
$object -> Init();
?>

Gjëja kryesore është të mos harroni të thërrisni funksionin menjëherë pas krijimit të objektit, ose të telefononi ndonjë metodë midis krijimit (operatori i ri) objekti dhe inicializimi i tij (thirrja Fillimi).

Në mënyrë që PHP të dijë se një metodë e caktuar duhet të thirret automatikisht kur krijohet një objekt, duhet t'i jepet i njëjti emër si klasa ( Coor):

funksioni Coord ($name)
$this->emri = $emri;
$this->city = "Londër";
}

Metoda që inicializon një objekt quhet konstruktor. Megjithatë, PHP nuk ka destruktorë sepse burimet lëshohen automatikisht kur dalin skriptet.

Qasja në elementët e klasës

Elementet e klasës aksesohen duke përdorur operatorin :: "color i dyfishtë" Duke përdorur "dy pika" ju mund të përdorni metodat e klasës.

Kur aksesoni metodat e klasave, programuesi duhet të përdorë emrat e këtyre klasave.

klasa A (
shembull funksioni () (
jehonë "Ky është funksioni origjinal A::example().
"
;
}
}

Klasa B shtrihet A (
shembull funksioni () (
jehonë "Kjo është një anulim i B::shembull().
"
;
A::shembull();
}
}

// Nuk ka nevojë të krijohet një objekt i klasës A.
// Nxjerr sa vijon:
// Ky është funksioni origjinal A::example().
A::shembull();

// Krijo një objekt të klasës B.
$b = forum i ri i portalit B PHP. S.U.

Variablat që janë anëtarë të një klase quhen "veti". Ata quhen edhe duke përdorur terma të tjerë si "atribute" ose "fusha", por për qëllime të këtij dokumentacioni, ne do t'i referohemi si prona. Ato përcaktohen duke përdorur fjalë kyçe publike, të mbrojtura ose private, duke ndjekur rregullat për deklarimin e saktë të variablave. Kjo deklaratë mund të përmbajë një inicializim, por ky inicializim duhet të jetë një vlerë konstante, domethënë, vlera duhet të llogaritet në kohën e kompilimit dhe nuk duhet të varet nga informacioni i marrë në kohën e ekzekutimit për t'i llogaritur ato.

$this pseudo variabli është i disponueshëm brenda çdo metode klase kur ajo metodë thirret nga një kontekst objekti. $ky është një referencë për objektin që thirret (zakonisht objekti të cilit i përket metoda, por mundësisht një objekt tjetër nëse metoda thirret në mënyrë statike nga konteksti i një objekti të dytë).

Shembulli #1 Përcaktimi i vetive

klasa SimpleClass
{
public $var1 = "përshëndetje " . "botë" ;
publike $var2 =<<Përshendetje Botë
EOD;
// përkufizimi i saktë i vetive sipas PHP 5.6.0:
publike $var3 = 1 + 2 ;
// përkufizim i pasaktë i vetive:
public $var4 = vet::myStaticMethod();
publike $var5 = $myVar ;

// përkufizimi i saktë i vetive:
publike $var6 = myConstant ;
public $var7 = grup (e vërtetë, e gabuar);

// përkufizimi i saktë i vetive sipas PHP 5.3.0:
publike $var8 =<<<"EOD"
Përshendetje Botë
EOD;
}
?>

Koment:

Meqenëse PHP 5.3.0 dhe nowdocs mund të përdoren në çdo kontekst statik të të dhënave, duke përfshirë përcaktimin e vetive.

Shembulli #2 Shembull i përdorimit të nowdoc për të inicializuar pronat

klasa e ushqimit (
// Me PHP 5.3.0
publik $bar =<<<"EOT"
bar
EOT;
publik $baz =<<baz
EOT;
}
?>

Koment:

Mbështetja e Nowdoc dhe Heredoc u shtua në PHP 5.3.0.

7 vjet më parë

Në rast se kjo i kursen dikujt ndonjë kohë, kam shpenzuar vite të tëra duke kuptuar pse nuk funksionoi sa vijon:

klasa MyClass
{
private $foo = FALSE;


{
$this->$foo = E VËRTETË;

Echo ($this->$foo);
}
}

$bar = Klasa e re My();

duke dhënë "Gabim fatal: Nuk mund të qasesh në pronën boshe në ...test_class.php në linjën 8"

Ndryshimi delikat i heqjes së $ para akseseve të $foo e rregullon këtë:

klasa MyClass
{
private $foo = FALSE;

Funksioni publik __construct()
{
$this->foo = E VËRTETË;

Echo ($this->foo);
}
}

$bar = Klasa e re My();

Mendoj sepse ai trajton $foo si një variabël në shembullin e parë, kështu që duke u përpjekur të thërrasësh $this->FALSE (ose diçka në ato rreshta) që nuk ka kuptim. Është e qartë pasi ta keni kuptuar, por atje nuk janë shembuj të aksesit në këtë faqe që e tregojnë këtë.

4 vjet me pare

Ju mund të përdorni emrat e pronave me viza në to (për shembull, sepse keni konvertuar një skedar XML në një objekt) në mënyrën e mëposhtme:

$ref = new StdClass();
$ref ->( "ref-type" ) = "Artikulli ditar" ;
var_dump ($ref);
?>

8 vjet më parë

$kjo mund të hidhet në grup. Por kur e bën këtë, ai prefikson emrat e pronave / çelësat e grupeve të reja me të dhëna të caktuara në varësi të klasifikimit të vetive. Emrat e pronave publike nuk ndryshohen. Karakteristikat e mbrojtura parashtesohen me një "*" të mbushur me hapësirë. Vetitë private janë të prefiksuara me emrin e klasës së mbushur me hapësirë...

Testi i klasës
{
publike $var1 = 1 ;
i mbrojtur $var2 = 2 ;
private $var3 = 3 ;
statike $var4 = 4 ;

Funksioni publik toArray()
{
ktheje (array) $this ;
}
}

$t = test i ri ;
print_r($t -> toArray());

/* daljet:

Array
=> 1
[ * var2] => 2
[test var3] => 3
)

*/
?>
Kjo është sjellje e dokumentuar kur konvertohet ndonjë objekt në një grup (shihFaqja manuale PHP). Të gjitha vetitë, pavarësisht nga dukshmëria, do të shfaqen gjatë transmetimit të një objekti në grup (me përjashtim të disa objekteve të integruara).

Për të marrë një grup me të gjithë emrat e pronave të pandryshuara, përdorni funksionin "get_object_vars($this)" në çdo metodë brenda fushës së klasës për të marrë një grup të të gjitha vetive pavarësisht nga dukshmëria e jashtme, ose "get_object_vars($object)" jashtë fushës së klasës për të. merrni një grup vetëm pronash publike (shih:Faqja manuale PHP).

9 vjet më parë

Mos e ngatërroni versionin e pronave të php me vetitë në gjuhë të tjera (për shembull C++). Në php, vetitë janë të njëjta me atributet, variabla të thjeshta pa funksionalitet. Ato duhet të quhen atribute, jo veti.

Vetitë kanë funksionalitet të nënkuptuar aksesor dhe mutator. Unë kam krijuar një klasë abstrakte që lejon funksionalitetin e nënkuptuar të pronës.

Klasa abstrakte PropertyObject
{
funksioni publik __get ($name)
{
nëse (method_ekziston ($this , ($method = "get_" . $name )))
{
ktheni $this -> $method ();
}
tjetër kthim;
}

Funksioni publik __isset ($name)
{
nëse (method_ekziston ($this , ($method = "isset_" . $name )))
{
ktheni $this -> $method ();
}
tjetër kthim;
}

Funksioni publik __set ($name, $value)
{
nëse (method_ekziston ($this , ($method = "set_" . $name )))
{
$this -> $method ($value );
}
}

Funksioni publik __unset ($name)
{
nëse (method_ekziston ($this , ($method = "unset_" . $name )))
{
$this -> $method();
}
}
}

?>

Pas zgjerimit të kësaj klase, mund të krijoni aksesorë dhe mutatorë që do të thirren automatikisht, duke përdorur metodat magjike të php, kur të aksesohet vetia përkatëse.

5 vjet më parë

Metoda e përditësuar objectThis() në vetitë e grupit të klasës transtyppage ose grupi në stdClass.

Shpresoj se ju ndihmon.

objekti i funksionit publikThis($array = null) (
nëse (!$array) (
foreach ($this si $property_name => $property_values) (
nëse (is_array($property_values) && !empty($property_values)) (
$this->($property_name) = $this->objectThis($property_values);
) ndryshe nëse (is_array ($property_values) && bosh ($property_values)) (
$this->($property_name) = new stdClass();
}
}
) tjeter (
$object = new stdClass();
foreach ($array si $index => $values) (
nëse (is_array ($values) && bosh ($values)) (
$object->($index) = new stdClass();
) ndryshe nëse (is_array ($values)) (
$object->($index) = $this->objectThis($values);
) tjeter (
$objekt->($indeks) = $vlera;
}
}
ktheje $objekt;
}
}

  • Përkthimi

Sot objektet përdoren në mënyrë shumë aktive, megjithëse kjo ishte e vështirë të imagjinohej pas lëshimit të PHP 5 në 2005. Në atë kohë unë ende dija pak për aftësitë e kësaj gjuhe. Versioni i pestë i PHP u krahasua me versionin e mëparshëm, të katërt, dhe avantazhi kryesor i versionit të ri ishte një model i ri, shumë i fuqishëm objekti. Dhe sot, dhjetë vjet më vonë, rreth 90% e të gjithë kodit PHP përmban objekte që nuk kanë ndryshuar që nga PHP 5.0. Kjo sugjeron fuqimisht rolin e luajtur nga prezantimi i modelit të objektit, i cili u përmirësua vazhdimisht gjatë viteve në vijim. Në këtë postim do të doja të flisja se si funksionon gjithçka "nën kapuç". Në mënyrë që njerëzit të kuptojnë thelbin e proceseve - pse u bë në këtë mënyrë dhe jo ndryshe - dhe më mirë, të përdorin më plotësisht aftësitë e gjuhës. Do të prek gjithashtu temën e përdorimit të kujtesës nga objektet, duke përfshirë krahasimin me grupet ekuivalente (kur është e mundur).

Unë do të flas duke përdorur shembullin e PHP 5.4, dhe gjërat që përshkruaj janë të vlefshme për 5.5 dhe 5.6, sepse struktura e modelit të objektit atje nuk ka pësuar pothuajse asnjë ndryshim. Ju lutemi vini re se në versionin 5.3 gjërat nuk janë aq të mira për sa i përket veçorive dhe performancës së përgjithshme.

Në PHP 7, i cili është ende duke u zhvilluar në mënyrë aktive, modeli i objektit nuk është ridizajnuar shumë, janë bërë vetëm ndryshime të vogla. Thjesht sepse gjithçka funksionon mirë gjithsesi, dhe më e mira është armiku i së mirës. Janë shtuar veçori që nuk ndikojnë në kernel, por kjo nuk do të diskutohet këtu.

Si demonstrim, do të filloj me standardet sintetike:

Klasa Foo ( publike $a = "foobarstring"; publike $b; publike $c = ["disa", "vlera"]; ) për ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = new Foo; echo memory_get_usage() - $m"\n"; }
Këtu ne deklarojmë një klasë të thjeshtë me tre atribute, dhe më pas krijojmë 1000 objekte të kësaj klase në një lak. Vini re se si përdoret memoria në këtë shembull: kur krijoni një objekt të klasës Foo dhe një variabël për ta ruajtur atë, ndahen 262 bajt memorie dinamike PHP.

Le ta zëvendësojmë objektin me një grup ekuivalent:

Për ($i=0; $i<1000; $i++) { $m = memory_get_usage(); ${"var".$i} = [["some", "values"], null, "foobarstring"]; echo memory_get_usage() - $m . "\n"; }
Në këtë rast përdoren të njëjtat elementë: vetë grupi, null dhe variabli i vargut foobarstring . Por 1160 bajt memorie janë konsumuar tashmë, që është 4.4 herë më shumë.

Ja një shembull tjetër:

$klasë =<<<"CL" class Foo { public $a = "foobarstring"; public $b; public $c = ["some", "values"]; } CL; echo memory_get_usage() . "\n"; eval($class); echo memory_get_usage() . "\n";
Meqenëse klasa deklarohet në kohën e kompilimit, ne përdorim operatorin eval() për të deklaruar dhe matur memorien e përdorur (duke përdorur menaxherin e memories PHP). Në këtë rast, asnjë objekt nuk krijohet në këtë kod. Sasia e memories së përdorur (diff memory) është 2216 byte.

Tani le të shohim se si funksionon e gjithë kjo në thellësi të PHP, duke mbështetur teorinë me vëzhgime praktike.

Gjithçka fillon me klasa

Brenda PHP, një klasë përfaqësohet duke përdorur strukturën zend_class_entry:

Struktura _zend_class_entry ( char type; const char *emri; zend_uint name_length; struct _zend_class_entry *parent; int refcount; zend_uint ce_flags; HashTable function_table; mbers_table;S_table konstante HashTable; int default_properties_count; int default_static_members_count; union _zend_function *constructor; union _zend_function *destructor; union _zend_function *clone; union _zend_function *__get; union _zend_function *__set; union _zend_function *__set; union _zend_function *___ zend_function *__call; union _zend_function *__callstatic ; union _zend_function *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs; /* trajtuesit */ zend_object_value (*create_type_Ds) _object_iterator *(*get_iterator)(zend_class_ entry *ce, zval * objekt, int by_ref TSRMLS_DC); int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* një klasë zbaton këtë ndërfaqe */ union _zend_function *(*get_static_method)(zend_class_entry *ce, char* metoda, int method_len TSRMLS_DC); /* kthimet e serializuesit */ int (*serialize)(zval *objekt, char i panënshkruar **buffer, zend_uint *buf_len, zend_serialize_data *të dhënat TSRMLS_DC); int (*unserialize)(zval **objekt, zend_class_entry *ce, const char unsigned *buf, zend_uint buf_len, zend_unserialize_data *të dhënat TSRMLS_DC); zend_class_entry **ndërfaqet; zend_uint num_interfaces; zend_class_entry **tipare; zend_uint num_traits; zend_trait_alias **trait_aliases; zend_trait_precedencë **parësi_tipare; union ( struct ( const char *emri i skedarit; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; ) përdorues; struct (const struct _zend_function_entry *builtin_functions; struct_zendrymo;dule_zendrymo;dule_zendrymo;) );
Madhësia e strukturës, bazuar në modelin LP64, është 568 bajt. Kjo do të thotë, sa herë që PHP deklaron një klasë, ajo detyrohet të krijojë një zend_class_entry, duke përdorur më shumë se gjysmë kilobajt memorie dinamike vetëm për këtë qëllim. Natyrisht, çështja nuk kufizohet vetëm në këtë: siç e vutë re, struktura përmban shumë tregues që gjithashtu duhet të vendosen në memorie. Kjo do të thotë, vetë klasat konsumojnë shumë më tepër memorie sesa të gjitha objektet e krijuara më pas prej tyre.

Ndër të tjera, klasat përmbajnë atribute (statike dhe dinamike) si dhe metoda. E gjithë kjo kërkon edhe kujtesë. Sa i përket metodave, është e vështirë të llogaritet marrëdhënia e saktë, por një gjë është e vërtetë: sa më i madh të jetë trupi i metodës, aq më i madh është OPArray e saj, që do të thotë sa më shumë memorie konsumon. Shtoni në këtë variabla statike që mund të deklarohen në një metodë. Më pas vijnë atributet, më vonë edhe ato do të vendosen në memorie. Madhësia varet nga vlerat e tyre të paracaktuara: numrat e plotë do të marrin pak, por një grup i madh statik do të hajë shumë memorie.

Është e rëndësishme të dini për një pikë tjetër në lidhje me zend_class_entry - rreth komenteve të PHP. Ato njihen gjithashtu si shënime. Këto janë variabla të vargut (në C, char* buffers), të cilat gjithashtu duhet të vendosen në memorie. Për gjuhën C, e cila nuk përdor Unicode, ndryshe nga PHP, rregulli është shumë i thjeshtë: një karakter = një bajt. Sa më shumë shënime të keni në një klasë, aq më shumë memorie do të përdoret pas analizimit.

Për zend_class_entry, fusha doc_comment përmban shënime të klasës. Një fushë të tillë kanë edhe metodat dhe atributet.

Përdoruesit dhe klasat e brendshme

Një klasë përdoruesi është një klasë e përcaktuar duke përdorur PHP, ndërsa një klasë e brendshme përcaktohet ose duke injektuar kodin burim në vetë PHP ose duke përdorur një shtesë. Dallimi më i madh midis këtyre dy llojeve të klasave është se klasat e përcaktuara nga përdoruesi funksionojnë në memorie të alokuar sipas kërkesës, ndërsa klasat e brendshme funksionojnë në memorie "të vazhdueshme".

Kjo do të thotë që kur PHP përfundon përpunimin e kërkesës aktuale HTTP, ajo pastron dhe shkatërron të gjitha klasat e personalizuara në përgatitje për përpunimin e kërkesës së radhës. Kjo qasje njihet si "arkitektura e "ndan asgjë". Kjo u ndërtua në PHP që në fillim dhe nuk ka ende plane për ta ndryshuar atë.

Pra, sa herë që krijohet një kërkesë dhe analizohen klasat, memoria ndahet për to. Pas përdorimit të një klase, gjithçka që lidhet me të shkatërrohet. Pra, sigurohuni që të përdorni të gjitha klasat e deklaruara, përndryshe kujtesa do të humbasë. Përdorni ngarkues automatikë, ata vonojnë analizimin/deklarimin në kohën e ekzekutimit kur PHP duhet të përdorë klasën. Megjithëse ekzekutimi është më i ngadalshëm, ngarkuesi automatik e përdor mirë memorien sepse nuk do të funksionojë derisa klasa të jetë e nevojshme.

Ndryshe është me klasat e brendshme. Ato ruhen përgjithmonë në memorie, pavarësisht nëse përdoren apo jo. Kjo do të thotë, ato shkatërrohen vetëm kur vetë PHP ndalon së punuari - pasi të jenë përpunuar të gjitha kërkesat (që do të thotë SAPI në internet, për shembull, PHP-FPM). Prandaj, klasat e brendshme janë më efikase se ato të personalizuara (vetëm atributet statike shkatërrohen në fund të kërkesës, asgjë tjetër).

Nëse (EG(full_tabela_pastrimi)) (zend_hash_reverse_apply(EG(function_tabela), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC); ) else ( zend_hash_reverse_apply ( EG (function_tabela), (apply_func_t) clean_non_persistent_function TSRMLS_CC); zend_hash_reverse_apply(EG(class_tabela), (apply_func_t) clean_non_persistent_class TSRMLS_CC); ) static int clean_non_persistent_class(zend_class_hyrja **ce TSRMLS_e_kthimi **ce TSRMLS_e=Lloji (IN_LA) END_HASH_APPLY_STOP: ZEND_ HASH_APPLY_REMOVE;)
Vini re se edhe me caching opcode si OPCache, klasa krijohet dhe shkatërrohet me çdo kërkesë, ashtu si me klasat e personalizuara. OPCache thjesht i shpejton të dyja këto procese.

Siç e keni vënë re, nëse aktivizoni shumë shtesa PHP, secila prej të cilave deklaron shumë klasa, por përdorni vetëm një numër të vogël të tyre, atëherë kujtesa humbet. Mos harroni se shtesat PHP deklarojnë klasa në kohën e fillimit të PHP, edhe nëse kërkesat e mëvonshme nuk do t'i përdorin ato klasa. Prandaj, nuk rekomandohet mbajtja aktive e shtesave nëse nuk po përdoren aktualisht, përndryshe do të humbni kujtesën. Sidomos nëse këto shtesa deklarojnë shumë klasa - megjithëse mund të bllokojnë kujtesën me diçka tjetër.

Klasat, ndërfaqet ose tiparet - nuk ka rëndësi

Për të menaxhuar klasat, ndërfaqet dhe tiparet në PHP, përdoret e njëjta strukturë - zend_class_entry. Dhe siç e keni parë tashmë, kjo strukturë është mjaft e rëndë. Ndonjëherë zhvilluesit deklarojnë ndërfaqe në kodin e tyre në mënyrë që ata të mund të përdorin emrat e tyre në blloqet e kapjes. Kjo ju lejon të kapni vetëm një lloj të caktuar përjashtimi. Për shembull, si kjo:

Ndërfaqja e klasës BarException ( ) MyException zgjeron Exception zbaton BarException ( ) provo ( $foo->bar(): ) catch (BarException $e) ( )
Nuk është shumë mirë që 912 bajt përdoren këtu vetëm për të deklaruar ndërfaqen BarException.

$klasë =<<<"CL" interface Bar { } CL; $m = memory_get_usage(); eval($class); echo memory_get_usage() - $m . "\n"; /* 912 bytes */
Nuk dua të them se kjo është e keqe apo marrëzi, nuk po përpiqem të fajësoj askënd apo asgjë. Unë thjesht po ju tërheq vëmendjen në këtë pikë. Nga pikëpamja e strukturës së brendshme të PHP, klasat, ndërfaqet dhe tiparet përdoren saktësisht në të njëjtën mënyrë. Ju nuk mund të shtoni atribute në ndërfaqe; analizuesi ose përpiluesi thjesht nuk do t'ju lejojë ta bëni këtë. Megjithatë, struktura zend_class_entry është ende aty, thjesht një numër fushash, duke përfshirë static_members_table , nuk do të jenë tregues të alokuar nga memoria. Deklarimi i një klase, një tipari ekuivalent ose një ndërfaqe ekuivalente do të kërkojë të njëjtën sasi memorie pasi të gjitha përdorin të njëjtën strukturë.

Lidhja e klasës

Shumë zhvillues nuk mendojnë për lidhjen e klasës derisa të fillojnë të pyesin se si funksionojnë gjërat në të vërtetë. Lidhja e klasës mund të përshkruhet si "procesi me të cilin vetë klasa dhe të gjitha të dhënat e lidhura me të përgatiten për përdorim të plotë nga zhvilluesi". Ky proces është shumë i thjeshtë dhe nuk kërkon shumë burime nëse flasim për një klasë që nuk zgjeron një tjetër, nuk përdor tipare dhe nuk zbaton një ndërfaqe. Procesi i lidhjes për klasa të tilla ndodh tërësisht në kohën e kompilimit dhe asnjë burim nuk harxhohet për këtë gjatë ekzekutimit. Ju lutemi vini re se ne po flisnim për lidhjen e një klase të deklaruar nga përdoruesi. Për klasat e brendshme, i njëjti proces bëhet kur klasat regjistrohen me bërthamën ose shtesat PHP, pak para se të ekzekutohen skriptet e përdoruesit - dhe kjo bëhet vetëm një herë gjatë gjithë kohës së ekzekutimit të PHP.

Gjërat bëhen shumë të ndërlikuara kur bëhet fjalë për zbatimin e ndërfaqeve ose trashëgiminë e klasës. Më pas, gjatë lidhjes së klasës, absolutisht gjithçka kopjohet nga objektet prind dhe fëmijë (qoftë klasa apo ndërfaqe).

/* Klasa e vetme */ rasti ZEND_DECLARE_CLASS: if (do_bind_class(CG(active_op_array), opline, CG(class_table), 1 TSRMLS_CC) == NULL) ( return; ) table = CG(class_tabela); pushim;
Në rastin e një deklarate të thjeshtë klase, ne ekzekutojmë do_bind_class() . Ky funksion thjesht regjistron një klasë plotësisht të përcaktuar në tabelën e klasës për përdorim të mëvonshëm në kohën e ekzekutimit, dhe gjithashtu kontrollon për metoda të mundshme abstrakte:

Zend_verify_abstract_class(zend_class_entry *ce TSRMLS_DC) ( zend_abstract_info ai; nëse ((ce->ce_flamujt & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flamujt & ZEND_ACC) , sizeof(ai)); z end_hash_apply_with_argument(&ce - >Tabela_funksioni, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC); nëse (ai.cnt) ( zend_error(E_ERROR, "Klasa %s përmban %d metodë abstrakte%s dhe për këtë arsye duhet të deklarohet abstrakte ("MASTRA_CT_FOTO_ABSFOIN_FONTING mbetur FMT MAX_ABSTRACT_INFO_FMT ")", ce->emri, ai.cnt, ai.cnt > 1 ? "s" : "", DISPLAY_ABSTRACT_FN(0), DISPLAY_ABSTRACT_FN(1), DISPLAY_ABSTRACT_FN(2)); ) ))
Nuk ka asgjë për të shtuar këtu, një rast i thjeshtë.

Kur lidhni një klasë që zbaton një ndërfaqe, duhet të bëni sa më poshtë:

  • Kontrolloni nëse ndërfaqja është deklaruar tashmë.
  • Kontrolloni nëse klasa e kërkuar është me të vërtetë një klasë dhe jo një ndërfaqe në vetvete (siç u përmend më lart, nga pikëpamja e strukturës së brendshme ato janë të strukturuara njësoj).
  • Kopjoni konstante nga ndërfaqja në klasë, duke kontrolluar për përplasje të mundshme.
  • Kopjoni metodat nga ndërfaqja në klasë, duke kontrolluar për përplasje dhe mospërputhje të mundshme në deklaratë (për shembull, shndërrimi i metodave të ndërfaqes në ato statike në klasën e fëmijës).
  • Shtoni ndërfaqen dhe të gjitha ndërfaqet e mundshme amtare në listën e ndërfaqeve të zbatuara nga klasa.
Me "kopjim" nuk nënkuptojmë një kopje të plotë të thellë. Për konstantat, atributet dhe funksionet, një rillogaritje kryhet nga ana tjetër për të përcaktuar se sa entitete në memorie i përdorin ato.

ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC) ( /* ... ... */ ) tjetër ( if (ce->num_interfaces >= current_iface_num) ( if (ce-> INTER) (ZEND_C) ->ndërfaqet = (zend_class_entry **) realloc(ce->ndërfaqet, sizeof(zend_class_entry *) * (++current_iface_num)); ) other ( ce->ndërfaqet = (zend_class_entry **) erealloc(ce->ndërfaqet, madhësia e (zend_class_entry *) * (++current_iface_num)); ) ) ce->interfaces = iface; zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref_content,docunta_checkt, sizeof_check) iface zend_hash_merge_ex(&ce->function_table, &iface->function_tabela, (copy_ctor_func_t) do_inherit_method, sizeof(zend_function), (merge_checker_func_t) do_inherit_method_check, ce); do_implement_herit_check, ce); ces (ce, iface TS RMLS_CC); ) )
Vini re ndryshimin midis klasave të brendshme dhe të përdoruesve. E para do të përdorë realloc() për të shpërndarë memorie, e dyta do të përdorë erealloc(). realloc() shpërndan memorie "të vazhdueshme", ndërsa erealloc() operon në memorie "në kërkesë".

Ju mund të shihni se kur dy tabela konstante (ndërfaqja-1 dhe klasa-1) bashkohen, ato e bëjnë këtë duke përdorur kthimin e thirrjes zval_add_ref. Ai nuk kopjon konstante nga një tabelë në tjetrën, por ndan treguesit e tyre, thjesht duke shtuar numrin e referencave.

Për secilën nga tabelat e funksionit (metodës), përdoret do_inherit_method:

Void statik do_inherit_method(zend_funksion *funksion) (funksioni_add_ref(funksion); ) ZEND_API void funksion_add_ref(zend_funksion *funksion) ( if (funksion->lloj == ZEND_USER_FUNCTION) (zend_op_array>oprayn_oprayn; )++; nëse (op_array->static_variables) (HashTable *static_variables = op_array->static_variables; zval *tmp_zval; ALLOC_HASHTABLE(op_array->static_variables); zend_hash_init(zend_variable_static_variables-> ), NULL, ZVAL_PTR_DTOR, 0 ); zend_hash_copy(op_array->static_variables, static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *)); ) op_array->run_time_cache = NULL;
Një rinumërim shtohet në OArray të funksionit dhe të gjitha variablat e mundshme statike të deklaruara në funksion (këtu një metodë) kopjohen duke përdorur zval_add_ref. Pra, i gjithë procesi i kopjimit kërkon shumë burime kompjuterike sepse ka shumë unaza dhe kontrolle të përfshira. Por përdoret pak memorie. Fatkeqësisht, lidhja e ndërfaqes sot zbulohet plotësisht në kohën e ekzekutimit dhe do ta ndjeni atë në çdo kërkesë. Ndoshta zhvilluesit do ta ndryshojnë këtë së shpejti.

Sa i përket trashëgimisë, këtu, në parim, gjithçka është e njëjtë si kur zbatohet një ndërfaqe. Vetëm më shumë "pjesëmarrës" janë të përfshirë. Por dua të vërej se nëse PHP tashmë di për klasën, atëherë lidhja bëhet në kohën e përpilimit, dhe nëse nuk e di, atëherë në kohën e ekzekutimit. Pra, është më mirë ta deklaroni kështu:

/* mirë */ klasa A ( ) klasa B shtrihet A ( )
në vend të:

/* keq */ klasa B shtrihet A ( ) klasa A ( )
Nga rruga, procedura rutinë e lidhjes së klasës mund të çojë në sjellje shumë të çuditshme:

/* kjo funksionon */ klasa B zgjeron A ( ) klasën A ( )

/* por ky nuk është */ Gabim fatal: Klasa "B" nuk u gjet */ klasa C shtrihet B ( ) klasa B shtrihet A ( ) klasa A ( )

Në opsionin e parë, lidhja e klasës B shtyhet deri në kohën e ekzekutimit, sepse kur kompajleri arrin deklaratën e kësaj klase, ai nuk di ende asgjë për klasën A. Kur fillon ekzekutimi, lidhja e klasës A ndodh pa diskutim. sepse tashmë është përpiluar, duke qenë një klasë singleton. Në rastin e dytë, gjithçka është ndryshe. Lidhja e klasës C shtyhet në kohën e ekzekutimit sepse përpiluesi nuk di ende asgjë për B kur përpiqet ta përpilojë atë. Por kur klasa C është e lidhur në kohën e ekzekutimit, ajo kërkon B, e cila nuk ekziston sepse nuk është e kompiluar sepse B është plotësuese. Shfaqet mesazhi "Klasa B nuk ekziston".

Objektet

Pra, tani ne e dimë se:
  • Klasat marrin shumë memorie.
  • Klasat e brendshme janë optimizuar shumë më mirë se klasat e përdoruesve, sepse këto të fundit duhet të krijohen dhe shkatërrohen me çdo kërkesë. Klasat e brendshme ekzistojnë gjatë gjithë kohës.
  • Klasat, ndërfaqet dhe tiparet përdorin të njëjtën strukturë dhe procedura, dallimet janë shumë të vogla.
  • Gjatë trashëgimisë ose deklarimit, procesi i lidhjes kërkon shumë kohë të CPU-së, por përdor pak memorie sepse shumë gjëra janë të përbashkëta dhe jo të dyfishuara. Gjithashtu, është më mirë të ekzekutoni lidhjen e klasës në kohën e përpilimit.

Tani le të flasim për objektet. Kapitulli i parë tregon se krijimi i një objekti "klasik" (një klasë përdoruesi "klasike") kërkonte shumë pak memorie, rreth 200 bajt. Gjithçka ka të bëjë me klasën. Kompilimi i mëtejshëm i klasës gjithashtu konsumon memorie, por kjo është për mirë sepse kërkohen më pak bajta për të krijuar një objekt të vetëm. Në thelb, një objekt është një koleksion i vogël strukturash të vogla.

Metodat e menaxhimit të objekteve

Në nivelin e motorit, metodat dhe funksionet janë e njëjta gjë - struktura zend_function_structure. Vetëm emrat ndryshojnë. Metodat përpilohen dhe shtohen në atributin function_table në zend_class_entry. Prandaj, në kohën e ekzekutimit, çdo metodë është paraqitur, është vetëm një çështje e transferimit të treguesit në ekzekutim.

Typedef union _zend_function ( tipi zend_uchar; struct ( tipi zend_uchar; const char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint num_args; zendum__args; zendum__args; _op_array op_array; zend_internal_function interior_function; ) zend_function ;
Kur një objekt përpiqet të thërrasë një metodë, motori si parazgjedhje shikon në tabelën e funksioneve të klasës së objektit. Nëse metoda nuk ekziston, atëherë thirret __call(). Gjithashtu kontrollohet dukshmëria - publike / e mbrojtur / private - në varësi të cilës janë ndërmarrë veprimet e mëposhtme:

Bashkimi statik _zend_funksioni *zend_std_get_method(zval **objekti_ptr, char *emri_metodës, int metoda_len, konst zend_literal *kyç TSRMLS_DC) (funksioni_zend *fbc; zval *objekti = *objekt_ptr; zend_objekti = *objekt_ptr; zend_char =valë *J_b); lc_method_name; ALLOCA_FLAG(use_heap) if (EXPECTED(key != NULL)) ( lc_method_name = Z_STRVAL(key->constant); hash_value = key->hash_value; ) other ( lc_method_name = ze_alloca_str. lc_method_emri, metoda_emri, metoda_len); hash_value = zend_hash_func(lc_method_name, method_len+1); ) /* Nëse metoda nuk gjendet */ if (E PAPRITUR(zend_hash_quick_find(&zobj->ce->function_methodlen_1, metoda_methodlen_h, hash_1. , (void **)&fbc) == DËSHTIM)) (nëse (E PAPRITUAR (!çelës)) (free_alloca(lc_method_name, use_heap); ) nëse (zobj->ce->__call) ( /* nëse klasa ka një Trajtuesi __call() */ return zend_get_user_call_function(zobj->ce, emri_metodës, metoda_len); /* thirr mbajtësin __call() */ ) else (kthim NULL; /* tjetër kthe NULL, i cili ka të ngjarë të çojë në një gabim fatal: metoda nuk u gjet */ ) ) /* Kontrollo nivelin e aksesit */ nëse (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) ( zend_function *updated_fbc; updated_fbc = zend_check_cint_ , Z_OBJ_HANDLER_P(objekt, get_class_entry)(objekt TSRMLS_CC), lc_method_name, method_len, hash_value TSRMLS_CC); if (EXPECTED(updated_fbc != NULL)) (fbc = updated_fbc (fbc)_bc (fbc) = zend_get_user_call_function(zobj->ce, emri_metodës, metoda_len); ) else ( zend_error_noreturn(E_ERROR, "Thirrje në metodën %s %s::%s() nga konteksti "%s"", zend_visibility_string(fbc->common.fn_flags ), ZEND_FN_SCOPE_NAME(fbc), emri_metodës, EG(fushëveprimi) ? EG(fushëveprimi)->emri: ""); ) ) ) tjetër ( /* ... ... */ )
Ju mund të keni vënë re një gjë interesante, shikoni rreshtat e parë:

Nëse (EXPECTED(key != NULL)) ( lc_method_name = Z_STRVAL(key->constant); hash_value = key->hash_value; ) tjetër ( lc_method_name = do_alloca (method_len+1, use_heap); /* Krijo një zend_t_olower_ src, src_length); */ zend_str_tolower_copy(lc_method_emri, method_emri, method_len); hash_value = zend_hash_func(lc_method_emri, method_len+1); )
Ky është një manifestim i pandjeshmërisë së rastit të PHP. Sistemi duhet së pari të zvogëlojë çdo funksion (zend_str_tolower_copy()) përpara se ta thërrasë atë. Jo saktësisht secili, por ato ku është i pranishëm deklarata if. Ndryshorja kryesore parandalon ekzekutimin e funksionit që konvertohet në shkronja të vogla (pjesa tjetër) - kjo është pjesë e optimizimit PHP të zbatuar në versionin 5.4. Nëse thirrja e metodës nuk është dinamike, atëherë përpiluesi ka llogaritur tashmë çelësin dhe më pak burime harxhohen në kohën e ekzekutimit.

Klasa Foo (funksioni publik BAR() ( ) ) $a = Foo i ri; $b = "bar"; $a->bar(); /* thirrje statike: mirë */ $a->$b(); /* thirrje dinamike: e keqe */
Kur kompilohet funksioni/metoda, ai konvertohet menjëherë në shkronja të vogla. Funksioni i mësipërm BAR() kthehet në bar() nga përpiluesi kur shtohet një metodë në tabelën e klasave dhe funksioneve.

Në shembullin e mësipërm, thirrja e parë është statike: përpiluesi ka llogaritur çelësin për vargun "bar" dhe kur vjen koha për të thirrur metodën, ai ka më pak punë për të bërë. Thirrja e dytë është tashmë dinamike, përpiluesi nuk di asgjë për "$b" dhe nuk mund të llogarisë çelësin për thirrjen e metodës. Më pas, në kohën e ekzekutimit, duhet ta konvertojmë vargun në shkronja të vogla dhe të llogarisim hash-in e tij (zend_hash_func()), i cili nuk ka ndikimin më të mirë në performancë.

Sa i përket __call() , ai nuk e zvogëlon aq shumë performancën. Megjithatë, kjo metodë harxhon më shumë burime sesa thirrja e një funksioni ekzistues.

Menaxhimi i atributeve të objekteve

Ja çfarë ndodh:

Siç mund ta shihni, kur krijohen objekte të shumta të së njëjtës klasë, motori ridrejton çdo atribut në të njëjtin tregues si në rastin e atributeve të klasës. Gjatë gjithë jetës së saj, një klasë ruan jo vetëm atributet e veta statike, por edhe atributet e objekteve. Në rastin e klasave të brendshme - gjatë gjithë kohës së funksionimit të PHP. Krijimi i një objekti nuk përfshin krijimin e atributeve të tij, kështu që kjo është një qasje mjaft e shpejtë dhe me kosto efektive. Vetëm kur një objekt është gati të ndryshojë një nga atributet e tij, motori krijon një të ri për të, duke supozuar se po ndryshoni atributin $a të Foo #2:

Pra, kur krijojmë një objekt, ne "thjesht" krijojmë një strukturë zend_object 32 bajt:

Typedef struct _zend_object ( zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* mbron nga __get/__set ... recursion */ ) zend_object;
Kjo strukturë shtohet në dyqanin e objekteve. Dhe kjo, nga ana tjetër, është struktura zend_object_store. Ky është regjistri global i objekteve të motorit Zend - një vend ku të gjitha objektet mblidhen dhe ruhen në një shembull:

ZEND_API zend_object_value zend_objects_new(zend_object **objekt, zend_class_entry *class_type TSRMLS_DC) (zend_object_value retval; *object = emalloc(sizeof(zend_object)); (*object_typ)-*L; *objekt)->properties_tabela = NULL; (*objekt)->roje = NULL; /* Shto objektin në dyqan */ retval.handle = zend_objects_store_put(*objekt, (zend_objects_store_dtor_t) zend_objects_destroy_object,s_endz mosha e objektit_stor, NULL TSRMLS_CC); retval.handlers = &std_object_handlers; kthimi i rivlerësimit;)
Më pas, motori krijon një vektor veçori për objektin tonë:

ZEND_API void object_properties_init(zend_object *objekt, zend_class_entry *class_type) ( int i; if (class_type->default_properties_count) (objekt->properties_table = emalloc(sizeof(zval*) *de class_count;0_pro-ties< class_type->numërimi i_vetive të paracaktuara; i++) (objekt->tabela_vetive[i] = class_type->tabela_properties_properties[i]; if (class_type->default_properties_tabela[i]) (#if ZTS ALLOC_ZVAL(object->properties_table[i]); MAKE_COPY>AL_COPY [i], objekt->tabela_properties[i]);#else Z_ADDREF_P(objekt->tabela_vetive[i]); #endif ) ) objekt->vetitë = NULL; ) )
Siç mund ta shihni, ne kemi ndarë një memorie tabele/vektoriale (si në C) për zval* bazuar në vetitë e deklaruara të klasës së objektit. Në rastin e PHP-së jo të sigurt për thread, ne thjesht shtojmë refcount në atribut dhe nëse përdoret Zend i sigurt në fije (ZTS, Zend thread siguri), atëherë duhet të kopjojmë plotësisht zval . Ky është një nga shumë shembuj që konfirmojnë performancën e ulët dhe intensitetin e lartë të burimeve të modalitetit ZTS në krahasim me PHP jo-ZTS.

Ju ndoshta keni dy pyetje:

  • Cili është ndryshimi midis pronave_tabela dhe vetive në strukturën zend_object?
  • Nëse vendosim atributet e objektit tonë në vektorin C, si mund t'i kthejmë ato? Shfletoni vektorin çdo herë (gjë që zvogëlon performancën)?

Përgjigja për të dyja pyetjet është dhënë nga zend_property_info.

Typedef struct _zend_property_info ( flamuj zend_uint; const char *emri; int name_length; ulong h; int offset; const char *doc_comment; int doc_comment_len; zend_class_entry *ce; ) zend_property_info;
Çdo deklaruar një atribut (veti) i objektit tonë ka informacionin përkatës të pronës të shtuar në fushën e pronës_info në zend_class_entry. Kjo bëhet gjatë përpilimit të atributeve të deklaruara në klasë:

Klasa Foo ( publike $a = "foo"; e mbrojtur $b; private $c; ) struct _zend_class_entry ( /* ... ... */ HashTable function_table; HashTable properties_info; /* këtu janë informacionet e vetive rreth $a, $b dhe $c */ zval **default_properties_table; /* dhe këtu, ne do të gjejmë $a, $b dhe $c me vlerat e tyre të paracaktuar */ int default_properties_count; /* kjo do të ketë vlerën 3: 3 veti */ /* ... ... */
Properties_infos është një tabelë që i tregon një objekti nëse atributi i kërkuar ekziston. Dhe nëse ekziston, atëherë kalon numrin e tij të indeksit në grupin objekt->properties. Më pas kontrollojmë dukshmërinë dhe aksesin në shtrirje (publike/e mbrojtur/private).

Nëse atributi nuk ekziston dhe duhet t'i shkruajmë, atëherë mund të provojmë të thërrasim __set() . Në rast dështimi, ne krijojmë një atribut dinamik që do të ruhet në fushën object->property_table.

Property_info = zend_get_property_info_quick(zobj->ce, anëtar, (zobj->ce->__set != NULL), çelësi TSRMLS_CC); if (EXPECTED(property_info != NULL) && ((EXPECTED((property_info->flags & ZEND_ACC_STATIC) == 0) && property_info->offset >= 0) ? (zobj->properties ? ((variable_ptr = (zval** )zobj->properties_tabela) != NULL) : (*(variable_ptr = &zobj->properties_tabela) != NULL)) : (EXPECTED(zobj->properties != NULL) && EXPECTED(zend_hash_zobj-properties-properties >emri, prona_info->gjatësia_emri+1, vetia_info->h, (void **) &variable_ptr) == SUKSES)))) ( /* ... ... */ ) tjetër ( zend_guard *guard = NULL; nëse (zobj->ce->__set && /* klasa ka një __set() ? */ zend_get_property_guard(zobj, pronë_info, anëtar, &guard) == SUKSES && !guard->në_set) ( Z_ADDREF_P(objekt); nëse (PZVAL_IS_REF( objekt)) ( SEPARATE_ZVAL(&objekt); ) guard->in_set = 1; /* parandalo vendosjen rrethore */ nëse (zend_std_call_setter(objekt, anëtar, vlera TSRMLS_CC) != SUKSES) ( /* telefono __set() */ ) roje ->në_set = 0; zval_ptr_dtor(&objekt); /* ... ... */
Për sa kohë që nuk i shkruani një objekti, konsumi i kujtesës së tij nuk ndryshon. Pas regjistrimit, ai zë më shumë hapësirë ​​(derisa të shkatërrohet), pasi përmban të gjitha atributet e shkruara në të.

Objektet që sillen si referenca falë ruajtjes së objekteve

Objektet nuk janë referenca. Kjo tregohet në një skenar të vogël:

Funksioni foo($var) ( $var = 42; ) $o = Klasa e re My; foo ($o); var_dump($o); /* ky është ende një objekt, jo numri i plotë 42 */
Të gjithë tani do të thonë se "në PHP 5, objektet janë lidhje", madje edhe manuali zyrtar e përmend këtë. Teknikisht kjo është krejtësisht e rreme. Megjithatë, objektet mund të sillen në të njëjtën mënyrë si referencat. Për shembull, kur kaloni një ndryshore që është një objekt në një funksion, ai funksion mund të modifikojë të njëjtin objekt.

Kjo ndodh sepse zvali i kaluar si funksion nuk kalon vetë objektin, por identifikuesin e tij unik, i cili përdoret për të kërkuar në dyqanin e objekteve të përbashkëta. Dhe rezultati është i njëjtë. Është e mundur të alokohen tre zval të ndryshëm në memorie dhe të gjitha mund të përmbajnë të njëjtën dorezë objekti.

Objekti(MyClass)#1 (0) ( ) /* #1 është doreza e objektit (numri), është unik */

Zend_object_store lejon që objektet të ruhen një herë në memorie. Mënyra e vetme për të shkruar në dyqan është krijimi i një objekti të ri me fjalën kyçe të re, funksionin unserialize(), API-në e reflektimit ose fjalën kyçe të klonimit. Asnjë operacion tjetër nuk do t'ju lejojë të kopjoni ose krijoni një objekt të ri në dyqan.

Typedef struct _zend_objects_store ( zend_object_store_bucket *object_buckets; zend_uint top; zend_uint size; int free_list_head; ) zend_objects_store; typedef struct _zend_object_store_bucket ( zend_bool destructor_called; zend_bool valid; zend_uchar apply_count; union _store_bucket ( struct _store_object ( void *object; zend_objects_store_dtor_t dtor; zend_objectstore_stor; _t klon; const zend_object_handlers *handlers; zend_uint refcount; gc_root_buffer *buffered; ) obj; struct (int tjetër; ) lista e lirë; ) kovë; ) zend_object_store_bucket;

Çfarë është kjo $?

Të kuptuarit e $this nuk është edhe aq e vështirë, por ka pjesë kodi të lidhura me këtë mjet në disa vende në motor: në përpilues, në kodin për marrjen e variablave në kohën e ekzekutimit, etj. $kjo shfaqet dhe zhduket sipas nevojës, duke iu caktuar automatikisht objektit aktual - në përgjithësi, një gjë "magjike". Dhe kodi i brendshëm lejon në mënyrë të përkryer menaxhimin e tij.

Së pari, përpiluesi nuk do të lejojë shkrimin në $this. Për ta bërë këtë, do të kontrollojë çdo detyrë që bëni dhe nëse gjen një detyrë për $this, do të hedhë një gabim fatal.

/* ... ... */ nëse (opline_is_fetch_this(last_op TSRMLS_CC)) ( zend_error(E_COMPILE_ERROR, "Nuk mund të ricaktohet $this"); ) /* ... ... */ static zend_bool opline_is_fetch_this(const zend_op *opline TSRMLS_DC) (nëse ((opline->opcode == ZEND_FETCH_W) && (opline->op1_type == IS_CONST) && (Z_TYPE(CONSTANT(opline->op1.konstant)) == IS_STRING) && ((opline-> vlera e zgjeruar & ZEND_FETCH_STATIC_MEMBER) != ZEND_FETCH_STATIC_MEMBER) && (Z_HASH_P(&CONSTANT(opline->op1.konstant)) == THIS_HASHVAL) && (Z_STRLEN(CONSTANT(opline->op)-="1. 1)) && !memcmp(Z_STRVAL(CONSTANT(opline->op1.konstant)), "this", sizeof("this"))) (kthimi 1;) else (kthimi 0;) )
Si menaxhohet $kjo? Përdorimi i tij është i mundur vetëm brenda një metode, gjatë thirrjes së së cilës përpiluesi gjeneron OPCode INIT_METHOD_CALL. Motori e di se kush e thërret metodën, në rastin e $a->foo() është $a. Pastaj vlera $a merret dhe ruhet në hapësirën e përbashkët. Më pas, metoda thirret duke përdorur OPCode DO_FCALL. Në këtë pikë, vlera e ruajtur merret përsëri (objekti thërret metodën) dhe i caktohet treguesit të brendshëm global $this - EG(This) .

Nëse (fbc->lloji == ZEND_USER_FUNCTION || fbc->skopi i zakonshëm) (duhet_ndryshim_skopi = 1; EX(aktuale_kjo) = EG(Kjo); EX(fushë_aktuale) = EG(sferë); EX(fushë_e_quajtur_aktuale) = EG( i quajtur_scope); EG(This) = EX(object); /* merrni objektin e përgatitur në kodin e mëparshëm INIT_METHOD dhe ndikojeni atë në EG(This) */ EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX (objekt)) ? fbc->common.scope: NULL; EG (quajtur_scope) = EX (thirrje)->quajtur_scope; )
Tani, kur thirret një metodë, nëse në trupin e saj përdorni $this për të vepruar në një variabël ose thirrni një metodë (p.sh. $this->a = 8), atëherë kjo do të rezultojë në OPCode ZEND_ASSIGN_OBJ , i cili nga ana tjetër do të marrë $ kjo kthehet nga EG(Kjo).

Static zend_always_inline zval **_get_obj_zval_ptr_ptr_unused(TSRMLS_D) ( if (EXPECTED(EG(This) != NULL)) ( return &EG(This); ) other ( zend_error_noreturn(E_ERROR, "Using $this"); kthehu ne kontekstin e objektit; I PAVLEFSHËM; ) )
Në rast se keni përdorur $this për të thirrur një metodë (për shembull, $this->foo()) ose keni kaluar një thirrje tjetër funksioni ($this->foo($this);), atëherë motori do të përpiqet të nxjerrë $this nga tabelat e simboleve aktuale siç bën ai për çdo ndryshore standarde. Por këtu përgatitja e veçantë kryhet gjatë krijimit të kornizës së pirgut të funksionit aktual:

Nëse (op_array->this_var != -1 && EG(This)) (Z_ADDREF_P(EG(This)); nëse (!EG(active_symbol_tabela)) (EX_CV(op_array->this_var) = (zval **) EX_CV_NUM(execute_data , op_array->last_var + op_array->this_var); *EX_CV(op_array->this_var) = EG(This); ) other ( if (zend_hash_add(EG(active_symbol_tab), "this", sizeof("this"), &EG (Kjo), sizeof(zval *), (void **) EX_CV_NUM(execute_data, op_array->this_var))==DËSHTIM) ( Z_DELREF_P(EG(Kjo)); ) )
Kur thërrasim një metodë, motori ndryshon fushëveprimin:

Nëse (fbc->lloj == ZEND_USER_FUNCTION || fbc->common.scope) ( /* ... ... */ EG(scope) = (fbc->lloj == ZEND_USER_FUNCTION || !EX(objekt)) fbc->common.scope: NULL; /* ... ... */ )
EG(fushëveprimi) është i tipit zend_class_entry . Kjo është klasa që zotëron metodën që po kërkoni. Dhe do të përdoret për çdo operacion në objektin që do të kryeni në trupin e metodës pasi të keni kontrolluar dukshmërinë e motorit:

Static zend_always_inline int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC) ( switch (property_info->flamujt & ZEND_ACC_PPP_MASK) (rasti ZEND_ACC_PPP_MASK) (rasti ZEND_ACC_PPP_MASK) ted (pronësia _info->ce, EG (fushëveprimi)); rast ZEND_ACC_PRIVATE: nëse ((ce==EG(fushëveprimi) || vetia_info->ce == EG(fushëveprimi)) && EG(fushëveprimi)) (kthimi 1;) tjetër (kthimi 0;) thyej; ) ktheni 0; )
Kështu mund të aksesoni anëtarët privatë të objekteve që nuk ju përkasin, por janë fëmijë të fushës suaj aktuale:

Klasa A ( $a private; funksioni publik foo(A $obj) ( $this->a = "foo"; $obj->a = "bar"; /* po, kjo është e mundur */ ) ) $a = A e re; $b = A e re; $a->foo($b);
Kjo veçori ka shkaktuar një numër të madh raportesh të gabimeve nga zhvilluesit. Por kështu funksionon modeli i objektit në PHP - në fakt, ne vendosim shtrirjen bazuar jo në objekt, por në klasë. Në rastin e klasës sonë "Foo", ju mund të punoni me çdo Foo private të çdo Foo tjetër, siç tregohet më sipër.

Rreth shkatërruesit

Destruktorët janë të rrezikshëm, mos u mbështetni tek ata pasi PHP nuk i thërret ata edhe në rast të një gabimi fatal:

Klasa Foo ( funksioni publik __destruct() ( jehonë "byebye foo"; ) ) $f = Foo i ri; ky funksion ekziston(); /* gabim fatal, funksioni nuk u gjet, destruktori i Foo NUK është ekzekutuar */
Po në lidhje me radhën në të cilën thirren destruktorët nëse thirren gjithsesi? Përgjigja është qartë e dukshme në kod:

Pavlefshme shutdown_destructors(TSRMLS_D) ( zend_try ( simbolet int; bëj ( simbolet = zend_hash_num_elements (&EG(symbol_tabela)); elementet(&EG(tabela_simboli) ) ); zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC); ) zend_catch ( /* nëse nuk mund t'i shkatërrojmë pastër, shënojini të gjitha objektet si të shkatërruara gjithsesi */ zend_objects_store_mark_destructed(&EG(objects_LS_Valt)_TS; _shkatërrues (zval **zv TSRMLS_DC) (nëse (Z_TYPE_PP(zv) == IS_OBJECT && Z_REFCOUNT_PP(zv) == 1) (ktheje ZEND_HASH_APPLY_REMOVE; ) tjetër (ktheje ZEND_HASH_APPLY_KEEP; ) )
Tre fazat e thirrjes së një destruktori janë demonstruar këtu:

  • Kaloni në tabelën e simboleve globale prapa dhe thirrni destruktorët për objektet që kanë refcount = 1.
  • Pastaj drejtimi i ciklit ndryshon dhe destruktorët thirren për të gjitha objektet e tjera, me refcount > 1.
  • Nëse shfaqet një problem në një nga fazat e mëparshme, thirrja për destruktorët e mbetur ndërpritet.
Në çfarë çon kjo:

Klasa Foo (funksioni publik __destruct() (var_dump("Destroyed Foo"); ) )
shiriti i klasës (funksioni publik __destruct() (var_dump("shiriti i shkatërruar"); ) )

Shembulli i parë:

$a = Foo e re; $b = shirit i ri; "Destroyed Bar" "Destroyed Foo"
I njëjti shembull:

$a = shirit i ri; $b = Foo e re; "Destroyed Foo" "Destroyed Bar"
Shembulli dy:

$a = shirit i ri; $b = Foo e re; $c = $b; /* shtimi i rillogaritjes së objektit $b */ "Destroyed Bar" "Destroyed Foo"
Shembulli tre:

Klasa Foo (funksioni publik __destruct() ( var_dump("Destroyed Foo"); die();) ) /* vini re die() këtu */ class Bar (funksioni publik __destruct() ( var_dump("Destroyed Bar"); ) ) $a = Foo e re; $a2 = $a; $b = shirit i ri; $b2 = $b; shkatërroi Foo
Kjo procedurë u zgjodh për një arsye. Por nëse nuk ju përshtatet, atëherë është më mirë t'i shkatërroni vetë objektet tuaja. Kjo është mënyra e vetme për të kontrolluar thirrjet në __destruct(). Nëse e lejoni PHP-në ta bëjë këtë për ju, mos u mërzitni me rezultatet. Ju gjithmonë keni mundësinë të shkatërroni objektet tuaja me dorë për të pasur kontroll të plotë mbi porosinë.

PHP nuk thërret destruktorë në rast të ndonjë gabimi fatal. Fakti është se në këtë rast Zend është i paqëndrueshëm, dhe thirrja e destruktorëve çon në ekzekutimin e kodit të përdoruesit, i cili mund të hyjë në tregues të gabuar dhe, si rezultat, të prishë PHP. Është më mirë të ruash stabilitetin e sistemit - kjo është arsyeja pse thirrja e destruktorëve është e bllokuar. Ndoshta diçka do të ndryshojë në PHP 7.

Sa i përket rekursioneve, ato janë të mbrojtura dobët në PHP, dhe kjo vlen vetëm për __get() dhe __set() . Nëse e shkatërroni objektin tuaj diku në kornizën e stivit të destruktorit, do të përfundoni në një lak të pafund rekurziv që do të hajë të gjitha burimet e stakut të procesit tuaj (zakonisht 8 kB, ulimit -s) dhe do të thyejë PHP-në.

Klasa Foo (funksioni publik __destruct() ( Foo i ri; ) /* do të rrëzohet */ )
Për ta përmbledhur, mos u besoni destruktorëve me kodin kritik, siç është kontrolli i mekanizmit të bllokimit, sepse PHP mund të mos thërrasë destruktorin ose mund ta thërrasë atë në një sekuencë të pakontrolluar. Nëse kodi i rëndësishëm ende përpunohet nga një destruktor, atëherë të paktën kontrolloni vetë ciklin jetësor të objekteve. PHP do të thërrasë destruktorin kur rinumërimi i objektit tuaj të bjerë në zero, që do të thotë se objekti nuk është më në përdorim dhe mund të shkatërrohet në mënyrë të sigurt.

konkluzioni

Shpresoj që tani shumë gjëra janë bërë më të qarta për ju në punën e përditshme me objekte. Ata nuk konsumojnë shumë memorie, dhe zbatimi i tyre në nivelin e motorit është optimizuar mirë. Përpiquni të përdorni një ngarkues automatik të dizajnuar mirë për të përmirësuar përdorimin e kujtesës. Deklaroni klasa sipas radhës së trashëgimisë logjike dhe nëse i ktheni më komplekset prej tyre në shtesa C, mund të optimizoni shumë procese dhe madje të rrisni më tej performancën e përgjithshme të klasave të tilla.

Në këtë tutorial do të mësoni bazat e programimit të orientuar drejt objekteve në PHP. Do të mësoni për parimet e OOP në përgjithësi dhe do të mësoni se si të shkruani skripta të thjeshta në PHP.

Mirë se vini në të parën e një serie mësimesh mbi OOP në PHP! Duke përfunduar të gjitha mësimet në këtë seri, do të mësoni për parimet dhe konceptet bazë të OOP dhe do të mësoni se si të krijoni shpejt dhe lehtë aplikacione të dobishme në PHP.

Në këtë tutorial do të filloj t'ju informoj dhe t'ju tregoj për konceptet bazë të OOP. Ti do të mësosh:

  • çfarë është OOP
  • se si OOP do t'ju ndihmojë të krijoni skripte më të mira PHP
  • disa koncepte bazë si klasa, objekte, metoda, variabla të klasës
  • ku të filloni të shkruani një skript PHP

A jeni gati të zhyteni në botën e objekteve PHP? Pastaj vazhdo!

Çfarë është programimi i orientuar nga objekti?

Nëse keni krijuar dhe përdorur ndonjëherë funksione të personalizuara në PHP, ju keni përdorur një stil programimi procedural. Në programimin procedural, ju zakonisht krijoni struktura të dhënash - numra, vargje, vargje, etj. - të ruajë disa të dhëna, dhe më pas të përpunojë këto struktura me funksione të veçanta që manipulojnë këto të dhëna.

Programimi i orientuar nga objekti, ose OOP, ka ecur përpara sepse këtu ne ruajmë strukturat e të dhënave dhe funksionet që i përpunojnë ato në një entitet të vetëm të quajtur objekt. Në vend që të përpunoni të dhënat me ndonjë funksion, ju i ngarkoni ato të dhëna në një objekt dhe më pas thërrisni metodat e tij për ta manipuluar atë dhe për të marrë rezultatin e dëshiruar.

Më shpesh, objektet e krijuara duke përdorur OOP pasqyrojnë entitete reale. Për shembull, nëse po krijoni një forum për faqen tuaj, do të krijoni një objekt Anëtar që do të ruajë informacione për secilin anëtar të forumit (emri, identifikimi, emaili, fjalëkalimi, etj.), si dhe metodat që do të përpunojnë këtë informacion ( regjistrim, autorizim, dalje, ndalim, etj.).

Pse të përdorni OOP?

Procedurale dhe e orientuar nga objekti janë dy mënyra të ndryshme për të bërë të njëjtën gjë. Kjo nuk do të thotë se njëra prej tyre është më e mirë se tjetra - të gjithë shkruajnë si të duan, kështu që mund t'i kombinoni lehtësisht këto dy qasje në një skenar.

Sidoqoftë, këtu janë disa përfitime të OOP për zhvilluesit:

  • Është më e lehtë të pasqyrohen situata reale: siç e theksova më lart, objektet pasqyrojnë entitete reale - njerëz, produkte, karta, artikuj në blog, etj. Kjo e thjeshton shumë detyrën kur sapo filloni të hartoni aplikacionin tuaj, pasi qëllimi i secilit objekt është si një qëllim marrëdhëniet ndërmjet objekteve do të jenë të qarta dhe të kuptueshme.
  • Është më e lehtë të shkruash programe modulare: OOP përfshin shkrimin e moduleve. Duke e ndarë kodin tuaj në module, do të jetë më e lehtë për ju ta menaxhoni, korrigjoni dhe zgjeroni atë.
  • Është më e lehtë të shkruash kodin që do të përdoret shumë herë: Shkrimi i kodit që mund të përdoret më shumë se një herë do të kursejë kohë kur shkruani një aplikacion dhe me kalimin e kohës mund të krijoni edhe një bibliotekë të tërë të këtyre llojeve të moduleve që mund t'i përdorni në shumë aplikacionet. Me OOP, bëhet relativisht më e lehtë për të shkruar një kod të tillë pasi strukturat dhe funksionet e të dhënave janë të kapsuluara në një objekt të vetëm që mund të përdoret çdo herë.

Disa koncepte themelore

Përpara se të filloni të shkruani skriptet, duhet të keni një kuptim të mirë të koncepteve të klasës, objektit, variablit të klasës dhe metodës.

Klasat

Një klasë është një kornizë për një objekt. Kjo është një pjesë e kodit që përcakton:

  • Llojet e të dhënave që do të përmbajnë objektet e klasës së krijuar
  • Funksionet që do të përmbajnë këto objekte.

Kur krijoni një aplikacion OOP, zakonisht krijoni disa klasa që do të përfaqësojnë llojet e ndryshme të entiteteve në aplikacionin tuaj. Për shembull, për të krijuar një forum, mund të krijoni klasat Forum, Temë, Postim dhe Anëtar.

Objektet

Një objekt është një lloj i veçantë variabli që krijohet përmes një klase. Ai përmban të dhëna aktuale dhe funksione për manipulimin e tij. Ju mund të krijoni sa më shumë objekte që dëshironi nga një klasë e vetme. Çdo funksion i një objekti është i pavarur nga një objekt tjetër, edhe nëse ato janë krijuar nga e njëjta klasë.

Për krahasim me entitetet reale:

  • Një klasë është korniza për një makinë: ajo përcakton se si do të duket dhe si do të veprojë makina, por është ende një entitet abstrakt.
  • Një objekt është një makinë e vërtetë e krijuar nga një kornizë teli: ka veti reale (të tilla si shpejtësia) dhe sjellje (të tilla si nxitimi ose frenimi).

Shënim: Një objekt shpesh quhet esenca e një klase, dhe procesi i krijimit të një objekti të një klase quhet zbatim.

Variablat e klasës

Vlerat e të dhënave që ruhen në një objekt të caktuar shkruhen në variabla të veçanta të quajtura variabla të klasës. Variablat e klasës janë të lidhura ngushtë me objektin e saj. Edhe pse të gjitha objektet e një klase kanë të njëjtat variabla, vlerat e tyre mund të ndryshojnë.

Metodat

Funksionet e përcaktuara në një klasë dhe të përdorura në objektet e asaj klase quhen metoda. Ato nuk janë shumë të ndryshme nga funksionet e rregullta - ju mund t'u kaloni vlerat, ato mund të përmbajnë variabla lokale dhe vlera të kthejnë. Megjithatë, metodat më shpesh punojnë me variabla objektesh. Për shembull, metoda login() për regjistrimin e përdoruesve në forumin tuaj mund ta vendosë variablin e klasës logedIn në true.

Si të krijoni një klasë në PHP?

Tani që e dini tashmë se cilat janë klasat, metodat, variablat e klasës dhe objektet, është koha për të krijuar disa klasa dhe objekte në kodin PHP.

Së pari, le të shohim se si të krijojmë një klasë. Në thelb, skripti për krijimin e një klase duket si ky:

Klasa Emri i klasës ( // (përkufizimi i klasës) )

Për shembull, nëse po krijoni një klasë Anëtare për forumin tuaj, do të shkruani këtë:

Anëtar i klasës ( // (përkufizimi i klasës) )

Është mjaft e thjeshtë. Natyrisht, kjo klasë nuk do të bëjë asgjë derisa të shtoni variabla dhe metoda në të. Sidoqoftë, kodi i mësipërm krijon një klasë të vlefshme PHP që mund të përdoret.

Një rregull i mirë i përgjithshëm: vendosni secilën klasë në një skedar të veçantë me të njëjtin emër si emri i klasës. Për shembull, vendosni klasën Anëtar në skedarin Member.php dhe ruajeni në një dosje, të themi, klasa.

Si të krijoni objekte në PHP?

Ju mund të krijoni një objekt duke përdorur fjalën kyçe të re:

Emri i ri i klasës()

Ky kod do të krijojë një objekt të klasës ClassName. Ju do të duhet ta përdorni këtë objekt më vonë, kështu që ju duhet ta ruani atë në një ndryshore. Për shembull, le të krijojmë një objekt të klasës Anëtar dhe ta ruajmë atë në variablin $member:

$anëtar = Anëtar i ri();

Ne gjithashtu mund të krijojmë një objekt tjetër të së njëjtës klasë:

$anëtar2 = Anëtar i ri();

Edhe pse i krijuam këto dy objekte nga e njëjta klasë, variablat $member dhe $member2 janë të pavarura nga njëra-tjetra.

Krijoni variabla të klasës

Tani që ne tashmë dimë se si të krijojmë klasa dhe objekte të klasës, le të shohim se si të krijojmë variabla të klasës. Ekzistojnë 3 aksesorë për variablat e klasës që mund të shtohen në një klasë:

  • Variablat e klasës publike (publike): e aksesueshme - d.m.th. ato mund të lexohen dhe/ose modifikohen - kudo në skript, pavarësisht se ku ndodhet ky kod - brenda klasës ose jashtë saj
  • Variablat e klasës private (private): të aksesueshme vetëm për metodat e klasës. Është më mirë që variablat e klasës të bëhen private për të ndarë objektet nga pjesa tjetër e kodit.
  • Variablat e mbrojtur të klasës: Të disponueshme për metodat e klasës suaj, si dhe për metodat e klasave të trashëguara (do të flasim për trashëgiminë më vonë).

Për të krijuar një variabël të klasës, shkruani fjalën kyçe publike, private ose e mbrojtur dhe më pas shkruani emrin e ndryshores:

Klasa ClassName ( $propertyName publike; $propertyName private; $propertyName e mbrojtur; )

Le të shtojmë një variabël të klasës publike në klasën tonë Anëtar për të ruajtur emrin e përdoruesit:

Anëtar i klasës (publik $username = ""; )

Vini re se ne kemi inicializuar variablin tonë të klasës, vlera e saj është vargu bosh, “”. Kjo do të thotë që kur krijohet një përdorues i ri, emri i përdoruesit do të jetë i paracaktuar në vargun bosh. Ashtu si në rastin e variablave të rregullt në PHP, variablat e klasës nuk duhet të inicializohen, por është më mirë të mos jesh dembel. Nëse nuk inicializon një variabël të klasës, vlera e saj e paracaktuar është null.

Qasja në variablat e klasës

Për të fituar akses në një ndryshore të një objekti, përdorni operatorin ->:

$object->propertyName

Le te perpiqemi. Le të shkruajmë një skript që deklaron një klasë Anëtar dhe një variabël të klasës, krijon një objekt të kësaj klase dhe më pas vendos vlerën e ndryshores së klasës dhe e shfaq atë në ekran:

emri i përdoruesit = "Fred"; echo $member->emri i përdoruesit; // Print "Fred" ?>

Ekzekutoni këtë kod, ai do të shfaqë vargun "Fred", vlerën e ndryshores së klasës $member->username. Siç mund ta shihni, ju veproni në një ndryshore objekti ashtu si një ndryshore e rregullt - mund ta vendosni në një vlerë dhe ta lexoni.

Shtimi i metodave në një klasë

Po në lidhje me krijimin e metodave? Siç e përmenda më herët, metodat janë vetëm funksione të rregullta që janë pjesë e një klase. Kështu që nuk mund të habiteni që ato janë krijuar duke përdorur të njëjtën fjalë kyçe të funksionit. I vetmi ndryshim nga krijimi i funksioneve të rregullta është se mund të shtoni edhe një nga identifikuesit e aksesit (publik, privat, i mbrojtur) në deklaratën e tij. Në këtë mënyrë, metodat janë të ngjashme me variablat e klasës:

Klasa Klasa Emri (metoda e funksionit publikEmri() ( // (kodi) ) metoda e funksionit privatEmri() ( // (kodi) ) metoda e funksionit të mbrojturEmri() ( // (kodi) ) )

Shënim: ashtu si në rastin e variablave të klasës, metodat publike mund të thirren nga kudo, metodat private mund të thirren vetëm brenda klasës dhe metodat e mbrojtura mund të thirren nga vetë klasa dhe pasardhësi i saj.

Le të përpiqemi të shtojmë disa metoda dhe variabla të klasës në klasën tonë:

  • variabël private e klasës $loggedIn për të identifikuar përdoruesin, d.m.th. nëse ai hyri apo jo,
  • Metoda login(), e cila do të hyjë në forum duke vendosur variablin e klasës $loggedIn në true,
  • metodë logout(), e cila do të dalë nga forumi duke vendosur variablin e klasës $loggedIn në false,
  • metodë isLoggedIn(), e cila do të kthejë vlerën e ndryshores së klasës $loggedIn.

Këtu është kodi ynë:

logedIn = e vërtetë; ) funksioni publik logout() ( $this->loggedIn = false; ) funksioni publik isLoggedIn() ( return $this->loggedIn; ) ) ?>

Ju mund të keni vënë re se kemi përdorur një fjalë kyçe të re $this. Në kontekstin e metodave të një objekti, ndryshorja speciale $this i referohet vetë objektit. Duke përdorur $this në metodën e një objekti, metoda mund të aksesojë çdo variabël të klasës dhe metodë të objektit.

Për shembull, metoda login() mund të aksesojë variablin e klasës $loggedIn të objektit nëpërmjet $this->loggedIn.

Meqë ra fjala, ndryshorja jonë e klasës është private, kështu që nuk mund të thirret nga asnjë pjesë e skriptit, por vetëm nga metodat login(), logout() dhe isLoggedIn(). Kjo është një qasje e mirë sepse pjesa e brendshme e objektit (si p.sh. se si saktësisht regjistron nëse përdoruesi është i identifikuar apo jo) është i ndarë nga pjesa tjetër e kodit. Sa herë që është e mundur, përpiquni të përdorni variabla të klasës private në mënyrë që objektet tuaja të jenë autonome, të lëvizshme dhe të mbrojtura.

Shënim: ndryshorja e klasës $username në shembullin tonë është publike. E bëra këtë vetëm për të demonstruar se si mund të aksesoni variablat e klasës së një objekti. Në projektet reale, ju preferoni ta bëni këtë ndryshore private dhe të krijoni variabla të veçanta të klasës publike për të vendosur vlerat e emrit të përdoruesit nëse është e nevojshme.

Përdorimi i Metodave

Për të thirrur një metodë në një objekt, përdorni operatorin ->, me të cilin jeni njohur tashmë.

$object->methodName()

Kjo funksionon ashtu si thirrja e një funksioni të rregullt. Ju mund të kaloni argumente në kllapa (duke supozuar se kërkon ndonjë argument sigurisht), dhe thirrja e metodës gjithashtu mund të kthejë vlera specifike që mund t'i përdorni më pas.

logedIn = e vërtetë; ) funksioni publik logout() ( $this->loggedIn = false; ) funksioni publik isLoggedIn() ( return $this->loggedIn; ) ) $member = Anëtar i ri(); $member->username = "Fred"; echo $member->emri i përdoruesit . "është". ($member->
"; $member->login(); echo $member->username . " është " . ($member->isLoggedIn() ? "logged in" : "logged out") . "
"; $member->logout(); echo $member->username . " është " . ($member->isLoggedIn() ? "logged in" : "logged out") . "
"; ?>

Ky skript do të shfaqë sa vijon:

Fred është loguar Fred është loguar Fred është loguar

Ja se si funksionon:

  1. Pas përshkrimit të klasës Anëtar, ne krijuam objektin e saj dhe e ruajmë atë në variablin $member. Ne gjithashtu i dhamë variablit të klasës $username të këtij objekti vlerën "Fred".
  2. Më pas thirrëm metodën $member->isLoggedIn() për të përcaktuar nëse përdoruesi është i identifikuar apo jo. Kjo metodë thjesht kthen vlerën e ndryshores së klasës $loggedIn. Meqenëse vlera e paracaktuar e kësaj variabli të klasës është false, rezultati i thirrjes së $member->isLoggedIn() do të jetë false, kështu që do të shfaqet mesazhi "Fred is logged out".
  3. Pastaj ne thërrasim metodën login(). Ai do të vendosë variablin e klasës $loggedIn në true.
  4. Tani, kur telefononi metodën $member->isLoggedIn(), ajo do të kthehet e vërtetë dhe do të shfaqë mesazhin "Fred është i regjistruar".
  5. Le të thërrasim metodën logout(), e cila vendos vlerën e vetive $loggedIn në false.
  6. Le të thërrasim metodën $member->isLoggedIn() për herë të tretë. Tani do të kthehet false sepse vetia $loggedIn është vendosur sërish në false. Pra, mesazhi "Fred është loguar" do të shfaqet përsëri.

Shënim: në rast se e keni parë këtë së pari: ?: është një operator tresh. Ky është një version i thjeshtuar i blloqeve if...else. Ju mund të mësoni rreth këtyre llojeve të operatorëve.

konkluzionet

Në këtë tutorial ju mësuat bazat e OOP në PHP. Keni mësuar për gjëra të tilla si:

  • Çfarë është OOP dhe pse është i dobishëm?
  • konceptet e klasave, objekteve, variablave të klasës dhe metodave
  • si të krijohen klasa dhe objekte
  • si të krijohen dhe përdoren variablat e klasës
  • konceptet e identifikuesve të aksesit publik, privat, të mbrojtur
  • si të krijohen dhe përdoren metodat e klasës

Ju tashmë keni mësuar shumë për të dhe do të mësoni shumë më tepër në mësimet e mëposhtme. Megjithatë, nëse keni punuar me të gjithë shembujt që kam dhënë, ju keni një bazë të fortë. Mund të filloni të krijoni aplikacione duke përdorur OOP.

Artikujt më të mirë mbi këtë temë