PHP class public, private en protected

Overzicht

Sponsored by: Vacatures door Monsterboard

Pagina: 1 2 volgende »

Sebastiaan Janssen

Sebastiaan Janssen

02/11/2020 13:16:57
Anchor link
Hi,


Wie kan mij uitleggen wat nu het verschil is tussen public, private en protected in een PHP class?
Hoe en wanneer gebruik je welke als je een class gaat programmeren.

gr. Sebastiaan
 
PHP hulp

PHP hulp

20/04/2024 15:51:40
 
- Ariën  -
Beheerder

- Ariën -

02/11/2020 13:20:05
Anchor link
De drie soorten bepalen of een eigenschap of methode toegankelijk is buiten de class, en in classes die ge-extend worden.

Lees hier eens: https://torquemag.io/2016/05/understanding-concept-visibility-object-oriented-php/
Gewijzigd op 02/11/2020 13:22:28 door - Ariën -
 
Ward van der Put
Moderator

Ward van der Put

02/11/2020 13:31:56
 
Ozzie PHP

Ozzie PHP

02/11/2020 14:43:21
Anchor link
Public wil zeggen dat je een eigenschap van buiten een class kunt aanspreken en wijzigen.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
<?php

class Person {

public $name;

}


$person = new Person();
$person->name = 'Sebastiaan';

?>

Meestal (eigenlijk altijd) wil je dit niet, omdat iedereen nu zomaar alle eigenschappen kan aanpassen. Meestal wil je een bepaalde vorm van controle behouden. Daarom worden vaak 'setters' en 'getters' gebruikt. De eigenschappen, meestal 'properties' genoemd, maak je dan private (of protected als je de class zou extenden, maar daar zal ik je nu niet mee lastigvallen).

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

class Person {

private $name;

public function getName() {
  return $this->name;
}


public function setName($name) {
  if (empty($this->name)) {
    $this->name = $name;
  }
else {
    echo 'Error, name is already set!';
  }
}

}


$person = new Person();
$person->setName('Sebastiaan');

echo $person->getName();

// Sebastiaan

$person->setName('Karel');

// Error, name is already set!

?>

Als ik je mag adviseren, dan raad ik je aan om altijd private te gebruiken (tenzij je classes gaat extenden dan heb je mogelijk protected nodig).
 
Thomas van den Heuvel

Thomas van den Heuvel

02/11/2020 23:00:25
Anchor link
Ozzie PHP op 02/11/2020 14:43:21:
Als ik je mag adviseren, dan raad ik je aan om altijd private te gebruiken (tenzij je classes gaat extenden dan heb je mogelijk protected nodig).

Zou het om die reden daarom eigenlijk niet beter zijn om altijd protected (als default) te gebruiken in plaats van private? Dit omdat private in zekere zin de principes van OOP tegengaat (uitbreidbaarheid).

Ik zou het dus omdraaien: tenzij je expliciete redenen hebt om iets niet te extenden gewoon protected gebruiken, al was het maar om dingen niet op voorhand uit te sluiten.
 
Ozzie PHP

Ozzie PHP

03/11/2020 01:12:09
Anchor link
Thomas van den Heuvel op 02/11/2020 23:00:25:
Ozzie PHP op 02/11/2020 14:43:21:
Als ik je mag adviseren, dan raad ik je aan om altijd private te gebruiken (tenzij je classes gaat extenden dan heb je mogelijk protected nodig).

Zou het om die reden daarom eigenlijk niet beter zijn om altijd protected (als default) te gebruiken in plaats van private? Dit omdat private in zekere zin de principes van OOP tegengaat (uitbreidbaarheid).

Daar valt iets voor te zeggen. Ik vind het zelf prettig om alles in eerste instantie op private te zetten en pas 'vrij te geven' op het moment dat dat ook echt nodig is, zodat je bewust kunt selecteren wat je wel en niet vrijgeeft. Maar jouw aanpak zou ook kunnen. Het belangrijkste is in ieder geval dat je niet alles (of beter gezegd helemaal niks) op public zet. En of je alles op private of protected zet daar valt voor beide keuzes iets voor te zeggen. Zolang je het maar consequent doet.
 
