Completed
Push — master ( 2461e8...b569b1 )
by Jean-Christophe
04:01
created

HtmlGrid   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 279
Duplicated Lines 2.15 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 11
Bugs 0 Features 0
Metric Value
wmc 54
c 11
b 0
f 0
lcom 1
cbo 7
dl 6
loc 279
rs 7.0642

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A asSegment() 0 3 1
A setWide() 0 5 1
A setWidth() 0 3 1
A addRow() 0 5 1
A addCol() 0 7 2
A addCols() 0 6 2
B setRowsCount() 3 20 6
A hasOnlyCols() 0 3 2
B setColsCount() 3 17 7
A getRow() 0 3 1
A rowCount() 0 6 2
A colCount() 0 8 3
A getCell() 0 9 4
A setDivided() 0 4 2
A setCelled() 0 4 2
A setCentered() 0 3 1
A setEqualWidth() 0 3 1
A setPadded() 0 5 2
A setRelaxed() 0 4 2
A setVerticalAlignment() 0 3 1
A createItem() 0 6 2
B setValues() 0 13 5
A setStretched() 0 3 1

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 HtmlGrid 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 HtmlGrid, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Ajax\semantic\html\collections;
4
5
use Ajax\common\html\HtmlCollection;
6
use Ajax\semantic\html\content\HtmlGridRow;
7
use Ajax\semantic\html\base\constants\Wide;
8
use Ajax\semantic\html\base\constants\VerticalAlignment;
9
use Ajax\semantic\html\base\HtmlSemCollection;
10
use Ajax\semantic\html\base\traits\TextAlignmentTrait;
11
use Ajax\semantic\html\content\HtmlGridCol;
12
13
/**
14
 * Semantic Grid component
15
 * @see http://semantic-ui.com/collections/grid.html
16
 * @author jc
17
 * @version 1.001
18
 */
