php mvc pagination

I am trying to create pagination using a very basic custom mvc framework. I have taken some code from an OOP example which isn't MVC and trying to now incorporate into my MVC project.

Here is the pagination class:

class Pagination {
	
	public $current_page;
	public $per_page;
	public $total_count;
	
	public function __construct($page=1, $per_page=20, $total_count=0)
	{
		$this->current_page = (int) $page;
		$this->per_page = (int) $per_page;
		$this->total_count = (int) $total_count;
	}
	
	public function offset()
	{
		return $this->per_page * ($this->current_page - 1);
	}
	
	public function total_pages()
	{
		return ceil($this->total_count / $this->per_page);
	}
	
	public function next_page()
	{
		$next = $this->current_page + 1;
		return ($next <= $this->total_pages()) ? $next : false;
	}
	
		public function previous_page()
	{
		$prev = $this->current_page - 1;
		return ($prev > 0) ? $prev : false;
	}
}

Open in new window


Here is the database query in the model:

	public function GetPaginatedEvents($per_page, $offset)
	{
		$this->db->query("SELECT `id`, `start_date`, `end_date`, `event_name`, `event_details`, `event_slug`, `img` FROM `events` LIMIT :limit OFFSET :offset");
		$this->db->bind(":limit", $per_page);
		$this->db->bind(":offset", $offset);
		$results = $this->db->resultSet();
		return $results;
	}

Open in new window


And here is the controller code:

      
public function index()
	{
		
		$current_page = $_GET['page'] ?? 1;
		$per_page = 6;
		$total_count = $this->eventsModel->EventPagination();
		
		$pagination = new Pagination($current_page, $per_page, $total_count);
		$offset = $pagination->offset();

		$events = $this->eventsModel->GetPaginatedEvents($per_page, $offset);
		
		
		
		$data = [
			
			'events' => $events,
			'next_page' => $pagination->next_page(),
		
		];
		
		
		$this->view('events/index', $data);
	}

Open in new window



So far, if I navigate to my events page I am shown 6 records which is correct. If I manually change $current_page = $_GET['page'] ?? 1; to $current_page = $_GET['page'] ?? 2; and reload the page, it shows the second set of records. So, I am satisfied that all this code is working.

My problem however is getting it to show the set of records based on the url. In the non OOP version the url would change to events.php?page=2

But now with MVC I cannot figure out how to get the url to display the second set of records or third. I tried typing in example.com/events/page/2 but that doesn't work.

Here is the core class:

class Core {
	
	protected $currentController = 'Pages';
	protected $currentMethod = 'index';
	protected $params = [];
	
	public function __construct() {
		$url = $this->getUrl();
		
		// Look in Controllers for first value
		if(file_exists('../app/controllers/' . ucwords($url[0]) . '.php')) {
			//if exists, set as controller
			$this->currentController = ucwords($url[0]);
			// Unset zero index
			unset($url[0]);
			
		}
		
		// Require the controller
		require_once '../app/controllers/' . $this->currentController . '.php';
		
		// Instantiate controller
		$this->currentController = new $this->currentController;
		
		
		 //Check for second part of url
		if(isset($url[1])) {
			
			
			$url[1] = str_replace('-', '_', $url[1]);

			
			// check to see if method exists in controller
			if(method_exists($this->currentController, $url[1])) {
				$this->currentMethod = $url[1];
				// Unset 1 index
				unset($url[1]);

			}
		} 
		
		
		// Get params
		$this->params = $url ? array_values($url) : [];
		
		// Call a callback with array of params
		call_user_func_array([$this->currentController, $this->currentMethod], $this->params);
	}
	
	public function getUrl() {
		if(isset($_GET['url'])) {
			$url = rtrim($_GET['url'], '/');
			$url = filter_var($url, FILTER_SANITIZE_URL);
			$url = explode('/', $url);
			return $url;
			
		}
	}
}

Open in new window

LVL 1
Black SulfurAsked:
Who is Participating?
 
Julian HansenCommented:
Just to follow up

In the MVC code you are using any parameters after the controller / method are passed to the method (or default method) as parameters

For instance