Thomas van den Heuvel

Thomas van den Heuvel

03/11/2020 02:05:59
Anchor link
Ozzie PHP op 03/11/2020 01:12:09:
En of je alles op private of protected zet daar valt voor beide keuzes iets voor te zeggen.

Nou nee, private druist gewoon in tegen het hergebruiken van code. Dit is niet een kwestie van smaak. Deze code zul je eerst aan moeten passen (private -> protected) om hier alsnog verder mee te kunnen. Waarom zou je dit op voorhand dichtmetselen?

Een extra muur bijzetten kan altijd. Een muur ergens alsnog uitslopen is gewoon extra werk.

Ozzie PHP op 03/11/2020 01:12:09:
Zolang je het maar consequent doet.

Nou nee, niet als dit inhoudt dat je iets consequent verkeerd doet. Je moet een reden hebben om specifiek protected of private (of zelfs public) te gebruiken. Dit is niet enkel syntactische suiker. Het argument voor protected (versus private) is dat je direct kunt extenden. De reden voor private zou kunnen zijn dat je dingen expliciet niet wilt extenden, maar hoe vaak komt dat voor? Heb je daar praktijkvoorbeelden van? En blijkt uit het gebruik van de class niet al vanzelf dat het extenden niet zoveel zin heeft of niet logisch is? Dit hoef je niet expliciet/op voorhand af te dwingen met private, tenzij dit dus echt de bedoeling is maar nogmaals, hoe vaak is dat echt nodig?

Het is beter om dingen open te houden in plaats van dingen achteraf open te breken, het laatste is gewoon meer werk. Dit valt deels onder het kopje "defensief programmeren" waarbij je potentieel overbodig toekomstig werk uit de weg gaat.

Het is ook niet een kwestie van "vrijgeven" want het gebruik is nog steeds beperkt tot binnen de class, of met protected afgeleide classes van zo'n class.
 
Ozzie PHP

Ozzie PHP

03/11/2020 12:31:57
Anchor link
Nogmaals wat jij zegt snijdt ook hout.

Ik programmeer graag defensief alsin dat ik properties graag beperk tot de eigen class tenzij anders nodig blijkt te zijn. Daarbij moet ik zeggen dat ik al een tijdje niet meer met OOP bezig ben geweest. Ik zal er eens over nadenken of het een goede gewoonte is om voortaan protected te gebruiken.

Wel is dan de vraag wat dan nog het bestaansrecht van private is?
 
Thomas van den Heuvel

Thomas van den Heuvel

03/11/2020 16:53:21
Anchor link
Hm, hier staan een aantal interessante punten. Misschien is het interessant om private vs protected toch wat verder te verkennen.

Wat voor keuze je ook maakt (public, protected, private). Deze zou ergens op gebaseerd moeten zijn en een reden moeten hebben.

Dan is er misschien nog een andere overweging: elke keer als je deze afweging maakt, moet je hier tijd aan spenderen. Als je private meeneemt in deze overweging is het antwoord niet altijd even eenduidig. Misschien is het op enig moment onbekend of het handig is om iets later nog te kunnen extenden.

Je zou ook kunnen opteren voor een "simpele beslisboom": indien iets direct opvraagbaar zou moeten zijn via het object: maak het public. In alle andere gevallen maak je het (voorlopig) protected.

En ook: het gebruik van private kan zorgen voor "onvoorspelbaar" gedrag in je code. Indien je een methode extend in de veronderstelling dat deze protected was en kan het even duren voordat je in de gaten hebt dat stiekem toch de parent methode werd gebruikt omdat deze private was. In zekere zin zorgt private ook voor meer complexiteit.
Gewijzigd op 03/11/2020 16:54:16 door Thomas van den Heuvel
 
Ozzie PHP

Ozzie PHP

04/11/2020 01:06:12
Anchor link
>> Je zou ook kunnen opteren voor een "simpele beslisboom": indien iets direct opvraagbaar zou moeten zijn via het object: maak het public. In alle andere gevallen maak je het (voorlopig) protected.