19
class HtmlGrid extends HtmlSemCollection{
20
	use TextAlignmentTrait;
21
	private $_createCols;
22
	private $_colSizing=true;
23
	private $_implicitRows=false;
24
25
	public function __construct( $identifier,$numRows=1,$numCols=NULL,$createCols=true,$implicitRows=false){
26
		parent::__construct( $identifier, "div","ui grid");
27
		$this->_implicitRows=$implicitRows;
28
		$this->_createCols=$createCols;
29
		if(isset($numCols)){
30
			//if($this->_createCols){
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
31
				$this->_colSizing=false;
32
			//}
33
			$this->setWide($numCols);
34
		}
35
		$this->setRowsCount($numRows,$numCols);
36
	}
37
38
	public function asSegment(){
39
		return $this->addToPropertyCtrl("class", "segment", array("segment"));
40
	}
41
42
	/**
43
	 * Defines the grid width (alias for setWidth)
44
	 * @param int $wide
45
	 */
46
	public function setWide($wide){
47
		$wide=Wide::getConstants()["W".$wide];
48
		$this->addToPropertyCtrl("class", $wide, Wide::getConstants());
49
		return $this->addToPropertyCtrl("class","column",array("column"));
50
	}
51
52
	/**
53
	 * Defines the grid width
54
	 * @param int $width
55
	 * @return \Ajax\semantic\html\collections\HtmlGrid
56
	 */
57
	public function setWidth($width){
58
	return $this->setWide($width);
59
	}
60
61
	/**
62
	 * Adds a row with $colsCount columns
63
	 * @param int $colsCount number of columns to create
64
	 * @return mixed
65
	 */
66
	public function addRow($colsCount=NULL){
67
		$rowCount=$this->rowCount()+1;
68
		$this->setRowsCount($rowCount,$colsCount,true);
69
		return $this->content[$rowCount-1];
70
	}
71
72
	/**
73
	 * Adds a col
74
	 * @param int $width with of the column to add
75
	 * @return mixed|\Ajax\semantic\html\collections\HtmlGrid
76
	 */
77
	public function addCol($width=NULL){
78
		$colCount=$this->colCount()+1;
79
		$this->setColsCount($colCount,true,$width);
80
		if($this->hasOnlyCols($this->count()))
81
			return $this->content[$colCount-1];
82
		return $this;
83
	}
84
85
	/**
86
	 * @param array $sizes array of width of the columns to create
87
	 * @return \Ajax\semantic\html\collections\HtmlGrid
88
	 */
89
	public function addCols($sizes=array()){
90
		foreach ($sizes as $size){
91
			$this->addCol($size);
92
		}
93
		return $this;
94
	}
95
96
	/**
97
	 * Create $rowsCount rows
98
	 * @param int $rowsCount
99
	 * @param int $colsCount
100
	 * @return \Ajax\semantic\html\collections\HtmlGrid
101
	 */
102
	public function setRowsCount($rowsCount,$colsCount=NULL,$force=false){
103
		$count=$this->count();
104
		if($rowsCount<2 && $force===false){
105 View Code Duplication
			for($i=$count;$i<$colsCount;$i++){
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...
106
				$this->addItem(new HtmlGridCol("col-".$this->identifier."-".$i));
107
			}
108
		}else{
109
			if($this->hasOnlyCols($count)){
110
				$tmpContent=$this->content;
111
				$item=$this->addItem($colsCount);
112
				$item->setContent($tmpContent);
113
				$this->content=array();
114
				$count=1;
115
			}
116
			for($i=$count;$i<$rowsCount;$i++){
117
				$this->addItem($colsCount);
118
			}
119
		}
120
		return $this;
121
	}
122
123
	protected function hasOnlyCols($count){
124
		return $count>0 && $this->content[0] instanceof HtmlGridCol;
125
	}
126
127
	/**
128
	 * Defines the number of columns in the grid
129
	 * @param int $numCols
130
	 * @param boolean $toCreate
131
	 * @param int $width
132
	 * @return \Ajax\semantic\html\collections\HtmlGrid
133
	 */
134
	public function setColsCount($numCols,$toCreate=true,$width=NULL){
135
		if(isset($width)===false)
136
			$this->setWide($numCols);
137
		if($toCreate==true){
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
138
			$count=$this->count();
139
			if($count==0 || $this->hasOnlyCols($count)){
140 View Code Duplication
				for($i=$count;$i<$numCols;$i++){
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...
141
					$this->addItem(new HtmlGridCol("col-".$this->identifier."-".$i,$width));
142
				}
143
			}else{
144
				for($i=0;$i<$count;$i++){
145
					$this->getItem($i)->setColsCount($numCols);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Ajax\common\html\HtmlDoubleElement as the method setColsCount() does only exist in the following sub-classes of Ajax\common\html\HtmlDoubleElement: Ajax\semantic\html\collections\HtmlGrid, Ajax\semantic\html\content\HtmlGridRow. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
146
				}
147
			}
148
		}
149
		return $this;
150
	}
151
152
	/**
153
	 * return the row at $index
154
	 * @param int $index
155
	 * @return \Ajax\semantic\html\collections\HtmlGridRow
156
	 */
157
	public function getRow($index){
158
		return $this->getItem($index);
159
	}
160
161
	/**
162
	 * Returns the row count
163
	 * @return int
164
	 */
165
	public function rowCount(){
166
		$count=$this->count();
167
		if($this->hasOnlyCols($count))
168
			return 0;
169
		return $count;
170
	}
171
172
	/**
173
	 * Returns the column count
174
	 * @return int
175
	 */
176
	public function colCount(){
177
		$count=$this->count();
178
		if($this->hasOnlyCols($count))
179
			return $count;
180
		if($count>0)
181
			return $this->getItem(0)->count();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Ajax\common\html\HtmlDoubleElement as the method count() does only exist in the following sub-classes of Ajax\common\html\HtmlDoubleElement: Ajax\common\html\HtmlCollection, Ajax\common\html\html5\HtmlList, Ajax\semantic\html\base\HtmlSemCollection, Ajax\semantic\html\base\HtmlSemNavElement, Ajax\semantic\html\collections\HtmlBreadcrumb, Ajax\semantic\html\collections\HtmlGrid, Ajax\semantic\html\collections\form\HtmlForm, Ajax\semantic\html\collections\form\HtmlFormFields, Ajax\semantic\html\colle...menus\HtmlAccordionMenu, Ajax\semantic\html\collections\menus\HtmlIconMenu, Ajax\semantic\html\colle...nus\HtmlLabeledIconMenu, Ajax\semantic\html\collections\menus\HtmlMenu, Ajax\semantic\html\content\HtmlGridRow, Ajax\semantic\html\elements\HtmlList, Ajax\semantic\html\elements\HtmlSegmentGroups, Ajax\semantic\html\elements\HtmlStep, Ajax\semantic\html\modules\HtmlAccordion, Ajax\semantic\html\modules\HtmlDropdown. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
182
		return 0;
183
	}
184
185
	/**
186
	 * Returns the cell (HtmlGridCol) at position rrow,$col
187
	 * @param int $row
188
	 * @param int $col
189
	 * @return \Ajax\semantic\html\collections\HtmlGridCol
190
	 */
191
	public function getCell($row,$col){
192
		if($row<2 && $this->hasOnlyCols($this->count()))
193
			return $this->getItem($col);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getItem($col); (Ajax\common\html\HtmlDoubleElement) is incompatible with the return type documented by Ajax\semantic\html\collections\HtmlGrid::getCell of type Ajax\semantic\html\collections\HtmlGridCol.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
194
		$row=$this->getItem($row);
195
		if(isset($row)){
196
			$col=$row->getItem($col);
197
		}
198
		return $col;
199
	}
200
201
	/**
202
	 * Adds dividers between columns ($vertically=false) or between rows ($vertically=true)
203
	 * @param boolean $vertically
204
	 * @return \Ajax\semantic\html\collections\HtmlGrid
205
	 */
206
	public function setDivided($vertically=false){
207
		$value=($vertically===true)?"vertically divided":"divided";
208
		return $this->addToPropertyCtrl("class", $value,array("divided"));
209
	}
210
211
	/**
212
	 * Divides rows into cells
213
	 * @param boolean $internally true for internal cells
214
	 * @return \Ajax\semantic\html\collections\HtmlGrid
215
	 */
216
	public function setCelled($internally=false){
217
		$value=($internally===true)?"internally celled":"celled";
218
		return $this->addToPropertyCtrl("class", $value,array("celled","internally celled"));
219
	}
220
221
	/**
222
	 * A grid can have its columns centered
223
	 */
224
	public function setCentered(){
225
		return $this->addToPropertyCtrl("class", "centered",array("centered"));
226
	}
227
228
	/**
229
	 * automatically resize all elements to split the available width evenly
230
	 * @return \Ajax\semantic\html\collections\HtmlGrid
231
	 */
232
	public function setEqualWidth(){
233
		return $this->addToProperty("class", "equal width");
234
	}
235
236
	/**
237
	 * Adds vertical or/and horizontal gutters
238
	 * @param string $value
239
	 * @return \Ajax\semantic\html\collections\HtmlGrid
240
	 */
241
	public function setPadded($value=NULL){
242
		if(isset($value))
243
			$this->addToPropertyCtrl("class", $value,array("vertically","horizontally"));
244
		return $this->addToProperty("class", "padded");
245
	}
246
247
	/**
248
	 * @param boolean $very
249
	 * @return \Ajax\semantic\html\collections\HtmlGrid
250
	 */
251
	public function setRelaxed($very=false){
252
		$value=($very===true)?"very relaxed":"relaxed";
253
		return $this->addToPropertyCtrl("class", $value,array("relaxed","very relaxed"));
254
	}
255
256
	public function setVerticalAlignment($value=VerticalAlignment::MIDDLE){
257
		return $this->addToPropertyCtrl("class", $value." aligned",VerticalAlignment::getConstantValues("aligned"));
258
	}
259
260
	/**
261
	 * {@inheritDoc}
262
	 * @see \Ajax\common\html\HtmlCollection::createItem()
263
	 */
264
	protected function createItem($value){
265
		if($this->_createCols===false)
266
			$value=null;
267
		$item=new HtmlGridRow($this->identifier."-row-".($this->count()+1),$value,$this->_colSizing,$this->_implicitRows);
268
		return $item;
269
	}
270
271
	/**
272
	 * Sets $values to the grid
273
	 * @param array $values
274
	 */
275
	public function setValues($values,$force=true){
276
		$count=$this->count();
277
		if($this->_createCols===false || $force===true){
278
			for($i=$count;$i<\sizeof($values);$i++){
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
279
				$colSize=\sizeof($values[$i]);
280
				$this->addItem(new HtmlGridRow($this->identifier."-row-".($this->count()+1),$colSize,$this->_colSizing,$this->_implicitRows));
281
			}
282
		}
283
		$count=\min(array($this->count(),\sizeof($values)));
284
		for($i=0;$i<$count;$i++){
285
			$this->content[$i]->setValues($values[$i],$this->_createCols===false);
286
		}
287
	}
288
289
	/**
290
	 * stretch the row contents to take up the entire column height
291
	 * @return \Ajax\semantic\html\content\HtmlGridRow
292
	 */
293
	public function setStretched(){
294
		return $this->addToProperty("class", "stretched");
295
	}
296
297
}