Little bug in the PHP Novice to Ninja 6th Ed code

Hello all,

I’ve been reading and re-reading this book, and following through building my own CMS framework upon its principles. And I’ve found a bug I can’t figure out in the last chapter.

He implements a way to order, limit and paginate the jokes. And then explains how to do the same thing within joke categories. The only trouble is it doesn’t quite work properly within the categories. The Jokes are order chronologically (newest to oldest). Within the joke categories on each paginated page the jokes are ordered chronologically but the pages themselves seem to run in the wrong order (oldest to newest).

I’ll put in a few bits of code to see if anyone can spot a problem.

CODE FROM jokes.html.php (page template)

Blockquote

  <?php
  
  $numPages = ceil($totalJokes/10);
  
  for ($i = 1; $i <= $numPages; $i++):
  
  if ($i == $currentPage):
  
  ?>
  
  <a class="currentpage" href="/joke/list?page=<?=$i?><?=!empty($categoryId) ? '&category=' . $categoryId : '' ?>"><?=$i?></a>
  
  <?php else: ?>
  
  <a href="/joke/list?page=<?=$i?><?=!empty($categoryId) ? '&category=' . $categoryId : '' ?>"><?=$i?></a>
  
  <?php endif; ?>
  
  <?php endfor; ?>

CODE FROM Controllers/Joke

Blockquote

public function list() {

	$page = $_GET['page'] ?? 1;

	$offset = ($page-1)*10;

	if (isset($_GET['category'])) {
		$category = $this->categoriesTable->findById($_GET['category']);
		$jokes = $category->getJokes(10, $offset);
		$totalJokes = $category->getNumJokes();
	}
	else {
		$jokes = $this->jokesTable->findAll('jokedate DESC', 10, $offset);
		$totalJokes = $this->jokesTable->total();
	}		

	$title = 'Joke list';

	

	$author = $this->authentication->getUser();

	return ['template' => 'jokes.html.php', 
			'title' => $title, 
			'variables' => [
					'totalJokes' => $totalJokes,
					'jokes' => $jokes,
					'user' => $author,
					'categories' => $this->categoriesTable->findAll(),
					'currentPage' => $page,
					'categoryId' => $_GET['category'] ?? null
				]
			];
}

CODE FROM Entity/Category

Blockquote

    public function getJokes($limit = null, $offset = null) {
	$jokeCategories = $this->jokeCategoriesTable->find('categoryId', $this->id, null, $limit, $offset);

	$jokes = [];

	foreach ($jokeCategories as $jokeCategory) {
		$joke =  $this->jokesTable->findById($jokeCategory->jokeId);
		if ($joke) {
			$jokes[] = $joke;
		}			
	}

	usort($jokes, [$this, 'sortJokes']);

	return $jokes;
}

	public function getNumJokes() {
		return $this->jokeCategoriesTable->total('categoryId', $this->id);
	}

	private function sortJokes($a, $b) {
		$aDate = new \DateTime($a->jokedate);
		$bDate = new \DateTime($b->jokedate);

		if ($aDate->getTimestamp() == $bDate->getTimestamp()) {
			return 0;
		}

		return $aDate->getTimestamp() > $bDate->getTimestamp() ? -1 : 1;
     }

Thanks for your help.

I am aware there is a new edition of the book out, but I’ve been bashing away at this edition since a fair while before that emerged, and feel I should solidify my understanding of it before updating.

Is there somewhere that $categoryId is defined?

EDIT: No, i can read. shhhhh…

Is category getting defined? If so, it… SHOULD be correct, as its sorting by jokedate DESC…

Incidentally, this is the reason spaceship operators were invented…

(It’s correct, but man trying to wrap your head around it…)

Within each category it is ordering them by jokedate DESC on each page, but the pages themselves are in the wrong order i.e newer jokes are on page 2, older jokes on page 1.

In the list of all jokes it all works correctly (jokedate DESC on each page, and newest jokes on page 1

The entire code can be found here:

oh. Well, yes, that makes sense from the code given.

$offset = ($page-1)*10;

=>
$offset = $totalJokes-($page-1)*10;

The offset needs to be from the end of the dataset, not the beginning. (It will also require $totalJokes to be set FIRST)

wait… no… that shouldnt be the case… the dataset should be sorted already… so the offset should be from the beginning…

hrm.

This would seem to be the cause. It’s not passing any sort of DESC here, and its using the offset from the beginning of the set, not the end of the set…

Sorry for the long interlude. Had the dreaded lurghy and a computer failure. I’m back up and running here, and agree. on the cause of the problem: it paginated based on id in the jokeCategories table (which has no date column), and then sorts each page based on date. So I presume I would need to create a JOIN in find() to the joke table which has the jokeDate column. Or is there a smarter way to do it?

Blockquote

   public function find($column, $value, $orderBy = null, $limit = null, $offset = null) {
	$query = 'SELECT * FROM ' . $this->table . ' WHERE ' . $column . ' = :value';

	$parameters = [
		'value' => $value
	];

	if ($orderBy != null) {
		$query .= ' ORDER BY ' . $orderBy;
	}

	if ($limit != null) {
		$query .= ' LIMIT ' . $limit;
	}

	if ($offset != null) {
		$query .= ' OFFSET ' . $offset;
	}

	$query = $this->query($query, $parameters);

	return $query->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);
}

Incase its useful to anyone I got the following to work:

Blockquote

public function findAndJoin($column, $value, $orderBy = null, $limit = null, $offset = null) {

    $query = 'SELECT * FROM ' . $this->table . ' JOIN joke ON ' . $this->table . ' .jokeId = joke.id WHERE ' . $column . ' = :value';

    $parameters = [

        'value' => $value

    ];
    if ($orderBy != null) {

        $query .= ' ORDER BY ' . $orderBy;

    }
    if ($limit != null) {

        $query .= ' LIMIT ' . $limit;

    }
    if ($offset != null) {

        $query .= ' OFFSET ' . $offset;

    }
    $query = $this->query($query, $parameters);

    return $query->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);

}
1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.