Tja, keuzes ... persoonlijk denk ik dat het nooit verstandig is om een eigenschap public te maken, omdat je er dan geen enkele controle op hebt. Private, protected .. pfff ... mijn insteek komt inderdaad meer overeen met sommige van de reacties in jouw eerste link. Zet alles op private en wijzig het pas als het nodig is. Valt daar iets voor te zeggen? Ja, ik denk het wel. Valt er iets voor jouw insteek te zeggen ... zet het op protected voor het geval je het nodig hebt? Ja daar valt ook iets voor te zeggen. Ik denk dat het uiteindelijk dan toch een persoonlijke voorkeur is. Waar voel je je prettig bij? Plak je alvast een pleister om je vinger omdat je je misschien gaat stoten? Of plak je de pleister als je je gestoten hebt?
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2020 13:09:36
Anchor link
Ozzie PHP op 04/11/2020 01:06:12:
persoonlijk denk ik dat het nooit verstandig is om een eigenschap public te maken, omdat je er dan geen enkele controle op hebt.

Dit "probleem" los je op door magic getters en setters te gebruiken ala https://www.yiiframework.com/doc/guide/2.0/en/concept-properties . Iets wat je ooit als een (normale) public var bent begonnen kun je daarna altijd nog via een getter en/of setter "wegstoppen" (incl. controle slag). Zonder dat je je hele codebase door hoeft (of iemand die jouw code gebruikt!) om overal van ->prop ==> ->getProp() te maken (of ->prop = $value ==> ->setProp($value) ).

Ja, ik weet het: overhead. Maar elke keer als je van een functie gebruik maakt is dat een overweging (performance vs efficiency). Het voorkomt in ieder geval dat je op voorhand altijd al een setje getters en setters staat te "boiler platen" omdat je er "misschien ooit nog eens" een getter/setter voor nodig hebt.
 
Ozzie PHP

Ozzie PHP

04/11/2020 13:32:04
Anchor link
@Rob

Het hangt er vanaf hoe strict je je handhaving wil toepassen.

Als jij het prima vindt dat iemand dit kan doen ...

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php

$person
= new Person();
$person->age  = 'Rob';
$person->name = 30;

?>

Dan kun je met public properties werken. Als je er achteraf achterkomt dat je toch een vorm van validatie wil gaan toepassen, hoe weet je dan welke property het betreft in je magic set method?
 
Rob Doemaarwat

Rob Doemaarwat

04/11/2020 14:12:38
Anchor link
Dan verbouw ik intern de public $age naar protected $_age:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class Person extends BaseObject{
  protected $_age = null;

  protected function getAge(){
    return $this->_age;
  }


  protected function setAge($value){
    if(!is_numeric($value) || ($value < 0)) throw new \Exception('Not a valid age');
    $this->_age = $value;
  }
}


?>

De invulling van __get() en __set() in BaseObject zorgen er dan voor dat de calls bij getAge() en setAge() uitkomen (zoals ik al zei: overhead).

Voor "gebruikers" van dit object (ook intern) blijft alles dan gelijk.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php

$person
= new Person();
$person->age  = 'Rob'; //throws error
$person->name = 30;

?>
Gewijzigd op 04/11/2020 14:22:01 door Rob Doemaarwat
 
Ad Fundum

Ad Fundum

04/11/2020 15:22:50
Anchor link
Omgekeerd kan je de magic setter ook gebruiken om fouten in je code op te sporen, bv.:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class basisobject {
  function
__set(string $eigenschap, $waarde) : void {
    trigger_error(get_class($this) . '->'. $eigenschap . ' is niet gedefineerd');
  }
}


class mijnobject  extends basisobject {
  public $email;
  function
__construct() {$this->emails = 1;}
}


$object = new mijnobject;  // geeft error
?>


EDIT: naar aanleiding van Ward zijn opmerking even de code aangepast dat het goed fout gaat :)
En ik kom er net achter dat de magic setter niet meer nodig is in PHP8, dan krijg je sowieso een waarschuwing bij het benaderen van vanalles wat je niet eer hebt gedefineerd:
https://github.com/php/php-src/blob/php-8.0.0RC3/UPGRADING
Gewijzigd op 04/11/2020 15:52:58 door Ad Fundum
 
