Passing arguments to an object's method

Hi,

I am trying learn Object Oriented Programming in PHP. To do so I am going to try and rewrite using OOP a php script that I wrote previously. The script as input a series of rows of data, each row consisting of three variables - room name, width in metres and length in metres. It then outputs Those three values along with the area in metres and the width in feet and inches (eg 7’ 6’') and the length in feet and inches and the area in square metres (in the UK some realtors/estate agents give the dimensions in only metres but I find imperial units easier to visualise).

My first bit of code is to create the Class Room:

class Room{
		public $width;
		public $length;
		public $roomName;

		public function imperialWidth(){		
			$valInFeet = $this->width*3.2808399;
			$valFeet = (int)$valInFeet;
			$valInches = round(($valInFeet-$valFeet)*12);
			return $valFeet."′ ".$valInches."″";
		}

		public function imperialLength(){		
			$valInFeet = $this->length*3.2808399;
			$valFeet = (int)$valInFeet;
			$valInches = round(($valInFeet-$valFeet)*12);
			return $valFeet."′ ".$valInches."″";
		}
}

I am able to pass values to the properties and and get values from the methods. But it seems to me that there is duplication that I should be able to overcome - the two methods that convert the metres into a string that reflects the imperial value are identical for length and for width apart from the fact that one uses the width and one uses the length. I think I should somehow be creating just one method and then passing different parameters to it, within the Room object? But I can’t figure out how to do this.

  1. Am I on the right track?
  2. How would I do this?

Thank you

This works, but is there a better way?

				class Room{
					public $width;
					public $length;
					public $roomName;
					
					public function convertToImperial($x){		
					$valInFeet = $x*3.2808399;
					$valFeet = (int)$valInFeet;
					$valInches = round(($valInFeet-$valFeet)*12);
					return $valFeet."′ ".$valInches."″";
				}				

					
					public function imperialWidth(){		
						return $this->convertToImperial($this->width);	
					}

					public function imperialLength(){		
						return $this->convertToImperial($this->length);	
					}
			}
1 Like

No this is perfectly acceptable and actually very good refactoring. This is what you want to be seeing. :slight_smile:

1 Like

Perhaps try and use inheritance?

https://www.php.net/manual/en/language.oop5.inheritance.php

1 Like

How would I use inheritance in this situation? I mean, as a principle - I think I could work out the code.

  1. Properties should be private.

  2. Any Property should to have at least getter and probably setter.

3.How your properties initialized? With setters or with constructor?

1 Like

Agreed

Why? In that case you might as well just use public properties. A class should more than just a data bag. It is meant to encapsulate behaviour around some state. Once the entire state is open for change there isn’t much encapsulation going on.

There really should be a constructor here. It should only be possible to create objects in a valid state. Since a room without a with or length isn’t a thing, those should really be required constructor arguments. The room name is debatable, although it should probably be required too.

class Room {
    private float $width;
    private float $length;
    private string $roomName;
    
    public function __construct(float $width, float $length, string $roomName) {
        $this->width = $width;
        $this->length = $length;
        $this->roomName = $roomName;
    }

    private function convertToImperial(float $x): string {
        $valInFeet = $x * 3.2808399;
        $valFeet = (int)$valInFeet;
        $valInches = round(($valInFeet-$valFeet) * 12);
        return $valFeet."′ ".$valInches."″";
    }

    public function imperialWidth(): string {
        return $this->convertToImperial($this->width);
    }

    public function imperialLength(): string {
        return $this->convertToImperial($this->length);
    }
}

Or, if you’re running PHP 8 you can simplify the constructor:

class Room {
    public function __construct(private float $width, private float $length, private string $roomName) {
    }

    private function convertToImperial(float $x): string {
        $valInFeet = $x * 3.2808399;
        $valFeet = (int)$valInFeet;
        $valInches = round(($valInFeet-$valFeet) * 12);
        return $valFeet."′ ".$valInches."″";
    }

    public function imperialWidth(): string {
        return $this->convertToImperial($this->width);
    }

    public function imperialLength(): string {
        return $this->convertToImperial($this->length);
    }
}

The second block is exactly the same as the first one, only syntax is different.

2 Likes

Getters required to read properties at least in extended classes and possibly for all. Setters could be protected,to change properties in extended classes. And anyway there is a big difference between access to propery with public setter and just public property

1 Like

But we don’t know any of the requirements yet. Why put a setter and getter on everything if we don’t even know if and how they can be changed? Maybe the width and length always need to be changed together for some reason, that I’d want a method that requires both arguments, not two setters. Maybe it’s not allowed to rename the room for some reason, then there shouldn’t be a setter. etc. etc.

1 Like

Because “no accessors” means no development. But normally code developes itself.

So what the problem? Two protected setters and public method that changes both and uses this setters.

Rename? What is relation between renaming and setters?

1 Like

Here we go:

<?php  declare(strict_types=1);
# following should be set in php.ini
  error_reporting(-1); // max error reporting
  ini_set('display_errors', 'true'); 


//=============================================================
class Conversion
{

//=============================================================
protected function convertToImperial(float $x): string
{
  $valInFeet = $x * 3.2808399;
  $valFeet 	 = (int) $valInFeet;
  $valInches = round(($valInFeet-$valFeet) * 12);

  return $valFeet 
  		."&prime; " 
  		.$valInches
  		."&Prime;" ;
}//

}/// endclass =================


//=============================================================
//=============================================================
class Room extends Conversion
{

//=============================================================
public function __construct
(
	float  $width    = NULL, 
	float  $length   = NULL, 
	string $roomName = NULL
)
# no return value
{
  if($width && $length && $roomName ):	
    $this->width 	= $width;
  	$this->length   = $length;
  	$this->roomName = $roomName;
  	$tmp = '<h3> Succesfully Constructed Class : </h3>';
  	# echo $tmp;	
  endif;	
}//

//=============================================================
public function show()
: string 
{
  	$result = '<dl><dt> $roomName ==> ' .$this->roomName 
  			. '</dt><dd> from $width   ==> ' .$this->width  .' meters '
  			. '</dd><dd> from $length  ==> ' .$this->length .' meters '
  			. '</dd><dd> &nbsp; '

  			. '</dd><dd> to: $width    ==> ' 
  			.$this->convertToImperial($this->width)
 
  			. '</dd><dd> to: $length   ==> ' 
  			.$this->convertToImperial($this->length)

  			. '</dd><dd> &nbsp; '
  			. '</dd></dl>' 
  			;

  	return $result;
}//

}/// endclass =================


$hall = new Room( 1.1, 2.2, 'Hall' );
	echo $hall->show();

$kitchen = new Room( 11.1, 22.2, 'Kitchen' );
	echo $kitchen->show();

Output:

Edit:

Have I got the public, private and protected methods correct?

1 Like

Wow! Excellent help - thank you so much John_Betong, igor_g and rpkamp. Even a bit of lively debate - in fact I found the discussion interesting as it told me more about the finer points of OOP than any tutorial ever has.

The PHP I posted was the first Class I have ever created, so as far as OOP is concerned I am very much a beginner.

John_Betong - thank you for that code example. It looks very slick. I note the error checking (the if/else clause). I understand most of it, but I wonder if I may ask a couple of questions?

  1. What is the advantage in using inheritance here rather than using the single level of class?
  2. The line
protected function convertToImperial(float $x): string

Does the float convert the string $x to a float? I understand that generally in PHP it is not necessary to do such conversions as it will convert according to the context. Is it good practice to do so here or is it necessary? Or are you just experienced in multiple languages and so do it automatically? Please don’t consider this a criticism - I am simply interested to learn from those with more knowledge/experience than I have.
And what does

: string

do?

Thank you again everyone for taking the time and effort to pass on your skills to those of us who are some distance behind you!

1 Like

That’s return type declaration. PHP is very late to that scene, but majority of languages have already had such a thing since the beginning. PHP just recently introduced return type declarations in PHP 7.0.

In languages like C#, C++, Java, etc you would create a method like

public string convertToImperial(float x) {
    // Do some thing in here.
}

So the scope of the method would go first, then the data type you want to return or am expecting to return, then the function name, and lastly, the parameters you want to pass into that method.

In PHP, it’s scope of the method first, then the keyword function, then the function name, next your parameters, and lastly a semi-colon and the data type you want to return or am expecting to return. In PHP 8.0, you can now specify “mixed” data types which just really means you can use multiple data type declarations in 1 method. To do it, it’ll look something like

protected function convertToImperial(float $x): string|bool|array|object

Realistically, you want to just stick to using 1 data type declaration to avoid any bad coding practice.

2 Likes

The whole principle of inheritance is that you start with some generic class that has multiple subclasses that are more specific instances of the superclass. When you draw a diagram of the classes Room an conversion as promosed you would say “A room is a kind of conversion”, which makes no sense whatsoever. Technically it works, but that doesn’t mean it’s the right thing to do.

When dealing with orthogonal concepts like these, we should be using composition rather than inheritance.

Since the convertToImperial is a pure function we can make it a static method on a class and call that method from the Room class.

class DistanceConverter {
    public static function convertMetricToImperial(float $x): string {
        $valInFeet = $x * 3.2808399;
        $valFeet = (int) $valInFeet;
        $valInches = round(($valInFeet-$valFeet) * 12);

        return $valFeet 
            ."&prime; " 
            .$valInches
            ."&Prime;" ;
    }
}
class Room {
    public function __construct(private float $width, private float $length, private string $roomName) {
    }

     public function imperialWidth(): string {
        return DistanceConverter::convertMetricToImperial($this->width);
    }

    public function imperialLength(): string {
        return DistanceConverter::convertMetricToImperial($this->length);
    }
}

However, I don’t think the Room class should even be concerned about imperial dimensions at all. Again composition makes more sense here IMO. We can keep the room to use metric only, and if for some reason outside of the class we need imperial we can fetch the value from the Room and convert it ourselves. This keeps the responsibility for conversion separate from the Room implementation.

class Room {
    public function __construct(private float $width, private float $length, private string $roomName) {
    }

     public function getWidth(): float {
        return $this->width;
    }

    public function getLength(): float {
        return $this->length;
    }
}
$room = new Room(12, 18, 'Some room');

echo 'The room is ' . DistanceConverter::convertMetricToImperial($room->getLength()) . ' by ' . DistanceConverter::convertMetricToImperial($room->getWidth());

If however distances are something really important in this project then lastly we could opt to make it a first class citizen and create a Value Object for it.

class Distance {
    public function __construct(float $metricDistance) {}

    public function getMetricDistance(): float {
        return $this->metricDistance;
    }

   public function getImperialDistance(): string {
       $valInFeet = $this->metricDistance * 3.2808399;
       $valFeet = (int) $valInFeet;
       $valInches = round(($valInFeet-$valFeet) * 12);

       return $valFeet 
           ."&prime; " 
           .$valInches
           ."&Prime;" ;
   }
}

Using that the Room class could look like this:

class Room {
    public function __construct(private Distance $width, private Distance $length, private string $roomName) {
    }

     public function getWidth(): Distance {
        return $this->width;
    }

    public function getLength(): Distance {
        return $this->length;
    }
}

And then to print you could use:

$room = new Room(12, 18, 'Some room');

echo 'The room is ' . $room->getLength()->getImperialDistance() . ' by ' . $room->getLength()->getImperialDistance();

And in that case you also wouldn’t need the DistanceConverter class anymore.

4 Likes

No, this is “type hinting”. It will force you to enter a float as the parameter. Anything else will give an error.
Though I think PHP’s type juggling ways will allow data that could be interpreted as a float.

2 Likes

It depends … :wink:

By default PHP will try to convert any non-float to a float if you pass it to a function/method that requires a float. However, if you start your PHP file with declare(strict_types=1); it will complain if you pass anything other than a float - even if the value passed could be coerced into a float.

i.e. this works

<?php

function foo(float $x) {}

foo('3.14');

this does not:

<?php
declare(strict_types=1);

function foo(float $x) {}

foo('3.14'); // TypeError

Note that declare(strict_types=1) is for calls inside that file only! If that file happens to declare a function and another file without declare(strict_types=1) calls that function it’s not strict!

file1.php

<?php
declare(strict_types=1);

function foo(float $x) {}

file2.php

<?php

require (__DIR__ . '/file1.php');

foo('3.14'); // no error, because _this file_ doesn't enforce strict types, even though file1.php does!

file3.php

<?php
declare(strict_types=1);

require (__DIR__ . '/file1.php');

foo('3.14'); // TypeError, because this file does enforce strict types
1 Like

So a class can just carry a function rather than being used to create an object? This is all good stuff but getting towards the edge of my comprehension horizon :grinning:

So the caveat is whether strict types is set. So strictly speaking, in John’s example, it is, so there will be no type juggling, and you will get an error in that case.

1 Like

No, the class defines a DistanceConverter object, just as “room” is an object, it’s just a different kind of object that has different properties and methods to serve a different purpose.

As long as it doesn’t require any of the state of an object then yes, you can do that. You can also create an object first and then call methods on that, that still works.

Well, to get really technical a class defines a blueprint for objects that are instances of that class.

As long as a program isn’t running there is only the class. You can only look at the structure of what objects would look like when the program is ran, but there are no actual objects at that point. As soon as you actually run the program and call new Room() for example then an object is created. And that object is an instance of the Room class.

It’s not, it’s a class. We can use that class to create Room objects, but in and of itself the code just describes a class.