Combineren van classes

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Pagina: « vorige 1 2

Ozzie PHP

Ozzie PHP

22/12/2022 22:18:33
Quote Anchor link
@Ad Fundum

>> In plaats daarvan kan je ook dezelfde functies schrijven waar geen geïnstantieerd object omheen zit

Wat bedoel je met een functie die data vasthoudt? Kun je een voorbeeldje geven?
 
PHP hulp

PHP hulp

03/02/2023 21:25:28
 
Ad Fundum

Ad Fundum

23/12/2022 11:16:33
Quote Anchor link
Ozzie PHP op 22/12/2022 22:18:33:
@Ad Fundum

>> In plaats daarvan kan je ook dezelfde functies schrijven waar geen geïnstantieerd object omheen zit

Wat bedoel je met een functie die data vasthoudt? Kun je een voorbeeldje geven?

De reden om value objects te gebruiken is bij gebrek aan ondersteuning voor eigen datatypen in PHP met bijvoorbeeld structs. De ondersteuning die je wilt is dat PHP je helpt met voorkomen van fouten. Als je een value object maakt met een X- en Y-coordinaat, dan doe je dat om zeker te weten dat er niets anders in zit, voor de 'type safety'.

Er is geen technische reden om value objects te gebruiken, ze verslechteren zelfs de performance. Nu is het performance verschil doorgaans te klein om geen value objects te gebruiken.

Het alternatief is dat je als programmeur zelf op blijft letten of de code wel klopt. Dat moet je toch op ontzettend veel manieren in PHP, dus als je dat toch al gewend bent, kan dit er ook nog wel bij. Je kunt dichter in de buurt komen met enums, waardoor de code ook al duidelijker wordt:

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
<?php
enum Pos: int { case X = 0; case Y = 1; }
$shape = [
  [
Pos::X->value => 0, Pos::Y->value => 0 ],
  [
Pos::X->value => 1, Pos::Y->value => 0 ],
  [
Pos::X->value => 0, Pos::Y->value => 1 ],
];

function
drawShape(array $shape) {  // specifieker dan 'array' kan niet..
  foreach ($shape as $pos) {
    print 'X: ' . $pos[Pos::X->value]
      .
', Y: ' . $pos[Pos::Y->value] . PHP_EOL;
  }
}


drawShape($shape);
?>

Ik geef direct toe dat ik de implementatie van 'backed' enums onelegant vind. Maar technisch kan je alles wat OOP doet ook zonder OOP doen. Zo is er bijvoorbeeld niets dat je alleen in C++ kan doen en niet in C.
Op die manier kan je met je eigen oplettendheid meer performance uit PHP krijgen.

Maar het is ook niet ongebruikelijk om value objects te gebruiken (wederom, bij gebrek aan beter), wanneer je meer controle wilt op types in PHP en je bereid bent om performance in te leveren.

Een van de voordelen van OOP is dat je modulair kan programmeren, met scheiding van verantwoordelijkheden. De noodzaak kwam voort uit het probleem dat procedurele programma's over het algemeen minder schaalbaar zouden zijn, vooral wanneer ze worden opgedeeld in meerdere afdelingen/organisaties.
Een probleem van de OOP-gedachte is de manier waarop inheritance werd geïmplementeerd, dat zorgt op zijn beurt voor te veel overhead (als je maar een fractie van de code wilt gebruiken moet je toch het hele object meenemen) en het introduceerde weer een eigen categorie problemen zoals het 'diamond problem'.

Code overhead is alleen een probleem wanneer de hardware niet snel genoeg mee groeit of wanneer stroom te duur wordt. En om andere OOP-problemen kan je heen werken. Maar het is minder stabiel waardoor bijvoorbeeld besturingssystemen eerder in C worden geschreven dan in C++.
Gewijzigd op 23/12/2022 11:23:29 door Ad Fundum
 
Ozzie PHP

Ozzie PHP

23/12/2022 14:03:53
Quote Anchor link
@Ad Fundum

Klinkt interessant! Ik ken dat enum helemaal (nog) niet.

Wat is precies de toepassing van zoiets?

Even een voorbeeld om de werking te begrijpen (laten we de OOP-aanpak even buiten beschouwing laten).

Even denken ...