Ward van der Put
Moderator

Ward van der Put

04/11/2020 15:32:05
Anchor link
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Foo
{
    public int $email;
  
    function
__construct()
    {

        $this->$emails = 1;
    }
}


$foo = new Foo();
?>


Dat geeft sowieso al een fout:

Notice: Undefined variable: emails in ... on line 9
Gewijzigd op 04/11/2020 15:32:34 door Ward van der Put
 
Ozzie PHP

Ozzie PHP

04/11/2020 18:46:13
Anchor link
Rob Doemaarwat op 04/11/2020 14:12:38:
De invulling van __get() en __set() in BaseObject zorgen er dan voor dat de calls bij getAge() en setAge() uitkomen (zoals ik al zei: overhead).

De vraag is inderdaad of je dit moet willen.
 
- Ariën  -
Beheerder

- Ariën -

04/11/2020 19:57:20
Anchor link
Ward van der Put op 02/11/2020 13:31:56:
Afbeelding


Plaatje werkt niet meer? Upload hem anders even bij ImgBB.com.
 
Thom nvt

Thom nvt

05/11/2020 07:26:59
Anchor link
Ik ben sowieso een beetje allergisch voor getters en setters, al helemaal de magic variant. Het heeft zeker zijn plaats maar ik ben altijd terughoudend met een object volgooien met getDitDing() en setDitAndereDing().

Dit hele betoog is een kwestie van smaak maar naar mijn mening verdwijnt daarmee het expliciete gedrag van je code en daarmee de leesbaarheid.
Alles in je codebase, maakt niet uit waar, kan middels zo'n getter/setter een object ophalen uit de databse, deze muteren en weer opslaan. Dat hoeft geen probleem te zijn maar het maakt dingen als interne validatie en vervolgacties soms lastig te begrijpen.

Bijvoorbeeld dit stukje (psuedocode), dat kun je overal plakken en het werkt zonder morren:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$admin
= $db->findUserByName('admin');
$admin->setPassword('test123');
$db->save($admin);
?>