/controller_name/method_x/page/2
Will be processed as
controller_name => identifies filename that contains the controller code
method_x is the name of the method to call in that controller. Note if the method does not exist then this becomes the first parameter passed to the default method.

If method is not found then default method (index) is used.

Therefore in your case this url
example.com/events/page/2

Open in new window

Will result in the following

In events.php in the controller file
Either
function page($pagenumber)

Open in new window

Or
function index($page, $pagenumber)

Open in new window


I recommend this
example.com/events/2

Open in new window

Then in events.php controller
function index($current_page = 1)
{
   ... // rest of code as before
}

Open in new window

0
 
Julian HansenCommented:
Firstly why is this non OOP
In the non OOP version the url would change to events.php?page=2

Open in new window

This is a valid URL irrespective of how you code the page.

In terms of using the /page/2 option - that would depend on how your framework breaks up the incoming URL.

In my framework I take URL and split it on the '/' assume the first parameter specifies the controller and pass the remaining items as an array of strings to the controller function.

You would need to look (or show us) how your controller handles the path - and how it passes in that additional parameter information.
0
 
Black SulfurAuthor Commented:
Sorry Julian,  I meant non MVC, not non OOP. And that is based on my current framework. None of the urls work with something=value. Everything is separated by a slash.

I think the core class I posted in my initial question is perhaps what you are looking for in terms of how the path is handled?

Other than that, this is the controller:

class Controller {
	
	public function model($model) {
		require_once '../app/models/' . $model . '.php';
		return new $model();
	}
	
	public function view($view, $data = []) {
		// check for view file
		if(file_exists('../app/views/' . $view . '.php')) {
			require_once '../app/views/' . $view . '.php';
		} else {
			die('View does not exist');
		}
	}
}

Open in new window


And then there is the database class and bootstrap file. The bootstrap file simply loads the controller class, core and database class.
0
Cloud Class® Course: C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.

 
Julian HansenCommented:
What is in the data[] array?
0
 
Black SulfurAuthor Commented:
Are you referring to the data array in the controller? That is what is passed to the view.

public function index()
	{
		
		$current_page = $_GET['page'] ?? 1;
		$per_page = 6;
		$total_count = $this->eventsModel->EventPagination();
		
		$pagination = new Pagination($current_page, $per_page, $total_count);
		$offset = $pagination->offset();

		$events = $this->eventsModel->GetPaginatedEvents($per_page, $offset);
		
		
		
		$data = [
			
			'events' => $events,
			'next_page' => $pagination->next_page(),
		
		];
		
		
		$this->view('events/index', $data);
	}

Open in new window


This is a database call in the model, passing in $per_page and $offset.

$events = $this->eventsModel->GetPaginatedEvents($per_page, $offset);

Open in new window


That in the data array is just the details of the event pulled from the database like, name, date etc. next_page is a value from the pagination class

public function next_page()
	{
		$next = $this->current_page + 1;
		return ($next <= $this->total_pages()) ? $next : false;
	}

Open in new window


Please let me know if I have misunderstood your question.
0
 
Julian HansenCommented:
Somewhere in the framework there must be something that stores the bits of the url?

The controller is the first sub-folder in the path - but the other values must be passed with some parameter or made available in some other way?

We are missing a bit between the index.php landing page and the instantiation of the controller class.
0
 
Black SulfurAuthor Commented:
The index file just loads bootsrap.php

Config simply has the database config and site path/name

// LOAD CONFIG


require_once 'config/config.php';

// LOAD HELPERS

require_once 'helpers/functions.php';


// LOAD LIBRARIES


spl_autoload_register(function($className) {
	$folders = [
		'libraries/',
		'controllers/',
	];
	foreach ($folders as $f) {
		$path = __DIR__ . '/' . $f . $className . '.php';
		if (is_file($path)) {
			include_once $path;
		}
	}
});

Open in new window


The core is also instantiated and here is the core class:

/*
* APP CORE CLASS
* Creates URL & loads core controller
*URL format - /controller/method/params
*/

class Core {
	
	protected $currentController = 'Pages';
	protected $currentMethod = 'index';
	protected $params = [];
	
