The submit-update-refresh cycle

My selfie

James Turner,

Tags: PHP

Today, I had a revelation about an incredibly common pattern in PHP. I had a page with a <form> at the top and some output from a database below it. I wanted to submit the form, add the submitted data to my database, and then refresh the current page to include the new data. Being a PHP newbie, I got it working eventually, thanks to the help of a colleague. There are two tricks I needed to keep in mind.

Code overview

Let’s say I’m adding a list of customers to a database. I’m using object-oriented PHP, but no fancy MVC approach. First I set up my class with a property to contain my array of data and a constructor function that manages the background data manipulation and the rendering on screen. The constructor will call all the functions that handle form submissions, manipulate data and output the HTML.

<?php
class Customers {

	private $customers = [];

	public function __construct(){
	}

}
?>

To keep things tidy, I’m going to separate the different tasks that I need to accomplish into neat, self-contained functions. First, I want a function that displays a form to add new customers at the top of the page.

public function __construct(){
	$this->display_input_form();
}

private function display_input_form(){
?>
	<form action="<?php echo "$_SERVER['PHP_SELF']" ?>" method="post">
		<label for="customerToAdd">Add customer:</label>
		<input type="text" name="customerToAdd" id="customerToAdd">
		<button type="submit" name="submit">Submit</button>
	</form>
<?php
}

Next, I add another function that outputs a list of customers. I’ll deal with the $customers property in a minute.

public function __construct(){
	$this->display_input_form();
	$this->display_customers( $this->customers );
}

private function display_customers( $customers ){
	if( is_array( $customers ) && (count( $customers ) > 0) ){
		echo '<ul>';
		foreach($customers as $customer){
			echo '<li>' . $customer . '</li>';
		}
		echo '</ul>';
	} else {
		echo '<p>Sorry! Got no customers.</p>';
	}
}

Where does my list of customers come from? Let’s add a function that runs a MySQL query and returns an array of data. This needs to appear before display_customers() is called. I’m going to assume that the query function used below is declared somewhere else in my class. It handles the database connection and errors, and returns either the results of a MySQL SELECT query as an array or false if there are no results.

public function __construct(){
	$this->customers = $this->get_customers();
	$this->display_input_form();
	$this->display_customers( $this->customers );
}

private function get_customers(){
	$sql = 'SELECT customer FROM customers';
	$result = $this->query( $sql );
	if( $result ){
		return $result;
	} else {
		return false;
	}
}

Trick #1: Handle form submit before displaying data

Next, I need to handle the form submission. At first glance, it feels like this should be done at the bottom of the script. After all, it’s the last thing that happens, right? The user reads the information on the page, inputs a new customer, and then clicks the submit button.

While this appears to be true on the first visit to a page, when a page reloads, the form submission is in fact the first thing that happens. It’s simply that the form submission is skipped the first time the page is loaded. If the form submission is handled after display_customers(), the page reloads displaying old data. Here’s the code for handling the form submission, without any data validation:

<?php
public function __construct(){
	$this->customers = $this->get_customers();
	$this->handle_submit( $this->customers );
	$this->display_input_form();
	$this->display_customers( $this->customers );
}

private function handle_submit( $customers ){
	if( isset( [$_POST['submit'] ) ){
		// Skipping form validation - very naughty!
		$new_customer = $_POST['customerToAdd'];
		$sql = 'INSERT INTO customers SET customer = ' . $new_customer;
		if( $this->query( $sql ) ){
			return true;
		} else {
			return false;
		};
	}
}

Trick #2: Pass data by reference

The next problem is that, once I’ve updated the database, I want the new data to be included when I call display_customers(). At the moment, that’s not happening. The first thing to do is update the $customers array after the new customer has been added to the database. There are a couple of ways I could do this; I’m going to requery the database.

private function handle_submit( $customers ){
	if( isset( [$_POST['submit'] ) ){
		...
		if( $this->query( $sql ) ){
			$customers = $this->get_customers();
			return true;
		} else {
			...
		};
	}
}

I could now return $customers as the output of this function. However, I don’t like to do that because the constructor function would have to look like this:

private function _construct(){
	$customers = $this->get_customers();
	$customers = $this->handle_submit( $customers ); // I don't like this :(
	...
}

To me, that third line just doesn’t read very well. Other developers (including my future self) who read that code might find it difficult to understand what’s going on. Perhaps one solution would be to change the name of that function from handle_submit() to updated_customers(), but then it’s not so clear where the form submit is taking place.

An alternative solution, which I learnt today and I’m pretty excited about, is to change the signature of handle_submit() to accept the $customers array by reference, using the & prefix. In this way, any changes that are made to $customers inside the handle_submit() function are still available outside the function.

private function handle_submit( &$customers ){
	...
}

Conclusion

Here’s the code in full:

class Customers {

	private $customers = [];

	public function __construct(){
		$this->customers = $this->get_customers();
		$this->handle_submit( $this->customers );
		$this->display_input_form();
		$this->display_customers( $this->customers );
	}

	private function get_customers(){
		$sql = 'SELECT customer FROM customers';
		$result = $this->query( $sql );
		if( $result ){
			return $result;
		} else {
			return false;
		}
	}
	
	private function handle_submit( &$customers ){
		if( isset( [$_POST['submit'] ) ){
			// Skipping form validation - very naughty!
			$new_customer = $_POST['customerToAdd'];
			$sql = 'INSERT INTO customers SET customer = ' . $new_customer;
			if( $this->query( $sql ) ){
				$customers = $this->get_customers();
				return true;
			} else {
				return false;
			};
		}
	}

	private function display_input_form(){
	?>
		<form action="<?php echo "$_SERVER['PHP_SELF']" ?>" method="post">
			<label for="customerToAdd">Add customer:</label>
			<input type="text" name="customerToAdd" id="customerToAdd">
			<button type="submit" name="submit">Submit</button>
		</form>
	<?php
	}
	
	private function display_customers( $customers ){
		if( is_array( $customers ) && (count( $customers ) > 0) ){
			echo '<ul>';
			foreach($customers as $customer){
				echo '<li>' . $customer . '</li>';
			}
			echo '</ul>';
		} else {
			echo '<p>Sorry! Got no customers.</p>';
		}
	}

}

So, now I have a nice, reusable template for a very common pattern in dynamic websites. Do you have a favourite way of doing the same job? Comment below. Thanks for reading.

Skip to navigation