Completed
Push — master ( 8b7375...189ad5 )
by Jean-Christophe
03:49
created

HtmlGrid::setCelled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 3
nc 2
nop 1
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
use Ajax\semantic\html\elements\HtmlDivider;
13
14
/**
15
 * Semantic Grid component
16
 * @see http://semantic-ui.com/collections/grid.html
17
 * @author jc
18
 * @version 1.001
19
 */
20
class HtmlGrid extends HtmlSemCollection {
21
	use TextAlignmentTrait;
22
	private $_createCols;
23
	private $_colSizing=true;
24
	private $_implicitRows=false;
25
26
	public function __construct($identifier, $numRows=1, $numCols=NULL, $createCols=true, $implicitRows=false) {
27
		parent::__construct($identifier, "div", "ui grid");
28
		$this->_implicitRows=$implicitRows;
29
		$this->_createCols=$createCols;
30
		if (isset($numCols)) {
31
			// if($this->_createCols){
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% 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...
32
			$this->_colSizing=false;
33
			// }
34
			$this->setWide($numCols);
35
		}
36
		$this->setRowsCount($numRows, $numCols);
37
	}
38
39
	public function asSegment() {
40
		return $this->addToPropertyCtrl("class", "segment", array ("segment" ));
41
	}
42
43
	/**
44
	 * Defines the grid width (alias for setWidth)
45
	 * @param int $wide
46
	 */
47
	public function setWide($wide) {
48
		$wide=Wide::getConstants()["W" . $wide];
49
		$this->addToPropertyCtrl("class", $wide, Wide::getConstants());
50
		return $this->addToPropertyCtrl("class", "column", array ("column" ));
51
	}
52
53
	/**
54
	 * Defines the grid width
55
	 * @param int $width
56
	 * @return \Ajax\semantic\html\collections\HtmlGrid
57
	 */
58
	public function setWidth($width) {
59
		return $this->setWide($width);
60
	}
61
62
	/**
63
	 * Adds a row with $colsCount columns
64
	 * @param int $colsCount number of columns to create
65
	 * @return mixed
66
	 */
67
	public function addRow($colsCount=NULL) {
68
		$rowCount=$this->rowCount() + 1;
69
		$this->setRowsCount($rowCount, $colsCount, true);
70
		return $this->content[$rowCount - 1];
71
	}
72
73
	/**
74
	 * Adds a col
75
	 * @param int $width with of the column to add
76
	 * @return mixed|\Ajax\semantic\html\collections\HtmlGrid
77
	 */
78
	public function addCol($width=NULL) {
79
		$colCount=$this->colCount() + 1;
80
		$this->setColsCount($colCount, true, $width);
81
		if ($this->hasOnlyCols($this->count()))
82
			return $this->content[$colCount - 1];
83
		return $this;
84
	}
85
86
	/**
87
	 *
88
	 * @param array $sizes array of width of the columns to create
89
	 * @return \Ajax\semantic\html\collections\HtmlGrid
90
	 */
91
	public function addCols($sizes=array()) {
92
		foreach ( $sizes as $size ) {
93
			$this->addCol($size);
94
		}
95
		return $this;
96
	}
97
98
	/**
99
	 * Create $rowsCount rows
100
	 * @param int $rowsCount
101
	 * @param int $colsCount
102
	 * @return \Ajax\semantic\html\collections\HtmlGrid
103
	 */
104
	public function setRowsCount($rowsCount, $colsCount=NULL, $force=false) {
105
		$count=$this->count();
106
		if ($rowsCount < 2 && $force === false) {
107 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...
108
				$this->addItem(new HtmlGridCol("col-" . $this->identifier . "-" . $i));
109
			}
110
		} else {
111
			if ($this->hasOnlyCols($count)) {
112
				$tmpContent=$this->content;
113
				$item=$this->addItem($colsCount);
114
				$item->setContent($tmpContent);
115
				$this->content=array ();
116
				$count=1;
117
			}
118
			for($i=$count; $i < $rowsCount; $i++) {
119
				$this->addItem($colsCount);
120
			}
121
		}
122
		return $this;
123
	}
124
125
	protected function hasOnlyCols($count) {
126
		return $count > 0 && $this->content[0] instanceof HtmlGridCol;
127
	}
128
129
	/**
130
	 * Defines the number of columns in the grid
131
	 * @param int $numCols
132
	 * @param boolean $toCreate
133
	 * @param int $width
134
	 * @return \Ajax\semantic\html\collections\HtmlGrid
135
	 */
136
	public function setColsCount($numCols, $toCreate=true, $width=NULL) {
137
		if (isset($width)) {
138
			$this->setWide($numCols);
139
		}
140
		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...
141
			$count=$this->count();
142
			if ($count == 0 || $this->hasOnlyCols($count)) {
143 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...
144
					$this->addItem(new HtmlGridCol("col-" . $this->identifier . "-" . $i, $width));
145
				}
146
			} else {
147
				for($i=0; $i < $count; $i++) {
148
					$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...
149
				}
150
			}
151
		}
152
		return $this;
153
	}
154
155
	/**
156
	 * return the row at $index
157
	 * @param int $index
158
	 * @return \Ajax\semantic\html\collections\HtmlGridRow
159
	 */
160
	public function getRow($index) {
161
		return $this->getItem($index);
162
	}
163
164
	/**
165
	 * Returns the row count
166
	 * @return int
167
	 */
