PHP - Inheritance - php

I am currently learning OOP concepts. I have used CodeIgniter, I know it has OOP concepts but I don't understand how it works. I just use the methods in the documentation.
I am now on the inheritance part.
Here is my code:
<?php
class Artist {
public $name;
public $genre;
public $test = 'This is a test string';
public function __construct(string $name, string $genre) {
$this->name = $name;
$this->genre = $genre;
}
}
class Song extends Artist {
public $title;
public $album;
public function __construct(string $title, string $album) {
$this->title = $title;
$this->album = $album;
}
public function getSongArtist() {
return $this->name;
}
}
$artist = new Artist('Joji Miller', 'Lo-Fi');
$song = new Song('Demons', 'In Tounges');
echo $song->getSongArtist(); // returns nothing
From what I understand inheritance will let me access properties and methods from the parent class.
In my example I have instantiate the Artist. So now I have Joji Miller as artist name.
Now, if I instantiate the Song class, I thought that I can access the artist name since I am extending the Artist class. But it is just empty.
Would you help me understand why it is not getting the artist name?
Hope I explained myself clearly. Thankyou..

Heh. Learning "oop principles" from CodeIgniter is like going to North Korea to study democracy. And you have already learned the wrong things.
The extends keyword should be read as "is special case of". As in class Admin extends User means that the admin instance is a more specialized case of generic user.
And that's where you are wrong. Song is not a subtype of an artist.
Instead the song has an artist, which performs it. As in:
$artist = new Artist('Freddie Mercury');
$song = new Song('Another One Bites the Dust', $artist);
echo $song->getArtist()->getName();
Another bad practice, that you seem to have picked up: stop defining class variables as public. That breaks the encapsulation. Instead those values should be assigned using methods, because then you will e able top track, format and validate those values.

First of all in your case you have not the best example of inheritance...
And it leads to confusion...
I'd rather advise you to have in base class base behavior related to all descendants, like here.
Base class:
<?php
class SomethingWithName
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
Your classes:
class Artist extends SomethingWithName
{
private $genre;
public function __construct(string $name, string $genre)
{
parent::__construct($name);
$this->genre = $genre;
}
public function getGenre(): string
{
return $this->genre;
}
}
class Song extends SomethingWithName
{
private $album;
private $artist;
public function __construct(string $name, string $album, Artist $artist)
{
parent::__construct($name);
$this->album = $album;
$this->artist = $artist;
}
public function getAlbum(): string
{
return $this->album;
}
public function getArtist(): Artist
{
return $this->artist;
}
}
Result:
$a = new Artist('Joji Miller', 'Lo-Fi');
$s = new Song('Demons', 'In Tounges', $a);
var_export([
$s->getName(), // Demons
$s->getAlbum(), // In Tounges
$s->getArtist()->getName(), // Joji Miller
$s->getArtist()->getGenre(), // Lo-Fi
]);

That's because when defining the Song class's __construct() function you are overriding the Artist's __contruct() function. Which is the function that gets called when you do $song = new Song(...)
Using the keyword parent:: you can access the parent's classes functions.
The following works.
<?php
class Artist {
public $name;
public $genre;
public $test = 'This is a test string';
public function __construct(string $name, string $genre) {
$this->name = $name;
$this->genre = $genre;
}
}
class Song extends Artist {
public $title;
public $album;
public function __construct(string $title, string $album, string $ArtistName, string $genre) {
$this->title = $title;
$this->album = $album;
parent::__construct($ArtistName, $genre);
}
public function getSongArtist() {
return $this->name;
}
}
$artist = new Artist('Joji Miller', 'Lo-Fi');
$song = new Song('Demons', 'In Tounges','Joji Miller', 'Lo-Fi');
echo $song->getSongArtist(); // returns nothing

Related

Php closure in a class method call