Stel we hebben een object 'deur'. En die deur kan een 'status' hebben, namelijk 'open' of 'dicht'.

Als je dan zou controleren op status, dan zou je kunnen doen:

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

if ($deur->status() == 'open') {
  // dat doet de deur dicht
}

?>

Er zijn maar 2 juiste statussen, namelijk 'open' of 'dicht'. Nu zou ik me kunnen voorstellen dat een programmeur die de code niet kent de verkeerde status (een verkeerd woord) gebruikt, bijv. geopend in plaats van open. Of gesloten in plaats van dicht.

Kun je dit met zo'n enum ondervangen? En zo ja, hoe ziet de code van die enum er dan uit?
 
Ward van der Put
Moderator

Ward van der Put

23/12/2022 14:28:46
Quote Anchor link
Waar je voorheen constanten zou gebruiken:

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

declare(strict_types=1);

interface GenderInterface
{
    public const FEMALE = 'https://schema.org/Female';
    public const MALE = 'https://schema.org/Male';
}


?>


kun je nu enumeraties gebruiken:

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

declare(strict_types=1);

enum GenderType: string
{
    case
Female = 'https://schema.org/Female';
    case
Male = 'https://schema.org/Male';
}


?>


Hoewel het lijkt of we het topic aan het kapen zijn, hangen beide samen met value objects: een value object is namelijk immutable, dus net als constanten en enumeraties onveranderlijk.

Als je het goed doet althans. :)
 
Jorn Reed

Jorn Reed

23/12/2022 14:49:00
Quote Anchor link
Zijn enums handig om grote if statements en switches te ontwijken?
 
Ozzie PHP

Ozzie PHP

23/12/2022 15:49:17
Quote Anchor link
Ward van der Put op 23/12/2022 14:28:46:

...

kun je nu enumeraties gebruiken:

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

declare(strict_types=1);

enum GenderType: string
{
    case
Female = 'https://schema.org/Female';
    case
Male = 'https://schema.org/Male';
}


?>


Pretty cool :-)

* Als ik het goed begrijp zet je dus het return type (in dit geval een string) direct achter de naam van de enumeratie?

Voortbordurend op jouw voorbeeld. Zoiets als dit zou dan dus ook kunnen? (gaat enkel even om de techniek)

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
<?php

declare(strict_types=1);

enum GenderType: string
{
    case
Female = 'vrouw';
    case
Male = 'vrouw';
}


class Person {

private int $gender;

public function __construst($user_id) {
  ...
}


public function getGender() : string {
  return $this->gender === 1 ? 'man' : 'vrouw';
}

}


$person = new Person($user_id);

if ($person->getGender() === GenderType::Female) {
  ...
}


?>

Dat zou dus werken?

Toevoeging op 23/12/2022 15:49:47:

Jorn Reed op 23/12/2022 14:49:00:
Zijn enums handig om grote if statements en switches te ontwijken?

Op welke manier?
 
Jorn Reed

Jorn Reed

23/12/2022 15:55:11
Quote Anchor link
Ik maak vaak gebruik van een bepaalde api, en ik vang altijd al die status codes zoals 401 etc af. Maar dat worden altijd hele lange stukken code. Dacht misschien dat enums er goed voor van pas kwamen
 
Ozzie PHP

Ozzie PHP

23/12/2022 16:16:54
Quote Anchor link
>> Dacht misschien dat enums er goed voor van pas kwamen

Ik vermoed eigenlijk van niet, maar wellicht is de code zelf voor verbetering vatbaar?
 
Jorn Reed

Jorn Reed

23/12/2022 16:32:35
Quote Anchor link
Oh geen idee, ik vind sowieso veel if/else en een switch vrij lelijk, maar had niet echt een andere oplossing.
 
Ward van der Put
Moderator

Ward van der Put

23/12/2022 16:59:40
Quote Anchor link
Je kunt die HTTP-statuscodes vaak vereenvoudigd afhandelen: alles dat begint met een 4 is een clientfout en alles met een 5 een serverfout. Als je de respons ergens logt voor eventueel verder onderzoek, maakt het voor je applicatie verder eigenlijk niet uit of de respons nou een `500 Internal Server Error`, `503 Service Unavailable` of `504 Gateway Timeout` was.
 
