Completed
Push — master ( 189ad5...73e21a )
by Jean-Christophe
04:04
created

HtmlGrid::asContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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