Starting out learning OOP in PHP - am I going about interacting with forms in a good way or not?

I am teaching myself OOP in PHP. This is my first script. I had some good help in an earlier post in setting up the classes. I have now taken this to the next step and completed a rough working page. The layout needs some work to make the results easier to read and I’m not keen on the colours. And I have not yet put in any text to explain what the user has to do.

But I am asking for advice re the PHP - am I going about using forms in OOP in a good way or could it be done better?

In the UK some estate agents (realtors) only quote room sizes in metres but I can visualise room sizes better in feet and inches. So this page takes as input a room name and width and length in metres, up to 2 decimal places. On clicking the submit button it outputs the room name, the metric width and length and area and the same in feet and inches. The room name and width and length are displayed as an input in the same form.

When the user submits another room they can also alter the name and/or dimensions of the old room, or to delete the old room they simply make one of the fields empty.

As you can see, the inputs to the form are sent as arrays. When submitted, it works through foreach loop on

$_POST['roomName']

and for each room it creates an object $room and carries out the above on that room. The next iteration creates a new $room and does the same again. Is this a sensible way of doing this? Once one $room object has created it’s input and conversions I don’t think there is any need to keep it and create an object with a different name?

Any advice on OOP PHP and forms gratefully received. Thank you.

<!doctype = html>
<html>
	<head>
		<meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
		<link rel="stylesheet" href="stylesheets/style.css?1">
    <style>
      *{
	margin: 0;
	padding: 0;
	
	box-sizing: border-box;
}

:root{
	--textColor: #039;
	--headlineColor: #69e;
	--backgroundColor: #fea;
  --siteBackgroundColor: #ffffff;
  --footerColor: darkblue;
  --footerGradientTop: #06f;
  --footerGradientBottom:  #6cf;
  --inputBorder: #999;
}

img{
	display: block;
}

html {
	height: 100%;
}

body{
	min-height: 100%;
	display: grid;
	grid-template-rows: auto 1fr auto;
	background-color: var(--backgroundColor);
}

main{
	margin: 0 auto;
	background-color: var(--siteBackgroundColor);
  width: auto;
  min-width: 700px; 
	padding: 10px;
  box-shadow: 5px 0 5px rgba(128,128,128,0.5);
}

p,span,input,legend,td,th{
	font-family: roboto, sans-serif;
	color: var(--textColor);
	padding: 5px;
}

p.results{
  white-space: nowrap;
}

legend{
  font-size: smaller;
}

input.new,fieldset.new{
  border-radius: 4px; 
  width: 100%;
}

input{
  border: none; 
}

fieldset{
  border: inset 1px var(--inputBorder);
  margin: 5px 0;
}

input.old{
  border: solid 1px var(--inputBorder);
  border-radius: 4px;
  width: 5em;
  text-align: right;
  margin: 5px;
}

input.room{
  width: 15em;
  text-align: left;
}

input.submit{
  width: auto;
  border: 1px outset;
  background-color: var(--backgroundColor);
  box-shadow: 2px 2px 2px 0 var(--inputBorder);
}

input.submit:hover{
  color: var(--backgroundColor);
  background-color: var(--textColor);
  border: 1px inset;
}

h1,h2,h3,h4,h5,h6{
	font-family:  bree, times, serif;
	color: var(--headlineColor);
	padding: 5px;
}

footer.footer{
	background-color: var(--footerColor);
  background-image: linear-gradient(to bottom, var(--footerGradientTop), var(--footerGradientBottom));
  min-width: 700px;
  margin: 0 auto;
	font-size: smaller;
	padding: 5px;
  box-shadow: 5px 0 5px  rgba(128,128,128,0.5);
  z-index: -1;
}