Lets say I have two classes Car and Owner
owner.php =>
class Owner {
public $name;
public function setName($name) {
$this->name = $name;
}
}
and respectively the car.php =>
class Car {
public $owner;
public function setOwner(Owner $owner) {
$this->owner = $owner;
}
}
To set and call methods I normally use this approach =>
$owner = new Owner;
$owner->setName('sam');
$car = new Car;
$car->setOwner($owner);
But what if I want it to do using Closure like below, how do I change the setOwner method accordingly?
$car = new Car;
$car->setOwner(function(Owner $owner) {
$owner->setName('sam');
});
What I want to do is something similar to Laravel where
User::where('car_id', $carId)
or
User::where(function($query) {
//code here
})
Car::setOwner() needs to call its argument with an Owner object.
class Car {
public function setOwner(Callable $ownerSetter) {
$o = new Owner;
$ownerSetter($o);
}
}
But this is a strange way to use a closure. A better example might be:
class Car {
private $owner;
public function setOwner($owner) {
$this->owner = $owner;
}
public function doSomethingToOwner(Callable $something) {
$something($this->owner);
}
}
$car->setOwner($owner);
$car->doSomethingToOwner(function($owner) {
echo $owner->name;
});

understanding dependency injection in PHP

class Author {
private $firstName;
private $lastName;
public function __construct($firstName, $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function getFirstName() {
return $this->firstName;
}
public function getLastName() {
return $this->lastName;
}
}
class Question {
private $author;
private $question;
public function __construct($question, Author $author) {
$this->author = $author;
$this->question = $question;
}
public function getAuthor() {
return $this->author;
}
public function getQuestion() {
return $this->question;
}
}
Class author is injected into the constructor of Question class am I correct? but how to call the Question class to get the author's name?
$question = new Question('What is PHP', 'Adam');
$question->getFirstname;
like this? I assume Question class inherited Author class so Question's instance can use the function of Author Class?
Simple
echo $question->getAuthor()->getFirstName();
Think of it this way if it helps
$author = $question->getAuthor();
echo $author->getFirstName();
Also note that you can't construct a Question with the string "Adam", you need to pass an instance of Author
$question = new Question('What is PHP', new Author('Adam', 'Lastname'));
You could create a new method:
class Question
{
// ...
function getAuthorFirstname()
{
return $this->author->getFirstname();
}
}
$question = new Question(.., new Author(..., ...));
echo $question->getAuthorFirstname();
Or, if you don't really care about Law of Demeter or feel that it doesn't apply:
$question = new Question(.., new Author(..., ...));
echo $question->getAuthor()->getFirstname();
In the end it all comes down to striking a balance between information hiding and pragmatism.

Getting class of variable

How can I figure out in what class a reference to a variable was initiated (and currently exists)?
Example:
<?php
class MyClass {
public $array = array(
"this",
"is",
"an",
"array"
);
}
$class = new MyClass();
$arrayReference = &$class->array;
GetClassForVariable($arrayReference); //Should return "MyClass"
?>
My best bet is some kind of Reflection, but I haven't found any functions that seem suitable for this.
Edit:
A better suited example for what I want is the following:
<?php
class API_Module {
public $module;
public $name;
private $methods = array();
public function __construct($module, $name) {
$this->module = $module;
$this->name = $name;
$this->methods["login"] = new API_Method($this, "login", "Login");
}
public function GetMethod($method) {
return $this->methods[$method];
}
public function GetURL() {
return $this->module; //Should return "session"
}
}
class API_Method {
public $method;
public $name;
private $parentReference;
private $variables = array();
public function __construct(&$parentReference, $method, $name) {
$this->parentReference = $parentReference;
$this->method = $method;
$this->name = $name;
$this->variables["myvar"] = new API_Variable($this, "myvar");
}
public function GetURL() {
return $this->GetParentURL() . "/" . $this->method; //Should return "session/login"
}
public function GetVariable($variableName) {
return $this->variables[$variableName];
}
private function GetParentURL() {
// Need to reference the class parent here
return $this->parentReference->GetURL();
}
}
class API_Variable {
public $name;
private $parentReference;
public function __construct(&$parentReference, $name) {
$this->parentReference = $parentReference;
$this->name = $name;
}
public function GetURL() {
return $this->GetParentURL() . "/" . $this->name; //Should return "session/login/myvar"
}
private function GetParentURL() {
// Need to reference the class parent here
return $this->parentReference->GetURL();
}
}
$sessionModule = new API_Module("session", "Session");
var_dump($sessionModule->GetMethod("login")->GetVariable("myvar")->GetURL()); //Should return "session/login/myvar"
?>
Now, this works fine, but I'd love to be able to do this without using $parentReference in every single subvariable. It might not be possible, but I'd love to know whether it is or not.
For your example:
$class = new MyClass();
$arrayReference = &$class->array;
GetClassForVariable($arrayReference); //Should return "MyClass"
to find out to which variable originally the alias $arrayReference refers to is not possible in PHP. There is no function available resolving the aliases.
Additionally $class->array is just a variable on it's own. So you would also need to find out based on a value in which class it was defined. That is not possible as well, similar to that PHP does not offer anything to resolve a variable alias, it also does not offer anything to learn about the definition of a variable.
So in short PHP does not have a ReflectionVariable class available ;) I wonder if it is even possible.
The get_class() function should work:
http://php.net/manual/en/function.get-class.php
I agree with GRoNGoR that you shouldn't need to get the parent class of a property of an instantiated object. You could instead just get the name of the class before accessing the property. For example:
$class = new MyClass();
$parent_class = get_class($class); // returns "MyClass"
$arrayReference = &$class->array;
Not sure why you'd need the parent class of the property when you have the object instance and can easily get the parent class from there.