168
	public function rowCount() {
169
		$count=$this->count();
170
		if ($this->hasOnlyCols($count))
171
			return 0;
172
		return $count;
173
	}
174
175
	/**
176
	 * Returns the column count
177
	 * @return int
178
	 */
179
	public function colCount() {
180
		$count=$this->count();
181
		if ($this->hasOnlyCols($count))
182
			return $count;
183
		if ($count > 0)
184
			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\colle...enus\HtmlPaginationMenu, Ajax\semantic\html\content\HtmlGridRow, Ajax\semantic\html\content\table\HtmlTR, Ajax\semantic\html\content\table\HtmlTableContent, Ajax\semantic\html\elements\HtmlIconGroups, 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, Ajax\semantic\html\views\HtmlCardGroups. 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...
185
		return 0;
186
	}
187
188
	/**
189
	 * Returns the cell (HtmlGridCol) at position rrow,$col
190
	 * @param int $row
191
	 * @param int $col
192
	 * @return \Ajax\semantic\html\collections\HtmlGridCol
193
	 */
194
	public function getCell($row, $col) {
195
		if ($row < 2 && $this->hasOnlyCols($this->count()))
196
			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...
197
		$row=$this->getItem($row);
198
		if (isset($row)) {
199
			$col=$row->getItem($col);
200
		}
201
		return $col;
202
	}
203
204
	/**
205
	 * Adds dividers between columns ($vertically=false) or between rows ($vertically=true)
206
	 * @param boolean $vertically
207
	 * @return \Ajax\semantic\html\collections\HtmlGrid
208
	 */
209
	public function setDivided($vertically=false) {
210
		$value=($vertically === true) ? "vertically divided" : "divided";
211
		return $this->addToPropertyCtrl("class", $value, array ("divided" ));
212
	}
213
214
	/**
215
	 * Divides rows into cells
216
	 * @param boolean $internally true for internal cells
217
	 * @return \Ajax\semantic\html\collections\HtmlGrid
218
	 */
219
	public function setCelled($internally=false) {
220
		$value=($internally === true) ? "internally celled" : "celled";
221
		return $this->addToPropertyCtrl("class", $value, array ("celled","internally celled" ));
222
	}
223
224
	/**
225
	 * A grid can have its columns centered
226
	 */
227
	public function setCentered() {
228
		return $this->addToPropertyCtrl("class", "centered", array ("centered" ));
229
	}
230
231
	/**
232
	 * automatically resize all elements to split the available width evenly
233
	 * @return \Ajax\semantic\html\collections\HtmlGrid
234
	 */
235
	public function setEqualWidth() {
236
		return $this->addToProperty("class", "equal width");
237
	}
238
239
	/**
240
	 * Adds vertical or/and horizontal gutters
241
	 * @param string $value
242
	 * @return \Ajax\semantic\html\collections\HtmlGrid
243
	 */
244
	public function setPadded($value=NULL) {
245
		if (isset($value))
246
			$this->addToPropertyCtrl("class", $value, array ("vertically","horizontally" ));
247
		return $this->addToProperty("class", "padded");
248
	}
249
250
	/**
251
	 *
252
	 * @param boolean $very
253
	 * @return \Ajax\semantic\html\collections\HtmlGrid
254
	 */
255
	public function setRelaxed($very=false) {
256
		$value=($very === true) ? "very relaxed" : "relaxed";
257
		return $this->addToPropertyCtrl("class", $value, array ("relaxed","very relaxed" ));
258
	}
259
260
	public function setVerticalAlignment($value=VerticalAlignment::MIDDLE) {
261
		return $this->addToPropertyCtrl("class", $value . " aligned", VerticalAlignment::getConstantValues("aligned"));
262
	}
263
264
	/**
265
	 *
266
	 * {@inheritDoc}
267
	 *
268
	 * @see \Ajax\common\html\HtmlCollection::createItem()
269
	 */
270
	protected function createItem($value) {
271
		if ($this->_createCols === false)
272
			$value=null;
273
		$item=new HtmlGridRow($this->identifier . "-row-" . ($this->count() + 1), $value, $this->_colSizing, $this->_implicitRows);
274
		return $item;
275
	}
276
277
	/**
278
	 * Sets $values to the grid
279
	 * @param array $values
280
	 */
281
	public function setValues($values, $force=true) {
282
		$count=$this->count();
283
		if ($this->_createCols === false || $force === true) {
284
			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...
285
				$colSize=\sizeof($values[$i]);
286
				$this->addItem(new HtmlGridRow($this->identifier . "-row-" . ($this->count() + 1), $colSize, $this->_colSizing, $this->_implicitRows));
287
			}
288
		}
289
		$count=\min(array ($this->count(),\sizeof($values) ));
290
		for($i=0; $i < $count; $i++) {
291
			$this->content[$i]->setValues($values[$i], $this->_createCols === false);
292
		}
293
	}
294
295
	/**
296
	 * stretch the row contents to take up the entire column height
297
	 * @return \Ajax\semantic\html\content\HtmlGridRow
298
	 */
299
	public function setStretched() {
300
		return $this->addToProperty("class", "stretched");
301
	}
302
303
	public function addDivider($afterColIndex, $vertical=true, $content=NULL) {
304
		$col=$this->getCell(0, $afterColIndex);
305
		return $col->addDivider($vertical, $content);
306
	}
307
}