footer > span{
    color: white;
}
    </style>
		<title>
				Dan's rooms calculator
		</title>
	</head>

	<body>
		<header>
			
		</header>
		<main>
			<h1>
				Dan's Room Calculator
			</h1>
      
        <?php 
        //if form has been submitted with completed fields
        if (!empty($_POST['roomName']) && !empty($_POST['width']) && !empty($_POST['length'])){
          
//*******************************************************************      
        class Room {
            private  $width;
            private  $length;
            private  $roomName;

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

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

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

            public function getArea(): float {
                return $this->width * $this->length;
            }

            public function getName(): string {
                return $this->roomName;
            }      
        }

//*******************************************************************
        class Converter {
            public static function convertMetricToImperialDistance(float $metres): string {
                $valInFeet = $metres * 3.2808399;
                $valFeet = (int) $valInFeet;
                $valInches = round(($valInFeet-$valFeet) * 12);

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

            public static function convertMetricToImperialArea(float $squareMetres): string{
                return round($squareMetres * 10.7639104)."ft<sup>2</sup>";
            }
        }
//*******************************************************************
        foreach ($_POST['roomName'] as $key=>$value){
          //sanitise input
          $roomName = filter_var($_POST['roomName'][$key], FILTER_SANITIZE_STRING);
          $width = filter_var($_POST['width'][$key], FILTER_SANITIZE_STRING);
          $length = filter_var($_POST['length'][$key], FILTER_SANITIZE_STRING);
          
          //if the user has emptied an existing room's field then do not proceed with this room and move onto the next room. Will also allow user to 
          if ($roomName && $width && $length){
          //instantiate a new Room object
          $room = new Room($width, $length, $roomName);  
        
          //Output the dimensions of the Rooms, as inputs for the form below  
          echo"
          <p class='results'>
          <input value='".$room->getName()."' name='roomName[]' class='old room' form='mainForm'>
          <input value='".$room->getWidth()."'  name='width[]' class='old' form='mainForm'><span>m</span><span>x</span>
          <input value='".$room->getLength()."'  name='length[]' class='old' form='mainForm'><span>m</span>
          <span>".number_format($room->getArea(),2,'.',',')."m<sup>2</sup></span>
          <span>".Converter::convertMetricToImperialDistance($room->getWidth())."</span><span>x</span>
          <span>".Converter::convertMetricToImperialDistance($room->getLength())."</span>
          <span>".Converter::convertMetricToImperialArea($room->getArea())."</span>
          </p>
          ";
            }   //end of if ($roomName)...
          }     //end of foreach ($_POST['roomName'] ...
        }       //end of initial if(!empty)  etc
        ?>
      
        <form action="convert04.php" method="post" id="mainForm">
          <fieldset class="new">
            <legend>
              Room Name
            </legend>
            <input name="roomName[]" type="text" class="new"> 
          </fieldset> 
          <fieldset class="new">
            <legend>
              width (m)
            </legend>
            <input name="width[]" type="number" step=".01" class="new"> 
          </fieldset> 
          <fieldset class="new">
            <legend>
              Length (m)
            </legend>
            <input name="length[]" type="number" step=".01" class="new"> 
          </fieldset> 

            <input type="submit" value="Add room/update" class="submit">

        </form>       
      
		</main>
		<footer class="footer">
			<?php include_once('includes/footer.php') ?>
		</footer>

	</body>
</html>      

Live site https://danieljeffery.co.uk/convertOOP/convert04.php as my description may be difficult to follow.

I have not yet tried OOP in PHP but, based on using OOP in C++, I offer these comments.

I am surprised to see the class “Converter”. Within the “Room” class I would have getImperialWidth() and getImperialLength() functions.

Have you considered having a “House” class that contains a list of the room objects, perhaps together with other data such as detached/semii-detached/terraced? I think it may then be appropriate for your foreach loop to be within a function of the “House” class.

1 Like

There was a lengthy discussion on this very subject a few weeks ago - Passing arguments to an object's method

Well actually the Converter is not within any class. I AM HOPING that the lines of asterisks are meaning each of these classes are in their own file (as PHP classes should be) and then included and that it was done like this just for our convenience.

Been a while since I had seen that topic. Boy did that get severely over engineered! Sure having a dedicated Converter class for making conversions is smart in a big project as it affords you multiple conversion methods and keeps conversions out of classes, but I think it is total overkill if you only have a class or two in a file. I think that is a bit of what @Archibald was trying to get at. If the room is only going to ever be converted between imperial and metric in relation to rooms, I probably would have kept it simple methods like you had before.

One word of advice going forward on dev boards, always ask yourself if you are overoptimizing something. Just because it can be optimized further doesn’t always mean you should. Always take into account the size and scope of your project. If at a later time you realize that you do need to add more conversions and convert more than just room dimensions, sure optimize it then and refactor into its own class… but I don’t think your project warranted it at that point in time.

:slight_smile:

3 Likes

No, I didn’t realise that. The asterisks where so I could clearly see where the class began and finished.

Some good advice there - thank you.

I am learning that just because some code works it doesn’t mean it is well written!

I did have a different version of the script that didn’t use OOP at all - is OOP overkill in this situation? (I was doing it as a way of learning OOP so it served that purpose).

Oh no worries, but yeah you should try and put one class per file and just include it into your other pages. This will save you a ton of headaches and makes things easier to learn and keep things minimized and focused on the task at hand.

Sometimes even doing OOP is overkill. Some people even argue that the OOP paradigm is overkill in most situations. (See functional programming)

I personally don’t mind using OOP for small projects if it makes sense. Either way, OOP is definitely something you should learn so no worries. :slight_smile:

One site that I think you might find really really useful is the recommend PHP conventions site. https://www.php-fig.org/

I am sure that will help answer a lot of questions on what is proper conventions and how to format things for various OOP features. :slight_smile:

1 Like

I don’t think overkill applies here. OOP and functional are completely different things that you can’t really compare 1 to 1. Functional programming doesn’t just mean you only use functions and no classes, it’s really very different in multiple aspects.

I do agree that if this is all the script does I wouldn’t go for OOP either. Just simple transaction script would be fine.

I agree about separating files and have modified the source and also a demo can be see here:

https://this-is-a-test-to-see-if-it-works.tk/sp-a/wake689/convert04/convert04.php

<?php declare(strict_types=1);
# SHOULD BE SET IN php.ini
  error_reporting(-1);
  ini_set('display_errors', 'true');

# echo '<pre>$_POST ==> '; print_r($_POST); echo '<pre>'; die;

$title    = 'Dan\'s rooms calculator';
$css      = 'style-002.css?1';
$room     = 'No room set';
$origin   = 'https://danieljeffery.co.uk/convertOOP/convert04.php';
$download = 'incs/convert04.zip';
$forum    = 'https://www.sitepoint.com/community/t/starting-out-learning-oop-in-php-am-i-going-about-interacting-with-forms-in-a-good-way-or-not/368841';

# RESET  
  if( isset($_POST['submit']) && 'Reset'===$_POST['submit'] )
  {
    $_POST = []; 
  }

require 'incs/C_room.php';
require 'incs/C_converter.php';
require 'incs/funcs_001.php';
 

?><!DOCTYPE HTML><html lang=en-gb>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="incs/<?= $css ?>">
<title> <?= $title ?> </title>
</head>
	<body>
		<header>
      <a href="<?= $origin ?>"> Origin </a>
      <a href="<?= $forum ?>"> Forum </a>
      <a href="<?= $download ?>"> Download zip </a>
		</header>

		<main>
			<h1>
				<?= $title ?>
			</h1>
      
      <form action="convert04.php" method="post" id="mainForm">
         <fieldset class="new">
            <legend>
              Room Name
            </legend>
            <input name="roomName[]" type="text" class="new"> 
          </fieldset> 
          
          <fieldset class="new">
            <legend>
              width (m)
            </legend>
            <input name="width[]" type="number" step=".01" class="new"> 
          </fieldset> 

          <fieldset class="new">
            <legend>
              Length (m)
            </legend>
            <input name="length[]" type="number" step=".01" class="new"> 
          </fieldset> 

          <input type="submit" class="submit" value="Add room/update">

          <input type="submit" class="reset"  name="reset" value="Reset">
      </form>       
    </main>

    <div class="results" >
      <h2> Room details </h2>
      <?= getResults() ?>
    </div>
        
    <?php debug($_POST, '$_POST'); ?>

		<footer class="footer">
      <span> Created by Daniel Jeffery </span>
			<?php # echo 'include_once("includes/footer.php")'; ?>
		</footer>

	</body>
</html> 

Thank you for this. However, I wanted the results to be cumulative eg the user adds lounge 1.2m x 2.34m and submits. This displays the results for this room. The user then adds hall 3.45m x 4.56m and submits - now it displays the results for lounge and for hall. If the user then wants to alter or delete hall or lounge name or measurements then this can be done. So the user builds up the measurements for the whole house in metric and imperial.

I’ve made some changes and the source can be downloaded from the zip file.

To delete an item, edit the room with an empty value. Not worth a notification if there are only a couple of users.

Page is now Google Mobile Friendly.

https://search.google.com/test/mobile-friendly?id=U3FKq9gGiS73KbcpJRyydg