Ad Fundum

Ad Fundum

24/12/2022 10:50:32
Quote Anchor link
Jorn Reed op 23/12/2022 14:49:00:
Zijn enums handig om grote if statements en switches te ontwijken?

Nee.

Om te beginnen zijn backed enums enorm beperkt. Ze kunnen zelfs niet alle scalars aan, alleen een int of string. Probeer je dat toch, dan krijg je deze melding:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
Fatal error: Enum backing type must be int or string


Een manier om grote if-statements te verhelpen is door te werken met interfaces en functie-pointers. In plaats van dit:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
switch $db_type {
  case 'a': query()...
  case 'b': query()...
  case 'c': query()...
  ...
}


Kan je dit doen:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
interface db_type {
  function query($sql) : dataset;
}
class A implements db_type { ... }
class B implements db_type { ... }
class C implements db_type { ... }
$db = new B;
...
$data = $db->query($sql);  // geen switch nodig want class implements interface
 
Ozzie PHP

Ozzie PHP

24/12/2022 20:02:56
Quote Anchor link
@Ad Fundum

Ik denk dat hij meer bedoelt als je een lijst van opties hebt waar iets mee moet gebeuren. Ik vermoed dat hij op basis van de error-code een tekst wil tonen.

In plaats van dat via een switch te doen, zou je de waardes in een array kunnen zetten. Maar wellicht is het handiger als Jorn even het betreffende stukje code laat zien, dan weten we waar we over praten.
 
Ward van der Put
Moderator

Ward van der Put

25/12/2022 12:19:26
Quote Anchor link
Volgens mij kun je inderdaad wel zeggen dat het aantal controles van condities met controlestructuren zoals if en switch afneemt wanneer je die condities formaliseert in abstracties. Klinkt misschien erg theoretisch, maar dat valt bij nader inzien wel mee als we in de praktijk duiken.

Terug naar een eerder voorbeeld. Stel, we willen het geslacht van een persoon vastleggen. Daar blijkt een norm voor te bestaan: ISO/IEC 5218 Information technology - Codes for the representation of human sexes.

We hebben alleen een tiny int nodig:

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

declare(strict_types=1);

class Person
{
    public int $gender = 0;
}


?>


Prachtig! We hebben een compacte class die zo min mogelijk geheugen gebruikt, zonder naïeve getters en setters. En de marketingafdeling kan trots zeggen dat onze software aan internationale standaardaarden voldoet.

Maar is deze code wel duidelijk? Kan een collega die deze class opent zien wat hij of zij met $gender kan doen? Snapt iedereen wel wat die 0 is?

Misschien moeten we onze ontwerpbeslissingen documenteren:

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
<?php

declare(strict_types=1);

/**
 * @sse https://www.iso.org/standard/36266.html ISO/IEC 5218:2004
 * @see https://en.wikipedia.org/wiki/ISO/IEC_5218 ISO/IEC 5218
 */

class Person
{
    /**
     * @var int $gender
     *   ISO/IEC 5218 compliant gender as an integer. MUST be 0 (Not known),
     *   1 (Male), 2 (Female), or 9 (Not applicable). Defaults to 0.
     */

    public int $gender = 0;
}


?>


Beter, maar we kunnen met documentatie alleen niet voorkomen dat de class verkeerd gebruikt wordt. De oude standaardoplossing in PHP is dan de controle over een property delegeren aan een setter.

In modern PHP zie de oude oplossing er bijvoorbeeld zo uit:

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
33
<?php

declare(strict_types=1);

/**
 * @see https://www.iso.org/standard/36266.html ISO/IEC 5218:2004
 * @see https://en.wikipedia.org/wiki/ISO/IEC_5218 ISO/IEC 5218
 */

class Person
{
    /**
     * @var int $gender
     *   ISO/IEC 5218 compliant gender as an integer. MUST be 0 (Not known),
     *   1 (Male), 2 (Female), or 9 (Not applicable). Defaults to 0.
     */

    protected int $gender = 0;
    
    public function setGender(int $gender): void
    {
        if ($gender === 0 || $gender === 1 || $gender === 2 || $gender === 9) {
            $this->gender = $gender;
        }
else {
            throw new \ValueError('Invalid gender');
        }
    }

    
    public function getGender(): int
    {
        return $this->gender;
    }
}