Als je het op een meer expliciete manier bouwt word duidelijker wat je wil doen. Dan is ook duidelijk dat daar wat validatie plaatsvind in je model (model is een laag, niet een enkel object).

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?php
$admin
= $db->findUserByName('admin');
try {
    $admin->changePassword('test123');
    $admin->saveChanges();
catch (PasswordException $e) {
    // Foutafhandeling
}?>


Dat kun je vervolgens zo ver uitbouwen als je wil, bijvoorbeeld met een command/query model maar dat gaat voor de meeste kleine applicaties veel te ver.

Punt is dat als je operaties/mutaties op je model (en eigenlijk alle methods in je codebase) expliciet maakt getters en setters niet nodig zijn en het gedrag je code veel leesbaarder word.
In bovenstaande voorbeelden is het logischer dat er bij changePassword() meer komt kijken dan alleen het setten van een nieuwe waarde, bijvoorbeeld een "your password was changed" mailtje versturen of lengte/complexiteit valideren.

Mocht iemand geïnteresseerd zijn kan ik wel een uitgebreid voorbeeld op GitHub o.i.d. plaatsen
Gewijzigd op 05/11/2020 07:27:43 door Thom nvt
 
Ward van der Put
Moderator

Ward van der Put

05/11/2020 10:08:56
Anchor link
Als $admin = $db->findUserByName('admin'), dan verwacht je bij $admin->changePassword('test123') een databaseoperatie of andere objectmutatie. Dat er plotseling ergens ook een e-mail uitgaat, is een ongewenst neveneffect. Niet onlogisch misschien, maar wel onzichtbaar en misschien ook niet altijd nodig maar je kunt het mailen hier niet meer verhinderen.

Daar is niets expliciet aan, maar een impliciete bron van lastig te vinden bugs: waar komt nou toch die mail vandaan?

Een soortgelijk probleem introduceer je hier, maar dan omgekeerd: er gebeurt juist te weinig.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$admin
= $db->findUserByName('admin');
$admin->changePassword('test123');
$admin->saveChanges();
?>


Kennelijk wijzigt changePassword() het wachtwoord niet, want daarvoor is aansluitend nog een saveChanges() nodig. En die gaat iemand vergeten of op de verkeerde plaats te vroeg of te laat aanroepen.

Eigenlijk is changePassword() dus meer een setter (wanneer we even vergeten en vergeven dat die stiekem ook nog een verborgen dubbele functie als password mailer heeft). Dan is dit juist explicieter:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$admin
= $db->findUserByName('admin');
$admin->setPassword('test123');
$db->save($admin);
?>
 
Ozzie PHP

Ozzie PHP

05/11/2020 10:58:56
Anchor link
@Thom nvt

Ik ben het eens met Ward. Code die uit zichzelf een mailtje verstuurt is zeer onwenselijk. Iemand die de code leest of ermee aan de slag gaat, ziet niet dat er een mail wordt verstuurd. Alleen degene die de betreffende functie changePassword heeft geschreven, is hiervan op de hoogte. Daarnaast moet je ervoor zorgen dat een functie maar 1 ding doet. Moet er nog iets anders gebeuren? Maak dan een nieuwe functie daarvoor.

In plaats van een set of change, kun je ook kiezen voor update. Dan is gelijk duidelijk dat het wachtwoord in de database wordt geüpdatet. Ook is het vreemd hoe jij hier een admin ophaalt en blijkbaar op voorhand op basis van de username al weet dat het een admin is. Is er maar 1 admin in je systeem? Haal die dan op met een aparte functie, of (logischer) gebruik $user als variabelenaam.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php

$user
= new User($id);  // $id wordt bepaald aan de hand van gebruikersnaam/wachtwoord
$user->updatePassword('blabla');
$user->mailCredentials();

?>

That's it. Hoe duidelijk wil je het hebben.

Ook die try en catch die jij erbij zet zogenaamd voor de duidelijkheid ... die zet je normaliter in je class (eenmalig) of overkoepelend in je framework. Maar waarom zou je die in je normale code flow zetten? Dat moet je dan iedere keer herhalen en dat wil je juist niet. Je moet het jezelf zo makkelijk mogelijk maken, waarvan bovenstaande code een voorbeeld is.
 
Thom nvt

Thom nvt

05/11/2020 11:38:43
Anchor link
Fair enough, op basis van bovenstaand (gebrekkig) voorbeeld ben ik het wel met jullie eens, daar had ik wat meer moeite in mogen stoppen.

Het is lastig om dit principe goed uit te leggen in één enkele forumpost, er komt nog heel wat meer bij kijken.
Het is gebaseerd op layered architecture/DDD en event-driven applicaties en daar zijn hele boeken over vol geschreven ("DDD in PHP" is een echte aanrader als je met complexe applicaties en business-cases werkt).

Het punt van expliciet maken gaat wel op, het voorbeeld van Ozzie is daarin wat beter neergezet.
Ik probeer de namen van functies en classes altijd zo dicht mogelijk op de realiteit te houden, je code is immers een model (vereenvoudiging) van de werkelijkheid.
In de "echte wereld" stel je bijvoorbeeld niet een wachtwoord in op een persoon. Je wijst hem toe of hij/zij wijzigt hem. "set" komt niet voor en is ambigu. Is het de eerste toewijzing? Een wijziging? Door wie mag dit gedaan worden?
Als je die business verantwoordelijkheid opsplitst in "changePassword" en "assignPassword" kun je bijvoorbeeld verschillende permissie-checks doen, beter een audit-trail bijhouden en, onder bepaalde voorwaarden, makkelijker debuggen.

Zoals gezegd had mijn voorbeel wat beter/uitgebreider kunnen zijn maar ik vind het lastig om dit soort principes kort en bondig uit te leggen.

De exception-handling in de normale code flow is een heel andere discussie die ik nu niet ga aanbreken.
Gewijzigd op 05/11/2020 11:41:44 door Thom nvt
 

Pagina: 1 2 volgende »

 

Dit topic is gesloten.



Overzicht

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.