Use method of parent class when extending

Probably a silly question.. but how do I correctly use the methods of class Test in class Testb without overriding them?
<?php
class Test {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
<?php
class Testb extends Test {
public function __construct() {
parent::__construct($name);
}
}
<?php
include('test.php');
include('testb.php');
$a = new Test('John');
$b = new Testb('Batman');
echo $b->getName();
You need to give the Testb constructor a $name parameter too if you want to be able to initialize it with that argument. I modified your Testb class so that its constructor actually takes an argument. The way you currently have it, you should not be able to initialize your Testb class. I use the code as follows:
<?php
class Test {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
class Testb extends Test {
// I added the $name parameter to this constructor as well
// before it was blank.
public function __construct($name) {
parent::__construct($name);
}
}
$a = new Test('John');
$b = new Testb('Batman');
echo $a->getName();
echo $b->getName();
?>
Perhaps you do not have error reporting enabled? In any event, you can verify my results here: http://ideone.com/MHP2oX

Get data from object composition

Let's say I have 3 objects : "Place", "Person", "Action".
Depending on the place where is the person and the age of this person, this person can do different action.
For example :
$place->person->action->drive(); // OK if place is "parking" and "person" is 18+
$place->person->action->learn(); // OK if the place is "school" and person is less than 18.
How can I access the data about the objects "Person" and "Place" from the Action class ?
Classes examples :
class Place {
public $person;
private $name;
function __construct($place, $person) {
$this->name = $place;
$this->person = $person;
}
}
class Person {
public $action;
private $name;
private $age;
function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
$this->action = new Action();
}
}
class Action {
public function drive() {
// How can I access the person's Age ?
// How can I acess the place Name ?
}
public function learn() {
// ... Same problem.
}
}
I think I could transmit "$this" from Person to Action when I create the Action Object (ie. $this->action = new Action($this)), but what about the Place data ?
It doesn't make sense to make Person a property of Place nor Action a property of Person.
I'd be more inclined to create public getters for Person and Place's properties and either make them injectable properties of Action or at least pass them as arguments to Action's methods, eg
class Place
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
class Person
{
private $name;
private $age;
public function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
public function getName()
{
return $this->name;
}
public function getAge()
{
return $this->age();
}
}
class Action
{
private $person;
private $place;
public function __constuct(Person $person, Place $place)
{
$this->person = $person;
$this->place = $place;
}
public function drive()
{
if ($this->person->getAge() < 18) {
throw new Exception('Too young to drive!');
}
if ($this->place->getName() != 'parking') {
throw new Exception("Not parking, can't drive!");
}
// start driving
}
}

Categories