?>


Deze PHP-code lijkt op het eerste gezicht acceptabel. Zo ziet PHP er wel vaker uit, dus je leest er gedachtenloos overheen. En de code werkt voorlopig goed, dus vooral niet aankomen. If it ain't broke, don't fix it!

De code heeft echter een aantal problematische afhankelijkheden (dependencies):

1. De code is afhankelijk van externe standaarden. Hier is het weliswaar een open standaard en een internationaal erkende standaard, maar dat is en blijft een externe afhankelijkheid die door anderen wordt bepaald. Als de standaard verandert, hebben we een probleem.

2. We hebben de afhankelijkheid deels moeten uitschrijven in documentatie, anders is het ontwerp niet duidelijk. Zo ontstaat een tweede afhankelijkheid: een goed begrip en correct gebruik van de code is afhankelijk van de documentatie. En we weten allemaal dat niemand documentatie goed of helemaal leest.

3. Om verkeerd gebruik van de property $gender tegen te gaan, hebben we een setter moeten toevoegen. En omdat de property niet meer public is ook nog een getter. Dat maakt de code ongeveer twee keer zo langzaam: test het maar. En het maakt onderhoud van de code duurder: je hebt niet langer slechts één property, maar ook nog twee methoden. We kunnen dit eventueel oplossen door een factory of builder verantwoordelijk te maken voor het setten ven properties, maar dat introduceert dan weer een andere afhankelijkheid.

4. We kunnen bij wijzigingen niet slechts de code aanpassen, maar moeten ook altijd de documentatie bijwerken. De ervaring leert dat dat laatste vaak niet gebeurt. En daarmee krijgen we een ongewenst maar bekend fenomeen: Code comments are useful lies.

Afhankelijkheden maken code slecht leesbaar, traag, foutgevoelig, moeilijk te veranderen, duur in onderhoud, enzovoort.

We proberen afhankelijkheden daarom zoveel mogelijk te beperken, te internaliseren en expliciet te maken. Dat kan, bijvoorbeeld, met een enum:

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
<?php

declare(strict_types=1);

enum Gender: int
{
    case
NotKnown      = 0;
    case
Male          = 1;
    case
Female        = 2;
    case
NotApplicable = 9;
}


class Person
{
    public Gender $gender = Gender::NotKnown;
}


?>


Niet zaligmakend, wel véél beter.
 
Ozzie PHP

Ozzie PHP

25/12/2022 18:06:03
Quote Anchor link
@Ward

Zit me even af te vragen ... de cases hebben nu een int als waarde. Ik neem aan dat dat enkel zo is om de waardes in een database op te slaan, toch?

Ervan uitgaande dat bovenstaande beredenering klopt, dan haal ik in het geval van een man dus de int 1 op. Hoe zou ik die dan moeten setten? De class property accepteert enkel Gender. Hoe vertaal ik die 1 weer naar Male?

PS 1 NotKnown -> Unknown

PS 2 Fijne dagen!
 
Ad Fundum

Ad Fundum

26/12/2022 14:26:36
Quote Anchor link
Ozzie PHP op 23/12/2022 14:03:53:
@Ad Fundum

Klinkt interessant! Ik ken dat enum helemaal (nog) niet.
Wat is precies de toepassing van zoiets?

Stel we hebben een object 'deur'. En die deur kan een 'status' hebben, namelijk 'open' of 'dicht'.
Als je dan zou controleren op status, dan zou je kunnen doen:

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

if ($deur->status() == 'open') {
  // dat doet de deur dicht
}

?>

Er zijn maar 2 juiste statussen, namelijk 'open' of 'dicht'. Nu zou ik me kunnen voorstellen dat een programmeur die de code niet kent de verkeerde status (een verkeerd woord) gebruikt, bijv. geopend in plaats van open. Of gesloten in plaats van dicht.

Kun je dit met zo'n enum ondervangen? En zo ja, hoe ziet de code van die enum er dan uit?

Voor twee statussen kan je beter een boolean gebruiken, bijvoorbeeld $isOpen = false | true; // let zelf op: kan NULL zijn..

