Hur är objektorienterad php-kod tänkt att se ut?

Permalänk

Hur är objektorienterad php-kod tänkt att se ut?

Tjo! Jag har en "enkel(?)" fråga om hur objektorienterad php-kod är tänkt att se ut, för att kunna förstå vad hela tanken med objektorienterad php är. Jag har hittills under min Webbutvecklingsutbildning på distans enbart programmerat funktionellt (JavaScript och just nu php).

Jämför objektorienterad php med att bara ha tonvis med include av diverse enskilda php-filer som kanske innehåller en eller två funktioner som du då skickar ett eller flera argument till som sedan returnerar något som du gör något med.

Vad jag förstår med objektorienterat är att du kan skydda variabler (t.ex. protected $variableName;) för att förhindra buggar från exempelvis samma variabelnamn inom samma scope i php-koden. Och så verkar man kunna använda sig av "datakontroller" inuti olika set- respektive get-klassmetoder för att komma åt och ändra data än att råka skriva $variabelName = "nytt värde"; någon annanstans.

Jag ställer denna fråga då jag upptäckt att projektuppgiften i en av mina två pågående distanskurser inom Webbutvecklingsprogrammet ska lösas med hjälp av objektorienterad php (så jag slipper det inte efter bara ett! ). Databaser ska även användas så jag antar att PDO blir starkt rekommenderat också.

Svar från en lärare i distanskursen om grejen med objektorienteringen inom php var bland annat följande nedan (momentuppgiften rör sig om en Todo App i ett formulär där du kan rensa hela listan och ta bort separata tillagda "Att göra"-saker):

Citat:

"Jag skulle säga att du lägger lite för mycket av logiken i klassen nu som gör den svår att återanvända. Fokusera på att endast lägga logik som hör till själva "att-göra listan" i klassen, inte hantering av formuläret.

Något som är bra att ha i åtanke är att inte försöka skriva ut så mycket direkt från klassen med echo, utan istället returnera en bool, så du kan styra flödet i moment3.php-filen och därifrån skriva ut eventuella meddelanden. Då blir koden mer modulär."

I mitt senaste inlämnade moment 3 så hade jag bara en klassinstans av klassen och en poäng med OOP är väl att kunna köra olika separata klassinstanser? Men om jag bara behöver köra en klassinstans, varför då ens köra med OOP där? Kan väl upplevas som överflödigt? Nu är det förvisso kurskrav så där kommer jag inte undan, men jag försöker förstå bara!

En sak till jag tänker med OOP som kanske är "Quality of Life" är att baka in mycket "initieringskod" inuti __Konstruktören i respektive klass så att jag bara skickar med initiala värden vid klassinstansiering för att sedan bearbeta dessa med diverse set- & get-klassmetoder i samma klass.

Å andra sidan skulle jag lika gärna kunna ha separata funktioner för detta som jag bara anropar kors och tvärs med samma variabler? Just nu kan jag bara se "buggfriheten" med OOP och inte riktigt det "modulära". Men det är nog för att jag bara har skapat och använt mig av en klass och inte flera som "samverkar" eller "kommunicerar" med varandra än så länge.

Hur skulle du svara på min snurriga fråga kring det hela?!

Tack för svar på förhand!

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem

Man kan väl tänka som OOP att skapa lite regler hur objekt/instanser kan påverkas (dvs. via metoder) även om det går att ha samma tänk i icke OOP. Det du mycket riktigt påpekat att "skydda" interna state på ett objekt så att säga. I PHP skickas alla objekt som skapas "by reference" också vilket gör att inga kopior på dina objekt görs.

Annars brukar OOP ofta innefatta mockbara testklasser, dependency injection, utbytbara/utbyggbara impementationer av klasser ("modulära egenskaper"). I PHP's fall autoload är nog väldigt standard också. Sök och läs vidare på dessa om du finner intresse.

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

[…] en Todo App i ett formulär där du kan rensa hela listan och ta bort separata tillagda "Att göra"-saker):I mitt senaste inlämnade moment 3 så hade jag bara en klassinstans av klassen och en poäng med OOP är väl att kunna köra olika separata klassinstanser? Men om jag bara behöver köra en klassinstans, varför då ens köra med OOP där? Kan väl upplevas som överflödigt?
[…]
Å andra sidan skulle jag lika gärna kunna ha separata funktioner för detta som jag bara anropar kors och tvärs med samma variabler?

Du ställer rätt frågor. Fortsätt grotta tills du förstått fördelarna och nackdelarna med olika förhållningssätt, för det finns knappast något rätt - bara olika sätt att göra bort sig…

