Pagination   C
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 386
Duplicated Lines 4.15 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 64
c 3
b 1
f 1
lcom 1
cbo 0
dl 16
loc 386
rs 5.8364

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A __toString() 0 4 1
A __get() 0 4 1
A get() 0 7 2
A __set() 0 4 1
B set() 0 29 6
A setUrl() 0 4 1
D render() 0 79 12
A generateUrl() 0 4 1
B paginationUrl() 0 42 5
C calculateNumbers() 16 43 16
C validateConfig() 0 62 17

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Pagination often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Pagination, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package    Fuel\Common
4
 * @version    2.0
5
 * @author     Fuel Development Team
6
 * @license    MIT License
7
 * @copyright  2010 - 2015 Fuel Development Team
8
 * @link       http://fuelphp.com
9
 */
10
11
namespace Fuel\Common;
12
13
/**
14
 * @since 1.0
15
 */
16
class Pagination
17
{
18
	/**
19
	 * @var  Fuel\Foundation\Input  current input instance
20
	 */
21
	protected $input;
22
23
	/**
24
	 * @var  Fuel\Display\View  pagination view
25
	 */
26
	protected $view;
27
28
	/**
29
	 * @var  array  configuration values
30
	 */
31
	protected $config = array(
32
		'current'        => 0,
33
		'offset'         => 0,
34
		'limit'          => 0,
35
		'totalPages'     => 0,
36
		'totalItems'     => 0,
37
		'numberOfLinks'  => 3,
38
		'uriSegment'     => 3,
39
		'getVariable'    => '',
40
		'showFirst'      => false,
41
		'showLast'       => false,
42
		'linkOffset'     => 0.5,
43
		'align'          => 'center',
44
	);
45
46
	/**
47
	 * @var  string  the pagination Url template. Use {page} for the location of the page number
48
	 */
49
	protected $paginationUrl = null;
50
51
	/**
52
	 * Construct a pagination object
53
	 *
54
	 * @param  Fuel\Display\Manager  The current applications ViewManager object
55
	 * @param  Fuel\Foundation\Input  The current requests Input container
56
	 * @param  string  View to be used to generate the pagination HTML
57
	 *
58
	 */
59
	public function __construct($viewmanager, $input, $view)
60
	{
61
		// store the input instance passed
62
		$this->input = $input;
63
64
		// construct the pagination view
65
		$this->view = $viewmanager->forge($view);
66
	}
67
68
	/**
69
	 * Render the pagination view when the object is cast to string or echo'd
70
	 */
71
	public function __toString()
72
	{
73
		return (string) $this->render();
74
	}
75
76
	/**
77
	 * Getter for configuration items
78
	 */
79
	public function __get($key)
80
	{
81
		return $this->get($key);
82
	}
83
84
	/**
85
	 * Getter for configuration items
86
	 */
87
	public function get($key)
88
	{
89
		if (isset($this->config[$key]))
90
		{
91
			return $this->config[$key];
92
		}
93
	}
94
95
	/**
96
	 * Setter for configuration items
97
	 */
98
	public function __set($key, $value)
99
	{
100
		$this->set($key, $value);
101
	}
102
103
	/**
104
	 * Setter for configuration items
105
	 */
106
	public function set($var, $value = null)
107
	{
108
		if ( ! is_array($var))
109
		{
110
			$var = array ($var => $value);
111
		}
112
113
		foreach ($var as $key => $value)
114
		{
115
			$value = $this->validateConfig($key, $value);
116
117
			if (isset($this->config[$key]))
118
			{
119
				// preserve the type
120
				if (is_bool($this->config[$key]))
121
				{
122
					$this->config[$key] = (bool) $value;
123
				}
124
				elseif (is_string($this->config[$key]))
125
				{
126
					$this->config[$key] = (string) $value;
127
				}
128
				else
129
				{
130
					$this->config[$key] = (int) $value;
131
				}
132
			}
133
		}
134
	}
135
136
	/**
137
	 * Setter for the pagination url. It must contain a {page} placeholder for the page number
138
	 *
139
	 * @param  string  $url  The pagination Url template to be used to generate link urls
140
	 */
141
	public function setUrl($url)
142
	{
143
		$this->paginationUrl = $url;
144
	}
145
146
	/**
147
	 * Render the pagination view, and return the view
148
	 *
149
	 * @return  View  the configured view object
150
	 */
151
	public function render()
152
	{
153
		// make sure we have a correct url
154
		$this->paginationUrl();
155
156
		// and a current page number
157
		$this->calculateNumbers();
158
159
		$urls = array();
160
161
		// generate the URL's for the pagination block
162
		if ($this->config['totalPages'] > 1)
163
		{
164
			// calculate start- and end page numbers
165
			$start = $this->config['current'] - floor($this->config['numberOfLinks'] * $this->config['linkOffset']);
166
			$end = $this->config['current'] + floor($this->config['numberOfLinks'] * ( 1 - $this->config['linkOffset']));
167
168
			// adjust for the first few pages
169
			if ($start < 1)
170
			{
171
				$end -= $start - 1;
172
				$start = 1;
173
			}
174
175
			// make sure we don't overshoot the current page due to rounding issues
176
			if ($end < $this->config['current'])
177
			{
178
				$start++;
179
				$end++;
180
			}
181
182
			// make sure we don't overshoot the total
183
			if ($end > $this->config['totalPages'])
184
			{
185
				$start = max(1, $start - $end + $this->config['totalPages']);
186
				$end = $this->config['totalPages'];
187
			}
188
189
			// now generate the URL's for the pagination block
190
			for($i = $start; $i <= $end; $i++)
191
			{
192
				$urls[$i] = $this->generateUrl($i);
193
			}
194
		}
195
196
		// send the generated url's to the view
197
		$this->view->set('urls', $urls);
198
199
		// store the current and total pages
200
		$this->view->set('active', $this->config['current']);
201
		$this->view->set('total', $this->config['totalPages']);
202
203
		// do we need to add a first link?
204
		if ($this->config['showFirst'])
205
		{
206
			$this->view->set('first', $this->generateUrl(1));
207
		}
208
209
		if (isset($start) and $start > 1)
210
		{
211
			$this->view->set('previous', $this->generateUrl($start-1));
212
		}
213
214
		if (isset($end) and $end !== $this->config['totalPages'])
215
		{
216
			$this->view->set('next', $this->generateUrl($end+1));
217
		}
218
219
		// do we need to add a last link?
220
		if ($this->config['showLast'])
221
		{
222
			$this->view->set('last', $this->generateUrl($this->config['totalPages']));
223
		}
224
225
		$this->view->set('align', $this->config['align']);
226
227
		// return the view
228
		return $this->view;
229
	}
230
231
	/**
232
	 * Generate the link to a particular page
233
	 *
234
	 * @param  int  $link  page number
235
	 *
236
	 * @return  string  generated link to a page
237
	 */
238
	protected function generateUrl($link)
239
	{
240
		return str_replace('{page}', $link, $this->paginationUrl);
241
	}
242
243
	/**
244
	 * Construct the pagination Url from the current Url and the configuration set
245
	 */
246
	protected function paginationUrl()
247
	{
248
		// if we have one, don't bother
249
		if ( ! empty($this->paginationUrl))
250
		{
251
			return;
252
		}
253
254
		// do we have any GET variables?
255
		$get = $this->input->getQuery();
256
257
		// do we need to set one
258
		if ( ! empty($this->config['getVariable']))
259
		{
260
			// don't use curly braces here, http_build_query will encode them
261
			$get[$this->config['getVariable']] = '___PAGE___';
262
		}
263
264
		// do we need to create a segment?
265
		if ( ! empty($this->config['uriSegment']))
266
		{
267
			$segments = explode('/', trim($this->input->getPathInfo(),'/'));
268
			$segments[$this->config['uriSegment']] = '{page}';
269
270
			// construct the Uri
271
			$this->paginationUrl = '/'.implode('/', $segments);
272
		}
273
		else
274
		{
275
			// start with the current Uri
276
			$this->paginationUrl = $this->input->getPathInfo();
277
		}
278
279
		// attach the extension if needed
280
		$this->paginationUrl .= $this->input->getExtension();
281
282
		// any get variables?
283
		if ( ! empty($get))
284
		{
285
			$this->paginationUrl .= '?'.str_replace('___PAGE___', '{page}', http_build_query($get->getContents()));
286
		}
287
	}
288
289
	/**
290
	 * If no current page number is given, calculate it
291
	 */
292
	protected function calculateNumbers()
293
	{
294
		// do we need to fetch or calculate the current page number?
295
		if (empty($this->config['current']))
296
		{
297
			// do we have a segment number?
298
			if ( ! empty($this->config['uriSegment']))
299
			{
300
				$segments = explode('/', trim($this->input->getPathInfo(),'/'));
301 View Code Duplication
				if (isset($segments[$this->config['uriSegment']]) and is_numeric($segments[$this->config['uriSegment']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302
				{
303
					$this->config['current'] = $segments[$this->config['uriSegment']];
304
				}
305
			}
306
307
			// do we have a getVariable set?
308
			if ( ! empty($this->config['getVariable']) and $get = $this->input->getQuery())
309
			{
310 View Code Duplication
				if (isset($get[$this->config['getVariable']]) and is_numeric($get[$this->config['getVariable']]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
311
				{
312
					$this->config['current'] = $get[$this->config['uriSegment']];
313
				}
314
			}
315
316
			// if none could be determine, try to calculate it
317 View Code Duplication
			if (empty($this->config['current']) and $this->config['offset'] and $this->config['limit'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
			{
319
				$this->config['current'] = (int) ($this->config['offset'] / $this->config['limit']) + 1;
320
			}
321
322
			// if all else fails, default to one
323
			if (empty($this->config['current']))
324
			{
325
				$this->config['current'] = 1;
326
			}
327
		}
328
329
		// do we need to calculate the total number of pages
330 View Code Duplication
		if (empty($this->config['totalPages']) and ! empty($this->config['totalItems']) and ! empty($this->config['limit']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
331
		{
332
			$this->config['totalPages'] = (int) ($this->config['totalItems'] / $this->config['limit']) + 1;
333
		}
334
	}
335
336
	/**
337
	 * Generate a pagination link
338
	 */
339
	protected function validateConfig($name, $value)
340
	{
341
		switch ($name)
342
		{
343
			case 'offset':
344
			case 'totalItems':
345
				// make sure it's an integer
346
				if ($value != intval($value))
347
				{
348
					$value = 0;
349
				}
350
				// and that it's within bounds
351
				$value = max(0, $value);
352
			break;
353
354
			// validate integer values
355
			case 'current':
356
			case 'limit':
357
			case 'totalPages':
358
			case 'numberOfLinks':
359
			case 'uriSegment':
360
				// make sure it's an integer
361
				if ($value != intval($value))
362
				{
363
					$value = 1;
364
				}
365
				// and that it's within bounds
366
				$value = max(1, $value);
367
			break;
368
369
			// validate booleans
370
			case 'showFirst':
371
			case 'showLast':
372
				if ( ! is_bool($value))
373
				{
374
					$value = (bool) $value;
375
				}
376
			break;
377
378
			// possible alignment values
379
			case 'align':
380
				if ( ! in_array($value = strtolower($value), array('left', 'center', 'right')))
381
				{
382
					$value = 'center';
383
				}
384
			break;
385
386
			// validate the link offset, and adjust if needed
387
			case 'linkOffset':
388
				// make sure we have a fraction between 0 and 1
389
				if ($value > 1)
390
				{
391
					$value = $value / 100;
392
				}
393
394
				// and that it's within bounds
395
				$value = max(0.01, min($value, 0.99));
396
			break;
397
		}
398
399
		return $value;
400
	}
401
}
402