	public function __construct() {
		$url = $this->getUrl();
		
		// Look in Controllers for first value
		if(file_exists('../app/controllers/' . ucwords($url[0]) . '.php')) {
			//if exists, set as controller
			$this->currentController = ucwords($url[0]);
			// Unset zero index
			unset($url[0]);
			
		}
		
		// Require the controller
		require_once '../app/controllers/' . $this->currentController . '.php';
		
		// Instantiate controller
		$this->currentController = new $this->currentController;
		
		
		 //Check for second part of url
		if(isset($url[1])) {
			
			
			$url[1] = str_replace('-', '_', $url[1]);

			
			// check to see if method exists in controller
			if(method_exists($this->currentController, $url[1])) {
				$this->currentMethod = $url[1];
				// Unset 1 index
				unset($url[1]);

			}
		} 
		
		
		// Get params
		$this->params = $url ? array_values($url) : [];
		
		// Call a callback with array of params
		call_user_func_array([$this->currentController, $this->currentMethod], $this->params);
	}
	
	public function getUrl() {
		if(isset($_GET['url'])) {
			$url = rtrim($_GET['url'], '/');
			$url = filter_var($url, FILTER_SANITIZE_URL);
			$url = explode('/', $url);
			return $url;
			
		}
	}
}

Open in new window


Base controller class

// Base controller - loads models and views

class Controller {
	
	public function model($model) {
		require_once '../app/models/' . $model . '.php';
		return new $model();
	}
	
	public function view($view, $data = []) {
		// check for view file
		if(file_exists('../app/views/' . $view . '.php')) {
			require_once '../app/views/' . $view . '.php';
		} else {
			die('View does not exist');
		}
	}
}

Open in new window


And .htacess

<IfModule mod_rewrite.c>
 Options -Multiviews
 RewriteEngine On
 RewriteBase /mysite/public
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
 </IfModule>

Open in new window


And that's really all there is to it.
0
 
Julian HansenCommented:
Here is the key bit
$url = $this->getUrl();
If you look at getUrl() it returns an array of path elements (exploded on '/')
This is what you want to use for your pagination

Then line 50 of the core class
$this->params = $url ? array_values($url) : [];

Open in new window

This is where the remaining parameters are made available.
Line 53 is where the method in the controller is called
call_user_func_array([$this->currentController, $this->currentMethod], $this->params)

Open in new window

The parameters are passed to the method

So what you need to do is in your Controller class Events method - get the params parameter and explore that for the parameters for your pagination.
Something like
function events($params) 
{
    $current_page = $params[1]; // Check this may be at another index
     ...
}

Open in new window

0
 
Black SulfurAuthor Commented:
I tried what you said regarding the controller for the events method but not sure I understand 100%.

This is the method in the controller and I am now passing in $params and set $current_page = $params[1]. Also tried $params[0]

	public function index($params)
	{
	
		$current_page = $params[1];
		$per_page = 6;
		$total_count = $this->eventsModel->EventPagination();
		
		$pagination = new Pagination($current_page, $per_page, $total_count);
		$offset = $pagination->offset();


		$events = $this->eventsModel->GetPaginatedEvents($per_page, $offset);
		
		
		
		$data = [
			
			'events' => $events,
			'next_page' => $pagination->next_page(),
			'current_page' => $current_page
		
		];
		
		
		$this->view('events/index', $data);
	}

Open in new window


Errors I get are:

Warning: Missing argument 1 for Events::index(), called in /Applications/MAMP/htdocs/yoursite/app/libraries/Core.php on line 59 and defined in /Applications/MAMP/htdocs/yoursite/app/controllers/Events.php on line 11

Notice: Undefined variable: params in /Applications/MAMP/htdocs/yoursite/app/controllers/Events.php on line 15

This error only happens if I go to the first page of results e.g.: mysite.com/events

If I manually put /page/1 after that, e.g.: mysite.com/events/page/1 then those errors go away but I get another error:

Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-6'

That seems to be coming from this line:

$per_page = 6;

Open in new window

0
 
Black SulfurAuthor Commented:
Thank you!
0
 
Julian HansenCommented:
You are welcome
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.