I en TODO-app tänker jag mig att man har flera TODOItems, vilket kan vara en lämplig klass, kanske med rubrik, beskrivning och eventuellt en typ (städning, skola, ekonomi…) och kanske ett databas-id. Om man dynamiskt ska kunna lägga till nya typer och spara dessa kan TODOItemType vara en klass. Om appen ska användas av flera användare har de minst varsin lista av TODOItems vilket ger en/två klasser till. Även i den absolut minimala inkarnationen av en sådan app behöver man hantera mer än en instans av TODOItem — att komma ihåg en enda sak att göra är inget man behöver en app till.

Att hålla reda på vilka fält ett objekt har är en av poängerna med OOP. I Javascript är ett objekt en associativ array där man kan lägga in vad som helst, vilket leder till mittObjekt.stafvel-buggar. I kompilerade objektorienterade språk ser kompilatorn till att du bara accessar fält som existerar och innehåller den förväntade typen. Grovt generaliserat, men det är hur som helst en feature man vill ha i sin kodbas. Ovanstående har en poäng även om man bara har en enda instans av en klass, ”Singleton”, i sitt körande program.

Enkapsulering av affärslogik är en annan av poängerna med OOP. I språket C, som inte är direkt objektorienterat deklarerar man en struct som definierar vilka fält ett ”objekt” har och lägger alla metod-deklarationer som manipulerar structen i samma fil. Poängen är att inte sprida ut affärslogiken som hanterar viss data. Objektorienterade språk har kompilatorstöd för att ”tvinga” dig att strukturera koden så. Lösa funktioner som kan ändra i objektets interna tillstånd och som är deklarerade i någon modul som du kanske inte ens har källkoden till är… inte kul.

Permalänk
Skrivet av toj_ts:

Man kan väl tänka som OOP att skapa lite regler hur objekt/instanser kan påverkas (dvs. via metoder) även om det går att ha samma tänk i icke OOP. Det du mycket riktigt påpekat att "skydda" interna state på ett objekt så att säga. I PHP skickas alla objekt som skapas "by reference" också vilket gör att inga kopior på dina objekt görs.

Annars brukar OOP ofta innefatta mockbara testklasser, dependency injection, utbytbara/utbyggbara impementationer av klasser ("modulära egenskaper"). I PHP's fall autoload är nog väldigt standard också. Sök och läs vidare på dessa om du finner intresse.

Tack för svaret! När jag kollade upp DI så upptäckte jag något som heter "OOP SOLID" som består av 5 principer inom OOP. Jag vet inte hur mycket det applicerar till OOP PHP och OOP JS (i den utsträckning det används) dock: https://betterprogramming.pub/solid-principles-with-almost-re...

Just "1. Single Responsibility" motsäger då lite det jag gjort i min Todo.class.php där jag har alla klassmetoder och klassvariabler i en och samma klass. Dessutom ser jag nu i min php-distanskurs att de kommer fortsätta med att baka in allt i en och samma klassfil (bilden nedan är exempel på att göra en databas i OOP PHP som lagrar&hanterar universitetskurser):

Eller vad handlar egentligen 1. Single Responsibility om? Går det - samtidigt principen följs - att ha flera klassmetoder i en och samma klass om de endast hanterar de deklarerade klassvariablerna och inga "externa" variabler?

"4. Interface Segregation Principle" verkar handla om att skapa strikta datatyper för funktioner som sedan "implements" i en given klass? Samtidigt verkar man ska behöva skapa en interface för varje enskild funktion som klasser sedan kan "implements". Har jag förstått rätt att om en interface har två eller fler "funktioner" inuti i sig så måste alla dessa användas så fort inerface-namnet "implements" av en klass? Vad fyller då egentligen interface för syfte mer än att etablera strikta datatyper?

"5. Dependency Inversion Principle" tycker jag låter väldigt sunt förnuft för att inte försöka ta den lata vägen. Den verkar handla om att om du har en klass med en konstruktör så ska denna konstruktör helst inte innehålla en annan "new ClassName();". Alltså:

class Todo{ private $todo; function __construct($todo = new NewTodo){ ...} function getTodo(){ return $todo; } } class NewTodo{ private $newTodo; function getNewTodo(){ return $this->newTodo->getTodo(); } }

En annan variant att göra fel här - om jag förstått det rätt - är att du har en klassmetod inuti en klass som egentligen är en klassmetod inuti en helt annan klass som förstnämnda klassen försöker nyttja och då blir alltså den klassen helt "beroende/dependent(?)" av sistnämnda klassens existens? (se klass NewTodo i samma kodsnutt ovan)

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Skrivet av KAD:

Du ställer rätt frågor. Fortsätt grotta tills du förstått fördelarna och nackdelarna med olika förhållningssätt, för det finns knappast något rätt - bara olika sätt att göra bort sig…

I en TODO-app tänker jag mig att man har flera TODOItems, vilket kan vara en lämplig klass, kanske med rubrik, beskrivning och eventuellt en typ (städning, skola, ekonomi…) och kanske ett databas-id. Om man dynamiskt ska kunna lägga till nya typer och spara dessa kan TODOItemType vara en klass. Om appen ska användas av flera användare har de minst varsin lista av TODOItems vilket ger en/två klasser till. Även i den absolut minimala inkarnationen av en sådan app behöver man hantera mer än en instans av TODOItem — att komma ihåg en enda sak att göra är inget man behöver en app till.

Att hålla reda på vilka fält ett objekt har är en av poängerna med OOP. I Javascript är ett objekt en associativ array där man kan lägga in vad som helst, vilket leder till mittObjekt.stafvel-buggar. I kompilerade objektorienterade språk ser kompilatorn till att du bara accessar fält som existerar och innehåller den förväntade typen. Grovt generaliserat, men det är hur som helst en feature man vill ha i sin kodbas. Ovanstående har en poäng även om man bara har en enda instans av en klass, ”Singleton”, i sitt körande program.

Enkapsulering av affärslogik är en annan av poängerna med OOP. I språket C, som inte är direkt objektorienterat deklarerar man en struct som definierar vilka fält ett ”objekt” har och lägger alla metod-deklarationer som manipulerar structen i samma fil. Poängen är att inte sprida ut affärslogiken som hanterar viss data. Objektorienterade språk har kompilatorstöd för att ”tvinga” dig att strukturera koden så. Lösa funktioner som kan ändra i objektets interna tillstånd och som är deklarerade i någon modul som du kanske inte ens har källkoden till är… inte kul.

När du säger "fält", menar du då dess klassvariabler? Det här med att göra en klass med bara en klassmetod inuti och sedan kanske en sekventiell rad med anrop till respektive klass för att få önskat resultat innebär ju ändå att jag måste underhålla på minst två ställen: 1) Lägga till ny klass 2) Lägga till nytt anrop på rätt plats i sekventiella anropsflödet om jag skapat ny klass som ska nyttjas.

Jag hittade ett matnyttigt inlägg om just SRP från SOLID-konceptet: https://thevaluable.dev/single-responsibility-principle-revis... Författaren menar avslutningsvis på att:

Citat:
  • The Single Responsibility Principle is ambiguous and lack preciseness.

  • We should decompose the problems we’re trying to solve, even before coding.

  • Coupling and cohesion are not booleans. We can’t achieve total cohesion with no coupling, but we should try to maximize the first while minimizing the second.

  • We should organize our code depending on the decomposition of the problem, with well-defined interfaces respectful of the cohesion and coupling we want between our modules.

Med "decomposition" tror jag författaren syftar på "nedbrytning av stora problem till mindre problem"? Är det "coupling" som pratar om i vilken utsträckning en klass är beroende av en annan klass?

En intressant sak med interface och SRP var denna artikel: https://dev.to/tamerlang/understanding-solid-principles-depen... som gav exempel på hur det går att ha en interface med ett antal funktioner som du bara "vet" är fundamentalt nödvändiga i klasser som väljer att "implements" interfacet ifråga:

interface DatabaseInterface { public function get(); public function insert(); public function update(); public function delete(); } class MySQLDatabase implements DatabaseInterface { // fields public function get(){ // get by id } public function insert(){ // inserts into db } public function update(){ // update some values in db } public function delete(){ // delete some records in db } }

Det "fundamentala" för en databas är väl att minst kunna CRUD:a vilket då "DatabaseInterface" tvingar en att inkludera som klassmetoder för varje klass som ska "implements" den?

I början från den sidan så visar den något där den påstår att en klass är "beroende" av en annan klass (via dess klassmetoder):

class BudgetReport { public $database; public function __construct($database) { $this->database = $database; } public function open(){ $this->database->get(); } public function save(){ $this->database->insert(); } }

Om jag förstått koden ovan rätt så innebär klassmetoderna att en annan klass som skickats in i dess konstruktör bör i sin tur ha klassmetoderna get() och insert() annars blir klassen BudgetReport's egna klassmetoder meningslösa?

Jag ber om ursäkt för alla A4-inlägg. Jag "tänker högt" i hopp om att få mitt tänkande rättat av flera gånger mer erfarna webbkodare här!

Jag tror också det är matnyttigt för andra nybörjar-webbkodare likt mig som nyss hoppat på OOP-tåget!

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Med "decomposition" tror jag författaren syftar på "nedbrytning av stora problem till mindre problem"?

Stämmer

Skrivet av WebbkodsLärlingen:

Är det "coupling" som pratar om i vilken utsträckning en klass är beroende av en annan klass?

Stämmer

Skrivet av WebbkodsLärlingen:

Det "fundamentala" för en databas är väl att minst kunna CRUD:a vilket då "DatabaseInterface" tvingar en att inkludera som klassmetoder för varje klass som ska "implements" den?

Stämmer

Skrivet av WebbkodsLärlingen:

Om jag förstått koden ovan rätt så innebär klassmetoderna att en annan klass som skickats in i dess konstruktör bör i sin tur ha klassmetoderna get() och insert() annars blir klassen BudgetReport's egna klassmetoder meningslösa?

Stämmer. Detta kallas för "dependency injection". I detta fall bör man även använda "type declaration" (aka "type hinting"), dvs:

public function __construct(DatabaseInterface $database)

BudgetReport behöver nu en klass som implementerar DatabaseInterface för att fungera. För BudgetReport spelar det ingen roll om det är MySQLDatabase eller t.ex PostgreSQLDatabase, så länge databasklassen implementerar DatabaseInterface. BudgetReport behöver bara veta att klassen innehåller de metoder som DatabaseInterface tvingar. Ett bra exempel på hur interface ska användas.

Visa signatur

MacBook Pro 14" | M1 Pro 10/16-Core | 32GB | 1TB
Legion Pro 16" | Ryzen 7 5800H | 16 GB | 1TB | GeForce RTX 3060

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Tack för svaret! När jag kollade upp DI så upptäckte jag något som heter "OOP SOLID" som består av 5 principer inom OOP. Jag vet inte hur mycket det applicerar till OOP PHP och OOP JS (i den utsträckning det används) dock: https://betterprogramming.pub/solid-principles-with-almost-re...

Just "1. Single Responsibility" motsäger då lite det jag gjort i min Todo.class.php där jag har alla klassmetoder och klassvariabler i en och samma klass. Dessutom ser jag nu i min php-distanskurs att de kommer fortsätta med att baka in allt i en och samma klassfil (bilden nedan är exempel på att göra en databas i OOP PHP som lagrar&hanterar universitetskurser):
<Uppladdad bildlänk>

Eller vad handlar egentligen 1. Single Responsibility om? Går det - samtidigt principen följs - att ha flera klassmetoder i en och samma klass om de endast hanterar de deklarerade klassvariablerna och inga "externa" variabler?

"4. Interface Segregation Principle" verkar handla om att skapa strikta datatyper för funktioner som sedan "implements" i en given klass? Samtidigt verkar man ska behöva skapa en interface för varje enskild funktion som klasser sedan kan "implements". Har jag förstått rätt att om en interface har två eller fler "funktioner" inuti i sig så måste alla dessa användas så fort inerface-namnet "implements" av en klass? Vad fyller då egentligen interface för syfte mer än att etablera strikta datatyper?

"5. Dependency Inversion Principle" tycker jag låter väldigt sunt förnuft för att inte försöka ta den lata vägen. Den verkar handla om att om du har en klass med en konstruktör så ska denna konstruktör helst inte innehålla en annan "new ClassName();". Alltså:

class Todo{ private $todo; function __construct($todo = new NewTodo){ ...} function getTodo(){ return $todo; } } class NewTodo{ private $newTodo; function getNewTodo(){ return $this->newTodo->getTodo(); } }

En annan variant att göra fel här - om jag förstått det rätt - är att du har en klassmetod inuti en klass som egentligen är en klassmetod inuti en helt annan klass som förstnämnda klassen försöker nyttja och då blir alltså den klassen helt "beroende/dependent(?)" av sistnämnda klassens existens? (se klass NewTodo i samma kodsnutt ovan)

När man använder dependency injection brukar man injecta och vara beroende ett interface snarare än den direkta implementationen. Lätt att byta saker då för test osv.

T ex när jag ropar på ITodoRepository ge mig TodoRepository osv.

Vad gäller SOLID verkar den vara helig men finns de som kritiserar det och menar att vissa saker är ologiska eller overkill osv.