De meerwaarde is een consistente manier om met (meer dan 2) verschillende situaties om te gaan. In PHP is dat dus beperkt tot een int en string. Andere scalars als NULL, bool, float werken niet. Het grootste gemis is dat je een object kan gebruiken in een enum.
Met alleen een string of int (of 'pure', niet 'backed') voegt een enum niet zoveel toe aan PHP. Je kan er een status mee vasthouden, via een soort gecontroleerde (trefwoorden)lijst. Dat kan je ook anders doen, zoals Ward doet met eigen setters, dat geeft al aan dat de enum eigenlijk niets toe voegt. Het gedraagt zich als een reguliere class zonder inheritance volgens de documentatie. In het geval van int backed enums kan je ook af met een eigen lijstje consts.
Gewijzigd op 26/12/2022 14:27:30 door Ad Fundum
 
Ward van der Put
Moderator

Ward van der Put

27/12/2022 11:32:04
Quote Anchor link
Ozzie PHP op 25/12/2022 18:06:03:
Zit me even af te vragen ... de cases hebben nu een int als waarde. Ik neem aan dat dat enkel zo is om de waardes in een database op te slaan, toch?

Na wat refactoring hebben we een ISO-norm toegepast die het geslacht uitdrukt in kleine integers: die lenen zich inderdaad bij uitstek voor een TINYINT in een database.

Een backed enumeration lijkt op een key/value store en daarmee op een databasetabel. Elke case (ook wel member genoemd) heeft een name en een value:

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
<?php

declare(strict_types=1);

enum Gender: int
{
    case
NotKnown      = 0;
    case
Male          = 1;
    case
Female        = 2;
    case
NotApplicable = 9;
}


class Person
{
    public Gender $gender = Gender::NotKnown;
}



$person = new Person();

//> enum(Gender::NotKnown)
var_dump( $person->gender );

//> string(8) "NotKnown"
var_dump( $person->gender->name );

//> int(0)
var_dump( $person->gender->value );

?>


Ozzie PHP op 25/12/2022 18:06:03:
Ervan uitgaande dat bovenstaande beredenering klopt, dan haal ik in het geval van een man dus de int 1 op. Hoe zou ik die dan moeten setten? De class property accepteert enkel Gender. Hoe vertaal ik die 1 weer naar Male?


Een backed enumeration implementeert de interne PHP-interface BackedEnum. Die definieert onder andere de twee standaardmethoden from() en tryFrom(): daarmee kun je een integer uit de database weer omzetten in een case uit de enum.

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
<?php

// Rij uit database
$row = ['gender' => 2];

$person = new Person();
$person->gender = Gender::from($row['gender']);

//> enum(Gender::Female)
var_dump( $person->gender );

//> string(6) "Female"
var_dump( $person->gender->name );

//> int(2)
var_dump( $person->gender->value );

?>
 
Ozzie PHP

Ozzie PHP

27/12/2022 13:50:09
Quote Anchor link
Heldere uitleg Ward. Dankjewel!
 
Ad Fundum

Ad Fundum

27/12/2022 13:58:56
Quote Anchor link
Als de database geen TINYINT (8 bit) ondersteunt kan je ook een SMALLINT (16 bit) gebruiken.

Overigens moet je de PHP enum niet verwarren met een database ENUM . Een database ENUM nummert gewoon vanaf 0 (tenzij NULL), zoals een ENUM bedoeld is. PHP heeft met 'pure' enums uberhaupt geen waarde, en met 'backed' enums heb je alleen ints en strings, die ook totaal geen relatie hebben met een database ENUM.

Eigenlijk zet de benaming ENUM in PHP je op het verkeerde spoor (het zal ook eens niet..):
ENUM is afgeleid van enumerated type ofwel een opgesomd lijstje (numeriek). Maar zowel pure als backed enums hebben niets met numerieke opsomming te maken.

Dit om een Babylonische spraakverwarring over wat een ENUM is te voorkomen.
Gewijzigd op 27/12/2022 14:02:23 door Ad Fundum
 
Ozzie PHP

Ozzie PHP

27/12/2022 14:06:03
Quote Anchor link
Dat stukje scholing ontbreekt bij mij. Voor mij zijn enums compleet nieuw. De toepassing lijkt me wel handig.
 

Pagina: « vorige 1 2



Overzicht Reageren

 
 

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.