Browse Source

Merge pull request #211 from dereuromark/phpstan

Phpstan
Mark Sch 7 years ago
parent
commit
04c861d980

+ 12 - 12
.travis.yml

@@ -5,7 +5,6 @@ sudo: false
 php:
 php:
   - 5.6
   - 5.6
   - 7.0
   - 7.0
-  - 7.1
   - 7.2
   - 7.2
 
 
 env:
 env:
@@ -18,26 +17,26 @@ matrix:
   fast_finish: true
   fast_finish: true
 
 
   include:
   include:
-    - php: 7.1
-      env: PHPCS=1 DEFAULT=0
-
-    - php: 7.1
-      env: CODECOVERAGE=1 DEFAULT=0
-
-    - php: 7.1
+    - php: 7.2
       env: DB=pgsql db_dsn='postgres://postgres@127.0.0.1/cakephp_test'
       env: DB=pgsql db_dsn='postgres://postgres@127.0.0.1/cakephp_test'
-      
-    - php: 7.1
+
+    - php: 7.2
       env: DB=sqlite db_dsn='sqlite:///:memory:'
       env: DB=sqlite db_dsn='sqlite:///:memory:'
 
 
     - php: 5.6
     - php: 5.6
       env: PREFER_LOWEST=1
       env: PREFER_LOWEST=1
 
 
+    - php: 7.2
+      env: CHECKS=1 DEFAULT=0
+
+    - php: 7.2
+      env: CODECOVERAGE=1 DEFAULT=0
+
 before_script:
 before_script:
   - if [[ $PREFER_LOWEST != 1 ]]; then composer install --prefer-source --no-interaction ; fi
   - if [[ $PREFER_LOWEST != 1 ]]; then composer install --prefer-source --no-interaction ; fi
   - if [[ $PREFER_LOWEST == 1 ]]; then composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable ; fi
   - if [[ $PREFER_LOWEST == 1 ]]; then composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable ; fi
 
 
-  - if [[ $PHPCS != 1 ]]; then composer require phpunit/phpunit:"^5.7.14|^6.0"; fi
+  - if [[ $CHECKS != 1 ]]; then composer require phpunit/phpunit:"^5.7.14|^6.0"; fi
 
 
   - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi"
   - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi"
   - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi"
   - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi"
@@ -48,7 +47,8 @@ before_script:
 
 
 script:
 script:
   - if [[ $DEFAULT == 1 ]]; then vendor/bin/phpunit; fi
   - if [[ $DEFAULT == 1 ]]; then vendor/bin/phpunit; fi
-  - if [[ $PHPCS == 1 ]]; then vendor/bin/phpcs -p --extensions=php --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --ignore=/tests/test_files/ src tests config ; fi
+  - if [[ $CHECKS == 1 ]]; then vendor/bin/phpcs -p --extensions=php --standard=vendor/fig-r/psr2r-sniffer/PSR2R/ruleset.xml --ignore=/tests/test_files/ src tests config ; fi
+  - if [[ $CHECKS == 1 ]]; then composer phpstan-setup && composer phpstan ; fi
 
 
   - if [[ $CODECOVERAGE == 1 ]]; then vendor/bin/phpunit --coverage-clover=clover.xml || true; fi
   - if [[ $CODECOVERAGE == 1 ]]; then vendor/bin/phpunit --coverage-clover=clover.xml || true; fi
   - if [[ $CODECOVERAGE == 1 ]]; then wget -O codecov.sh https://codecov.io/bash; fi
   - if [[ $CODECOVERAGE == 1 ]]; then wget -O codecov.sh https://codecov.io/bash; fi

+ 3 - 0
composer.json

@@ -33,6 +33,7 @@
 		"psr-4": {
 		"psr-4": {
 			"Tools\\Test\\": "tests/",
 			"Tools\\Test\\": "tests/",
 			"Cake\\Test\\": "vendor/cakephp/cakephp/tests/",
 			"Cake\\Test\\": "vendor/cakephp/cakephp/tests/",
+			"Cake\\PHPStan\\": "vendor/cakephp/cakephp/tests/PHPStan/",
 			"App\\": "tests/test_app/"
 			"App\\": "tests/test_app/"
 		}
 		}
 	},
 	},
@@ -44,6 +45,8 @@
 		"issues": "https://github.com/dereuromark/cakephp-tools/issues"
 		"issues": "https://github.com/dereuromark/cakephp-tools/issues"
 	},
 	},
 	"scripts": {
 	"scripts": {
+		"phpstan": "phpstan analyse -c tests/phpstan.neon -l 3 src/",
+		"phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^0.10.1 && mv composer.backup composer.json",
 		"test": "php phpunit.phar",
 		"test": "php phpunit.phar",
 		"test-setup": "[ ! -f phpunit.phar ] && wget https://phar.phpunit.de/phpunit-5.7.20.phar && mv phpunit-5.7.20.phar phpunit.phar || true",
 		"test-setup": "[ ! -f phpunit.phar ] && wget https://phar.phpunit.de/phpunit-5.7.20.phar && mv phpunit-5.7.20.phar phpunit.phar || true",
 		"test-coverage": "php phpunit.phar --log-junit webroot/coverage/unitreport.xml --coverage-html webroot/coverage --coverage-clover webroot/coverage/coverage.xml",
 		"test-coverage": "php phpunit.phar --log-junit webroot/coverage/unitreport.xml --coverage-html webroot/coverage --coverage-clover webroot/coverage/coverage.xml",

+ 4 - 4
src/Controller/Component/CommonComponent.php

@@ -25,11 +25,11 @@ class CommonComponent extends Component {
 	 */
 	 */
 	public function startup(Event $event) {
 	public function startup(Event $event) {
 		// Data preparation
 		// Data preparation
-		if (!empty($this->Controller->request->data) && !Configure::read('DataPreparation.notrim')) {
-			$this->Controller->request->data = Utility::trimDeep($this->Controller->request->data);
+		if ($this->Controller->request->getData() && !Configure::read('DataPreparation.notrim')) {
+			$this->Controller->request->data = Utility::trimDeep($this->Controller->request->getData());
 		}
 		}
-		if (!empty($this->Controller->request->query) && !Configure::read('DataPreparation.notrim')) {
-			$this->Controller->request->query = Utility::trimDeep($this->Controller->request->query);
+		if ($this->Controller->request->getQuery() && !Configure::read('DataPreparation.notrim')) {
+			$this->Controller->request->query = Utility::trimDeep($this->Controller->request->getQuery());
 		}
 		}
 		if (!empty($this->Controller->request->params['pass']) && !Configure::read('DataPreparation.notrim')) {
 		if (!empty($this->Controller->request->params['pass']) && !Configure::read('DataPreparation.notrim')) {
 			$this->Controller->request->params['pass'] = Utility::trimDeep($this->Controller->request->params['pass']);
 			$this->Controller->request->params['pass'] = Utility::trimDeep($this->Controller->request->params['pass']);

+ 1 - 1
src/Controller/Component/UrlComponent.php

@@ -111,7 +111,7 @@ class UrlComponent extends Component {
 		if (!isset($url['?'])) {
 		if (!isset($url['?'])) {
 			$url['?'] = [];
 			$url['?'] = [];
 		}
 		}
-		$url['?'] += $this->request->query;
+		$url['?'] += $this->request->getQuery();
 
 
 		return $url;
 		return $url;
 	}
 	}

+ 1 - 1
src/Controller/Controller.php

@@ -20,7 +20,7 @@ class Controller extends ShimController {
 	 *   (e.g: Table instance, 'TableName' or a Query object)
 	 *   (e.g: Table instance, 'TableName' or a Query object)
 	 * @param array $settings Settings
 	 * @param array $settings Settings
 	 *
 	 *
-	 * @return \Cake\ORM\ResultSet Query results
+	 * @return \Cake\ORM\ResultSet|\Cake\Datasource\ResultSetInterface Query results
 	 */
 	 */
 	public function paginate($object = null, array $settings = []) {
 	public function paginate($object = null, array $settings = []) {
 		$defaultSettings = (array)Configure::read('Paginator');
 		$defaultSettings = (array)Configure::read('Paginator');

+ 13 - 23
src/Mailer/Email.php

@@ -26,7 +26,7 @@ class Email extends CakeEmail {
 	protected $_error = null;
 	protected $_error = null;
 
 
 	/**
 	/**
-	 * @var bool|null
+	 * @var array|null
 	 */
 	 */
 	protected $_debug = null;
 	protected $_debug = null;
 
 
@@ -36,6 +36,11 @@ class Email extends CakeEmail {
 	protected $_log = [];
 	protected $_log = [];
 
 
 	/**
 	/**
+	 * @var \Tools\Utility\Mime|null
+	 */
+	protected $_Mime;
+
+	/**
 	 * @param string|null $config
 	 * @param string|null $config
 	 */
 	 */
 	public function __construct($config = null) {
 	public function __construct($config = null) {
@@ -46,21 +51,6 @@ class Email extends CakeEmail {
 	}
 	}
 
 
 	/**
 	/**
-	 * Change the layout
-	 *
-	 * @deprecated Since CakePHP 3.4.0-RC4 in core as getLayout()/setLayout()
-	 *
-	 * @param string|bool $layout Layout to use (or false to use none)
-	 * @return $this
-	 */
-	public function layout($layout = false) {
-		if ($layout !== false) {
-			$this->_layout = $layout;
-		}
-		return $this;
-	}
-
-	/**
 	 * Sets wrap length.
 	 * Sets wrap length.
 	 *
 	 *
 	 * @param int $length Must not be more than CakeEmail::LINE_LENGTH_MUST
 	 * @param int $length Must not be more than CakeEmail::LINE_LENGTH_MUST
@@ -181,7 +171,7 @@ class Email extends CakeEmail {
 	 */
 	 */
 	public function profile($config = null) {
 	public function profile($config = null) {
 		if ($config === null) {
 		if ($config === null) {
-			return $this->_profile;
+			return $this->getProfile();
 		}
 		}
 
 
 		return $this->setProfile($config);
 		return $this->setProfile($config);
@@ -321,7 +311,7 @@ class Email extends CakeEmail {
 	 * @param string|null $name (optional)
 	 * @param string|null $name (optional)
 	 * @param array|string|null $options Options - string CID is deprecated
 	 * @param array|string|null $options Options - string CID is deprecated
 	 * @param array $notUsed Former Options @deprecated 4th param is now 3rd since CakePHP 3.4.0 - Use addEmbeddedAttachmentByContentId() otherwise.
 	 * @param array $notUsed Former Options @deprecated 4th param is now 3rd since CakePHP 3.4.0 - Use addEmbeddedAttachmentByContentId() otherwise.
-	 * @return string CID ($this is deprecated!)
+	 * @return string|null CID (null is deprecated)
 	 */
 	 */
 	public function addEmbeddedAttachment($file, $name = null, $options = null, array $notUsed = []) {
 	public function addEmbeddedAttachment($file, $name = null, $options = null, array $notUsed = []) {
 		if (empty($name)) {
 		if (empty($name)) {
@@ -346,13 +336,13 @@ class Email extends CakeEmail {
 		}
 		}
 		$options['contentId'] = $contentId ?: str_replace('-', '', Text::uuid()) . '@' . $this->_domain;
 		$options['contentId'] = $contentId ?: str_replace('-', '', Text::uuid()) . '@' . $this->_domain;
 		$file = [$name => $options];
 		$file = [$name => $options];
-		$res = $this->addAttachments($file);
+		$this->addAttachments($file);
 		if ($contentId === null) {
 		if ($contentId === null) {
 			return $options['contentId'];
 			return $options['contentId'];
 		}
 		}
 
 
 		// Deprecated
 		// Deprecated
-		return $res;
+		return $contentId;
 	}
 	}
 
 
 	/**
 	/**
@@ -397,7 +387,7 @@ class Email extends CakeEmail {
 	 * @param string|null $mimeType (leave it empty to get mimetype from $filename)
 	 * @param string|null $mimeType (leave it empty to get mimetype from $filename)
 	 * @param array|string|null $options Options - string CID is deprecated
 	 * @param array|string|null $options Options - string CID is deprecated
 	 * @param array $notUsed
 	 * @param array $notUsed
-	 * @return string CID CcontentId ($this is deprecated)
+	 * @return string|null CID CcontentId (null is deprecated)
 	 */
 	 */
 	public function addEmbeddedBlobAttachment($content, $filename, $mimeType = null, $options = null, array $notUsed = []) {
 	public function addEmbeddedBlobAttachment($content, $filename, $mimeType = null, $options = null, array $notUsed = []) {
 		if ($mimeType === null) {
 		if ($mimeType === null) {
@@ -421,13 +411,13 @@ class Email extends CakeEmail {
 		$options['mimetype'] = $mimeType;
 		$options['mimetype'] = $mimeType;
 		$options['contentId'] = $contentId ? $contentId : str_replace('-', '', Text::uuid()) . '@' . $this->_domain;
 		$options['contentId'] = $contentId ? $contentId : str_replace('-', '', Text::uuid()) . '@' . $this->_domain;
 		$file = [$filename => $options];
 		$file = [$filename => $options];
-		$res = $this->addAttachments($file);
+		$this->addAttachments($file);
 		if ($contentId === null) {
 		if ($contentId === null) {
 			return $options['contentId'];
 			return $options['contentId'];
 		}
 		}
 
 
 		// Deprecated
 		// Deprecated
-		return $res;
+		return $contentId;
 	}
 	}
 
 
 	/**
 	/**

+ 9 - 4
src/Model/Behavior/BitmaskedBehavior.php

@@ -117,6 +117,10 @@ class BitmaskedBehavior extends Behavior {
 		}
 		}
 
 
 		$mapper = function ($row, $key, $mr) use ($field, $mappedField) {
 		$mapper = function ($row, $key, $mr) use ($field, $mappedField) {
+			/**
+			 * @var \Cake\Collection\Iterator\MapReduce $mr
+			 * @var \Cake\Datasource\EntityInterface|array $row
+			 */
 			if (!is_object($row)) {
 			if (!is_object($row)) {
 				if (isset($row[$field])) {
 				if (isset($row[$field])) {
 					$row[$mappedField] = $this->decodeBitmask($row[$field]);
 					$row[$mappedField] = $this->decodeBitmask($row[$field]);
@@ -125,10 +129,11 @@ class BitmaskedBehavior extends Behavior {
 				return;
 				return;
 			}
 			}
 
 
-			/** @var \Cake\Datasource\EntityInterface $row */
-			$row->set($mappedField, $this->decodeBitmask($row->get($field)));
-			$row->setDirty($mappedField, false);
-			$mr->emit($row);
+			/** @var \Cake\Datasource\EntityInterface $entity */
+			$entity = $row;
+			$entity->set($mappedField, $this->decodeBitmask($entity->get($field)));
+			$entity->setDirty($mappedField, false);
+			$mr->emit($entity);
 		};
 		};
 		$query->mapReduce($mapper);
 		$query->mapReduce($mapper);
 	}
 	}

+ 3 - 2
src/Model/Behavior/TypographicBehavior.php

@@ -98,10 +98,11 @@ class TypographicBehavior extends Behavior {
 	 */
 	 */
 	public function initialize(array $config = []) {
 	public function initialize(array $config = []) {
 		if (empty($this->_config['fields'])) {
 		if (empty($this->_config['fields'])) {
-			$schema = $this->getTable()->schema();
+			$schema = $this->getTable()->getSchema();
 
 
+			$fields = [];
 			foreach ($schema->columns() as $field) {
 			foreach ($schema->columns() as $field) {
-				$v = $schema->column($field);
+				$v = $schema->getColumn($field);
 				if (!in_array($v['type'], ['string', 'text'])) {
 				if (!in_array($v['type'], ['string', 'text'])) {
 					continue;
 					continue;
 				}
 				}

+ 18 - 12
src/Model/Table/Table.php

@@ -105,6 +105,8 @@ class Table extends ShimTable {
 	/**
 	/**
 	 * Get all related entries that have been used so far
 	 * Get all related entries that have been used so far
 	 *
 	 *
+	 * @deprecated Must be refactored.
+	 *
 	 * @param string $tableName The related model
 	 * @param string $tableName The related model
 	 * @param string|null $groupField Field to group by
 	 * @param string|null $groupField Field to group by
 	 * @param string $type Find type
 	 * @param string $type Find type
@@ -287,19 +289,21 @@ class Table extends ShimTable {
 		}
 		}
 		$format = !empty($options['dateFormat']) ? $options['dateFormat'] : 'ymd';
 		$format = !empty($options['dateFormat']) ? $options['dateFormat'] : 'ymd';
 
 
+		/** @var \Cake\I18n\Time $time */
+		$time = $value;
 		if (!is_object($value)) {
 		if (!is_object($value)) {
-			$value = new Time($value);
+			$time = new Time($value);
 		}
 		}
-		$pieces = $value->format(FORMAT_DB_DATETIME);
+		$pieces = $time->format(FORMAT_DB_DATETIME);
 		$dateTime = explode(' ', $pieces, 2);
 		$dateTime = explode(' ', $pieces, 2);
-		$date = $dateTime[0];
-		$time = (!empty($dateTime[1]) ? $dateTime[1] : '');
+		$datePart = $dateTime[0];
+		$timePart = (!empty($dateTime[1]) ? $dateTime[1] : '');
 
 
 		if (!empty($options['allowEmpty']) && (empty($date) && empty($time))) {
 		if (!empty($options['allowEmpty']) && (empty($date) && empty($time))) {
 			return true;
 			return true;
 		}
 		}
 
 
-		if (Validation::date($date, $format) && Validation::time($time)) {
+		if (Validation::date($datePart, $format) && Validation::time($timePart)) {
 			// after/before?
 			// after/before?
 			$seconds = isset($options['min']) ? $options['min'] : 1;
 			$seconds = isset($options['min']) ? $options['min'] : 1;
 			if (!empty($options['after'])) {
 			if (!empty($options['after'])) {
@@ -324,7 +328,7 @@ class Table extends ShimTable {
 			}
 			}
 
 
 			// We need this for those not using immutable objects just yet
 			// We need this for those not using immutable objects just yet
-			$compareValue = clone $value;
+			$compareValue = clone $time;
 
 
 			if (!empty($options['after'])) {
 			if (!empty($options['after'])) {
 				$compare = $compareValue->subSeconds($seconds);
 				$compare = $compareValue->subSeconds($seconds);
@@ -333,7 +337,7 @@ class Table extends ShimTable {
 				}
 				}
 				if (!empty($options['max'])) {
 				if (!empty($options['max'])) {
 					$after = $options['after']->addSeconds($options['max']);
 					$after = $options['after']->addSeconds($options['max']);
-					if ($value->gt($after)) {
+					if ($time->gt($after)) {
 						return false;
 						return false;
 					}
 					}
 				}
 				}
@@ -345,7 +349,7 @@ class Table extends ShimTable {
 				}
 				}
 				if (!empty($options['max'])) {
 				if (!empty($options['max'])) {
 					$after = $options['before']->subSeconds($options['max']);
 					$after = $options['before']->subSeconds($options['max']);
-					if ($value->lt($after)) {
+					if ($time->lt($after)) {
 						return false;
 						return false;
 					}
 					}
 				}
 				}
@@ -375,10 +379,12 @@ class Table extends ShimTable {
 			return false;
 			return false;
 		}
 		}
 		$format = !empty($options['format']) ? $options['format'] : 'ymd';
 		$format = !empty($options['format']) ? $options['format'] : 'ymd';
+
+		$dateTime = $value;
 		if (!is_object($value)) {
 		if (!is_object($value)) {
-			$value = new Time($value);
+			$dateTime = new Time($value);
 		}
 		}
-		$date = $value->format(FORMAT_DB_DATE);
+		$date = $dateTime->format(FORMAT_DB_DATE);
 
 
 		if (!empty($options['allowEmpty']) && empty($date)) {
 		if (!empty($options['allowEmpty']) && empty($date)) {
 			return true;
 			return true;
@@ -387,7 +393,7 @@ class Table extends ShimTable {
 			// after/before?
 			// after/before?
 			$days = !empty($options['min']) ? $options['min'] : 0;
 			$days = !empty($options['min']) ? $options['min'] : 0;
 			if (!empty($options['after']) && isset($context['data'][$options['after']])) {
 			if (!empty($options['after']) && isset($context['data'][$options['after']])) {
-				$compare = $value->subDays($days);
+				$compare = $dateTime->subDays($days);
 				/** @var \Cake\I18n\Time $after */
 				/** @var \Cake\I18n\Time $after */
 				$after = $context['data'][$options['after']];
 				$after = $context['data'][$options['after']];
 				if (!is_object($after)) {
 				if (!is_object($after)) {
@@ -398,7 +404,7 @@ class Table extends ShimTable {
 				}
 				}
 			}
 			}
 			if (!empty($options['before']) && isset($context['data'][$options['before']])) {
 			if (!empty($options['before']) && isset($context['data'][$options['before']])) {
-				$compare = $value->addDays($days);
+				$compare = $dateTime->addDays($days);
 				/** @var \Cake\I18n\Time $before */
 				/** @var \Cake\I18n\Time $before */
 				$before = $context['data'][$options['before']];
 				$before = $context['data'][$options['before']];
 				if (!is_object($before)) {
 				if (!is_object($before)) {

+ 1 - 1
src/Utility/Random.php

@@ -113,7 +113,7 @@ class Random {
 	 * @return string Dob a db (ISO) format datetime string
 	 * @return string Dob a db (ISO) format datetime string
 	 */
 	 */
 	public static function dob($min = 18, $max = 100) {
 	public static function dob($min = 18, $max = 100) {
-		$dobYear = date('Y') - (static::int($min, $max));
+		$dobYear = (int)date('Y') - (static::int($min, $max));
 
 
 		$dobMonth = static::int(1, 12);
 		$dobMonth = static::int(1, 12);
 
 

+ 27 - 77
src/Utility/Time.php

@@ -7,8 +7,6 @@ use Cake\I18n\Date;
 use Cake\I18n\Time as CakeTime;
 use Cake\I18n\Time as CakeTime;
 use DateInterval;
 use DateInterval;
 use DateTime;
 use DateTime;
-use DateTimeZone;
-use Geo\Geocoder\Calculator;
 
 
 /**
 /**
  * Extend CakeTime with a few important improvements:
  * Extend CakeTime with a few important improvements:
@@ -68,29 +66,6 @@ class Time extends CakeTime {
 	}
 	}
 
 
 	/**
 	/**
-	 * Gets the timezone that is closest to the given coordinates
-	 *
-	 * @param float $lat
-	 * @param float $lng
-	 * @return \DateTimeZone Timezone object
-	 * @deprecated Would need Geo plugin to work
-	 */
-	public static function timezoneByCoordinates($lat, $lng) {
-		$current = ['timezone' => null, 'distance' => 0];
-		$identifiers = DateTimeZone::listIdentifiers();
-		foreach ($identifiers as $identifier) {
-			$timezone = new DateTimeZone($identifier);
-			$location = $timezone->getLocation();
-			$point = ['lat' => $location['latitude'], 'lng' => $location['longitude']];
-			$distance = (int)Calculator::calculateDistance(compact('lat', 'lng'), $point);
-			if (!$current['distance'] || $distance < $current['distance']) {
-				$current = ['timezone' => $identifier, 'distance' => $distance];
-			}
-		}
-		return $current['timezone'];
-	}
-
-	/**
 	 * Calculate the difference between two dates
 	 * Calculate the difference between two dates
 	 *
 	 *
 	 * TODO: deprecate in favor of DateTime::diff() etc which will be more precise
 	 * TODO: deprecate in favor of DateTime::diff() etc which will be more precise
@@ -177,33 +152,6 @@ class Time extends CakeTime {
 	}
 	}
 
 
 	/**
 	/**
-	 * Returns age by horoscope info.
-	 *
-	 * @param int $year Year
-	 * @param int $sign Sign
-	 * @return int|array Age
-	 */
-	public static function ageByHoroscope($year, $sign) {
-		App::uses('ZodiacLib', 'Tools.Misc');
-		$Zodiac = new ZodiacLib();
-		$range = $Zodiac->getRange($sign);
-
-		if ($sign == ZodiacLib::SIGN_CAPRICORN) {
-			// undefined
-			return [date('Y') - $year - 1, date('Y') - $year];
-		}
-		if ($range[0][0] > date('m') || ($range[0][0] == date('m') && $range[0][1] > date('d'))) {
-			// not over
-			return date('Y') - $year - 1;
-		}
-		if ($range[1][0] < date('m') || ($range[1][0] == date('m') && $range[1][1] <= date('d'))) {
-			// over
-			return date('Y') - $year;
-		}
-		return [date('Y') - $year - 1, date('Y') - $year];
-	}
-
-	/**
 	 * Rounded age depended on steps (e.g. age 16 with steps = 10 => "11-20")
 	 * Rounded age depended on steps (e.g. age 16 with steps = 10 => "11-20")
 	 * //FIXME
 	 * //FIXME
 	 * //TODO: move to helper?
 	 * //TODO: move to helper?
@@ -216,18 +164,18 @@ class Time extends CakeTime {
 	 */
 	 */
 	public static function ageRange($year, $month = null, $day = null, $steps = 1) {
 	public static function ageRange($year, $month = null, $day = null, $steps = 1) {
 		if ($month == null && $day == null) {
 		if ($month == null && $day == null) {
-			$age = date('Y') - $year - 1;
+			$age = (int)date('Y') - $year - 1;
 		} elseif ($day == null) {
 		} elseif ($day == null) {
-			if ($month >= date('m')) {
-				$age = date('Y') - $year - 1;
+			if ($month >= (int)date('m')) {
+				$age = (int)date('Y') - $year - 1;
 			} else {
 			} else {
-				$age = date('Y') - $year;
+				$age = (int)date('Y') - $year;
 			}
 			}
 		} else {
 		} else {
-			if ($month > date('m') || ($month == date('m') && $day > date('d'))) {
-				$age = date('Y') - $year - 1;
+			if ($month > (int)date('m') || ($month == (int)date('m') && $day > (int)date('d'))) {
+				$age = (int)date('Y') - $year - 1;
 			} else {
 			} else {
-				$age = date('Y') - $year;
+				$age = (int)date('Y') - $year;
 			}
 			}
 		}
 		}
 		if ($age % $steps == 0) {
 		if ($age % $steps == 0) {
@@ -273,8 +221,6 @@ class Time extends CakeTime {
 			list($y, $m, $d) = explode('-', $date[0]);
 			list($y, $m, $d) = explode('-', $date[0]);
 			$t = mktime(0, 0, 0, $m, $d, $y);
 			$t = mktime(0, 0, 0, $m, $d, $y);
 		} else {
 		} else {
-			$d = date('d');
-			$m = date('m');
 			$y = date('Y');
 			$y = date('Y');
 			$t = time();
 			$t = time();
 		}
 		}
@@ -284,12 +230,13 @@ class Time extends CakeTime {
 			$t += WEEK * $relative;	// 1day * 7 * relativeWeeks
 			$t += WEEK * $relative;	// 1day * 7 * relativeWeeks
 		}
 		}
 
 
-		if (($kw = date('W', $t)) === 0) {
-			$kw = 1 + date($t - DAY * date('w', $t), 'W');
+		$kw = (int)date('W', $t);
+		if ($kw === 0) {
+			$kw = 1 + (int)date($t - DAY * (int)date('w', $t), 'W');
 			$y--;
 			$y--;
 		}
 		}
 
 
-		return $kw . '/' . $y;
+		return str_pad($kw, 2, '0', STR_PAD_LEFT) . '/' . $y;
 	}
 	}
 
 
 	/**
 	/**
@@ -301,7 +248,7 @@ class Time extends CakeTime {
 	 */
 	 */
 	public static function cWeekMod($num) {
 	public static function cWeekMod($num) {
 		$base = 6;
 		$base = 6;
-		return $num - $base * floor($num / $base);
+		return (int)($num - $base * floor($num / $base));
 	}
 	}
 
 
 	/**
 	/**
@@ -315,7 +262,7 @@ class Time extends CakeTime {
 	public static function cWeekBeginning($year, $cWeek = 0) {
 	public static function cWeekBeginning($year, $cWeek = 0) {
 		if ($cWeek <= 1 || $cWeek > static::cWeeks($year)) {
 		if ($cWeek <= 1 || $cWeek > static::cWeeks($year)) {
 			$first = mktime(0, 0, 0, 1, 1, $year);
 			$first = mktime(0, 0, 0, 1, 1, $year);
-			$wtag = date('w', $first);
+			$wtag = (int)date('w', $first);
 
 
 			if ($wtag <= 4) {
 			if ($wtag <= 4) {
 				/* Thursday or less: back to Monday */
 				/* Thursday or less: back to Monday */
@@ -357,7 +304,7 @@ class Time extends CakeTime {
 		if ($year === null) {
 		if ($year === null) {
 			$year = date('Y');
 			$year = date('Y');
 		}
 		}
-		return date('W', mktime(23, 59, 59, 12, 28, $year));
+		return (int)date('W', mktime(23, 59, 59, 12, 28, $year));
 	}
 	}
 
 
 	/**
 	/**
@@ -371,13 +318,14 @@ class Time extends CakeTime {
 	 * @return object DateTime with incremented/decremented month/year values.
 	 * @return object DateTime with incremented/decremented month/year values.
 	 */
 	 */
 	public function incrementDate($startDate, $years = 0, $months = 0, $days = 0, $timezone = null) {
 	public function incrementDate($startDate, $years = 0, $months = 0, $days = 0, $timezone = null) {
+		$dateTime = $startDate;
 		if (!is_object($startDate)) {
 		if (!is_object($startDate)) {
-			$startDate = new CakeTime($startDate);
+			$dateTime = new CakeTime($startDate);
 			if ($timezone) {
 			if ($timezone) {
-				$startDate->setTimezone($this->safeCreateDateTimeZone($timezone));
+				$dateTime->setTimezone($this->safeCreateDateTimeZone($timezone));
 			}
 			}
 		}
 		}
-		$startingTimeStamp = $startDate->getTimestamp();
+		$startingTimeStamp = $dateTime->getTimestamp();
 		// Get the month value of the given date:
 		// Get the month value of the given date:
 		$monthString = date('Y-m', $startingTimeStamp);
 		$monthString = date('Y-m', $startingTimeStamp);
 		// Create a date string corresponding to the 1st of the give month,
 		// Create a date string corresponding to the 1st of the give month,
@@ -410,8 +358,8 @@ class Time extends CakeTime {
 		//TODO: other relative time then today should work as well
 		//TODO: other relative time then today should work as well
 		$Date = new CakeTime($relativeTime !== null ? $relativeTime : 'now');
 		$Date = new CakeTime($relativeTime !== null ? $relativeTime : 'now');
 
 
-		$max = mktime(23, 23, 59, $Date->format('m'), $Date->format('d'), $Date->format('Y') - $firstAge);
-		$min = mktime(0, 0, 1, $Date->format('m'), (int)$Date->format('d') + 1, $Date->format('Y') - $secondAge - 1);
+		$max = mktime(23, 23, 59, $Date->format('m'), $Date->format('d'), (int)$Date->format('Y') - $firstAge);
+		$min = mktime(0, 0, 1, $Date->format('m'), (int)$Date->format('d') + 1, (int)$Date->format('Y') - $secondAge - 1);
 
 
 		if ($returnAsString) {
 		if ($returnAsString) {
 			$max = date(FORMAT_DB_DATE, $max);
 			$max = date(FORMAT_DB_DATE, $max);
@@ -935,14 +883,16 @@ class Time extends CakeTime {
 	 * - default, separator
 	 * - default, separator
 	 * - boolean zero: if false: 0 days 5 hours => 5 hours etc.
 	 * - boolean zero: if false: 0 days 5 hours => 5 hours etc.
 	 * - verbose/past/future: string with %s or boolean true/false
 	 * - verbose/past/future: string with %s or boolean true/false
-	 * @return string
+	 * @return string|array
 	 */
 	 */
 	public static function relLengthOfTime($date, $format = null, array $options = []) {
 	public static function relLengthOfTime($date, $format = null, array $options = []) {
+		$dateTime = $date;
 		if ($date !== null && !is_object($date)) {
 		if ($date !== null && !is_object($date)) {
-			$date = static::parse($date);
+			$dateTime = static::parse($date);
 		}
 		}
-		if ($date !== null) {
-			$date = $date->format('U');
+
+		if ($dateTime !== null) {
+			$date = $dateTime->format('U');
 			$sec = time() - $date;
 			$sec = time() - $date;
 			$type = ($sec > 0) ? -1 : (($sec < 0) ? 1 : 0);
 			$type = ($sec > 0) ? -1 : (($sec < 0) ? 1 : 0);
 			$sec = abs($sec);
 			$sec = abs($sec);
@@ -1206,7 +1156,7 @@ class Time extends CakeTime {
 
 
 		$value = $base + $tmp;
 		$value = $base + $tmp;
 		if ($pad === null) {
 		if ($pad === null) {
-			return $value;
+			return (string)$value;
 		}
 		}
 		return number_format($value, $pad, $decPoint, '');
 		return number_format($value, $pad, $decPoint, '');
 	}
 	}

+ 1 - 1
src/Utility/Utility.php

@@ -455,7 +455,7 @@ class Utility {
 	/**
 	/**
 	 * Main deep method
 	 * Main deep method
 	 *
 	 *
-	 * @param callable $function
+	 * @param string|callable $function Callable or function name.
 	 * @param mixed $value
 	 * @param mixed $value
 	 * @return array|string
 	 * @return array|string
 	 */
 	 */

+ 2 - 2
src/View/Helper/FormatHelper.php

@@ -348,8 +348,8 @@ class FormatHelper extends Helper {
 		$translate = isset($options['translate']) ? $options['translate'] : true;
 		$translate = isset($options['translate']) ? $options['translate'] : true;
 
 
 		$type = pathinfo($icon, PATHINFO_FILENAME);
 		$type = pathinfo($icon, PATHINFO_FILENAME);
-		$title = isset($t) ? $t : ucfirst($type);
-		$alt = (isset($a) ? $a : Inflector::slug($title));
+		$title = ucfirst($type);
+		$alt = Inflector::slug($title);
 		if ($translate !== false) {
 		if ($translate !== false) {
 			$title = __($title);
 			$title = __($title);
 			$alt = __($alt);
 			$alt = __($alt);

+ 6 - 5
src/View/Helper/GravatarHelper.php

@@ -4,6 +4,7 @@ namespace Tools\View\Helper;
 
 
 use Cake\Validation\Validation;
 use Cake\Validation\Validation;
 use Cake\View\Helper;
 use Cake\View\Helper;
+use Cake\View\View;
 
 
 /**
 /**
  * CakePHP Gravatar Helper
  * CakePHP Gravatar Helper
@@ -62,10 +63,10 @@ class GravatarHelper extends Helper {
 	public $helpers = ['Html'];
 	public $helpers = ['Html'];
 
 
 	/**
 	/**
-	 * @param \Cake\View\View|null $View
+	 * @param \Cake\View\View $View
 	 * @param array $config
 	 * @param array $config
 	 */
 	 */
-	public function __construct($View = null, $config = []) {
+	public function __construct(View $View, array $config = []) {
 		// Default the secure option to match the current URL.
 		// Default the secure option to match the current URL.
 		$this->_defaultConfig['secure'] = (bool)env('HTTPS');
 		$this->_defaultConfig['secure'] = (bool)env('HTTPS');
 
 
@@ -79,7 +80,7 @@ class GravatarHelper extends Helper {
 	 * @param array $options Array of options, keyed from default settings
 	 * @param array $options Array of options, keyed from default settings
 	 * @return string Gravatar image string
 	 * @return string Gravatar image string
 	 */
 	 */
-	public function image($email, $options = []) {
+	public function image($email, array $options = []) {
 		$imageUrl = $this->url($email, $options);
 		$imageUrl = $this->url($email, $options);
 		unset($options['default'], $options['size'], $options['rating'], $options['ext']);
 		unset($options['default'], $options['size'], $options['rating'], $options['ext']);
 		return $this->Html->image($imageUrl, $options);
 		return $this->Html->image($imageUrl, $options);
@@ -90,10 +91,10 @@ class GravatarHelper extends Helper {
 	 * TODO: rename to avoid E_STRICT errors here
 	 * TODO: rename to avoid E_STRICT errors here
 	 *
 	 *
 	 * @param string $email Email address
 	 * @param string $email Email address
-	 * @param string|array $options Array of options, keyed from default settings
+	 * @param array $options Array of options, keyed from default settings
 	 * @return string Gravatar Image URL
 	 * @return string Gravatar Image URL
 	 */
 	 */
-	public function url($email, $options = []) {
+	public function url($email, array $options = []) {
 		$options = $this->_cleanOptions($options + $this->_config);
 		$options = $this->_cleanOptions($options + $this->_config);
 		$ext = $options['ext'];
 		$ext = $options['ext'];
 		$secure = $options['secure'];
 		$secure = $options['secure'];

+ 1 - 1
src/View/Helper/HtmlHelper.php

@@ -95,7 +95,7 @@ class HtmlHelper extends CoreHtmlHelper {
 			if (!isset($url['?'])) {
 			if (!isset($url['?'])) {
 				$url['?'] = [];
 				$url['?'] = [];
 			}
 			}
-			$url['?'] += $this->request->query;
+			$url['?'] += $this->request->getQuery();
 		}
 		}
 		return parent::link($title, $url, $options);
 		return parent::link($title, $url, $options);
 	}
 	}

+ 6 - 5
src/View/Helper/NumberHelper.php

@@ -4,6 +4,7 @@ namespace Tools\View\Helper;
 
 
 use Cake\Utility\Hash;
 use Cake\Utility\Hash;
 use Cake\View\Helper\NumberHelper as CakeNumberHelper;
 use Cake\View\Helper\NumberHelper as CakeNumberHelper;
+use Cake\View\View;
 
 
 /**
 /**
  * Ovewrite to allow usage of own Number class.
  * Ovewrite to allow usage of own Number class.
@@ -20,13 +21,13 @@ class NumberHelper extends CakeNumberHelper {
 	 * - `engine` Class name to use to replace Number functionality.
 	 * - `engine` Class name to use to replace Number functionality.
 	 *            The class needs to be placed in the `Utility` directory.
 	 *            The class needs to be placed in the `Utility` directory.
 	 *
 	 *
-	 * @param \Cake\View\View|null $View The View this helper is being attached to.
-	 * @param array $options Configuration settings for the helper
+	 * @param \Cake\View\View $View The View this helper is being attached to.
+	 * @param array $config Configuration settings for the helper
 	 * @throws \Cake\Core\Exception\Exception When the engine class could not be found.
 	 * @throws \Cake\Core\Exception\Exception When the engine class could not be found.
 	 */
 	 */
-	public function __construct($View = null, $options = []) {
-		$options = Hash::merge(['engine' => 'Tools.Number'], $options);
-		parent::__construct($View, $options);
+	public function __construct(View $View, array $config = []) {
+		$config = Hash::merge(['engine' => 'Tools.Number'], $config);
+		parent::__construct($View, $config);
 	}
 	}
 
 
 }
 }

+ 2 - 2
src/View/Helper/TimeHelper.php

@@ -4,10 +4,10 @@ namespace Tools\View\Helper;
 
 
 use Cake\Core\App;
 use Cake\Core\App;
 use Cake\Core\Configure;
 use Cake\Core\Configure;
+use Cake\Core\Exception\Exception;
 use Cake\View\Helper\TimeHelper as CakeTimeHelper;
 use Cake\View\Helper\TimeHelper as CakeTimeHelper;
 use Cake\View\View;
 use Cake\View\View;
 use DateTime;
 use DateTime;
-use RuntimeException;
 
 
 /**
 /**
  * Wrapper for TimeHelper and TimeLib
  * Wrapper for TimeHelper and TimeLib
@@ -60,7 +60,7 @@ class TimeHelper extends CakeTimeHelper {
 
 
 		$engineClass = App::className($config['engine'], 'Utility');
 		$engineClass = App::className($config['engine'], 'Utility');
 		if (!$engineClass) {
 		if (!$engineClass) {
-			throw new RuntimeException(sprintf('Class for %s could not be found', $config['engine']));
+			throw new Exception(sprintf('Class for %s could not be found', $config['engine']));
 		}
 		}
 
 
 		$this->_engine = new $engineClass($config);
 		$this->_engine = new $engineClass($config);

+ 1 - 1
src/View/Helper/TimelineHelper.php

@@ -216,7 +216,7 @@ JS;
 		$datePieces = [];
 		$datePieces = [];
 		$datePieces[] = $date->format('Y');
 		$datePieces[] = $date->format('Y');
 		// JavaScript uses 0-indexed months, so we need to subtract 1 month from PHP's output
 		// JavaScript uses 0-indexed months, so we need to subtract 1 month from PHP's output
-		$datePieces[] = (int)($date->format('m') - 1);
+		$datePieces[] = (int)($date->format('m')) - 1;
 		$datePieces[] = (int)$date->format('d');
 		$datePieces[] = (int)$date->format('d');
 		$datePieces[] = (int)$date->format('H');
 		$datePieces[] = (int)$date->format('H');
 		$datePieces[] = (int)$date->format('i');
 		$datePieces[] = (int)$date->format('i');

+ 45 - 48
src/View/Helper/TypographyHelper.php

@@ -268,7 +268,6 @@ class TypographyHelper extends Helper {
 	 * @return string
 	 * @return string
 	 */
 	 */
 	public function formatCharacters($str, $locale = null) {
 	public function formatCharacters($str, $locale = null) {
-		//static $table;
 		if ($locale === null) {
 		if ($locale === null) {
 			$locale = Configure::read('Typography.locale');
 			$locale = Configure::read('Typography.locale');
 		}
 		}
@@ -301,53 +300,51 @@ class TypographyHelper extends Helper {
 			],
 			],
 		];
 		];
 
 
-		if (!isset($table)) {
-			$table = [
-				// nested smart quotes, opening and closing
-				// note that rules for grammar (English) allow only for two levels deep
-				// and that single quotes are _supposed_ to always be on the outside
-				// but we'll accommodate both
-				// Note that in all cases, whitespace is the primary determining factor
-				// on which direction to curl, with non-word characters like punctuation
-				// being a secondary factor only after whitespace is addressed.
-				'/\'"(\s|$)/'					=> '&#8217;&#8221;$1',
-				'/(^|\s|<p>)\'"/'				=> '$1&#8216;&#8220;',
-				'/\'"(\W)/'						=> '&#8217;&#8221;$1',
-				'/(\W)\'"/'						=> '$1&#8216;&#8220;',
-				'/"\'(\s|$)/'					=> '&#8221;&#8217;$1',
-				'/(^|\s|<p>)"\'/'				=> '$1&#8220;&#8216;',
-				'/"\'(\W)/'						=> '&#8221;&#8217;$1',
-				'/(\W)"\'/'						=> '$1&#8220;&#8216;',
-
-				// single quote smart quotes
-				'/\'(\s|$)/'					=> '&#8217;$1',
-				'/(^|\s|<p>)\'/'				=> '$1&#8216;',
-				'/\'(\W)/'						=> '&#8217;$1',
-				'/(\W)\'/'						=> '$1&#8216;',
-
-				// double quote smart quotes
-				'/"(\s|$)/'						=> '&#8221;$1',
-				'/(^|\s|<p>)"/'					=> '$1&#8220;',
-				'/"(\W)/'						=> '&#8221;$1',
-				'/(\W)"/'						=> '$1&#8220;',
-
-				// apostrophes
-				"/(\w)'(\w)/"					=> '$1&rsquo;$2', // we dont use #8217; to avoid collision on replace
-
-				// Em dash and ellipses dots
-				'/\s?\-\-\s?/'					=> '&#8212;',
-				'/(\w)\.{3}/'					=> '$1&#8230;',
-
-				// double space after sentences
-				'/(\W)  /'						=> '$1&nbsp; ',
-
-				// ampersands, if not a character entity
-				'/&(?!#?[a-zA-Z0-9]{2,};)/'		=> '&amp;'
-			];
-			if ($locale && !empty($locales[$locale])) {
-				foreach ($table as $key => $val) {
-					$table[$key] = str_replace($locales['default'], $locales[$locale], $val);
-				}
+		$table = [
+			// nested smart quotes, opening and closing
+			// note that rules for grammar (English) allow only for two levels deep
+			// and that single quotes are _supposed_ to always be on the outside
+			// but we'll accommodate both
+			// Note that in all cases, whitespace is the primary determining factor
+			// on which direction to curl, with non-word characters like punctuation
+			// being a secondary factor only after whitespace is addressed.
+			'/\'"(\s|$)/'					=> '&#8217;&#8221;$1',
+			'/(^|\s|<p>)\'"/'				=> '$1&#8216;&#8220;',
+			'/\'"(\W)/'						=> '&#8217;&#8221;$1',
+			'/(\W)\'"/'						=> '$1&#8216;&#8220;',
+			'/"\'(\s|$)/'					=> '&#8221;&#8217;$1',
+			'/(^|\s|<p>)"\'/'				=> '$1&#8220;&#8216;',
+			'/"\'(\W)/'						=> '&#8221;&#8217;$1',
+			'/(\W)"\'/'						=> '$1&#8220;&#8216;',
+
+			// single quote smart quotes
+			'/\'(\s|$)/'					=> '&#8217;$1',
+			'/(^|\s|<p>)\'/'				=> '$1&#8216;',
+			'/\'(\W)/'						=> '&#8217;$1',
+			'/(\W)\'/'						=> '$1&#8216;',
+
+			// double quote smart quotes
+			'/"(\s|$)/'						=> '&#8221;$1',
+			'/(^|\s|<p>)"/'					=> '$1&#8220;',
+			'/"(\W)/'						=> '&#8221;$1',
+			'/(\W)"/'						=> '$1&#8220;',
+
+			// apostrophes
+			"/(\w)'(\w)/"					=> '$1&rsquo;$2', // we dont use #8217; to avoid collision on replace
+
+			// Em dash and ellipses dots
+			'/\s?\-\-\s?/'					=> '&#8212;',
+			'/(\w)\.{3}/'					=> '$1&#8230;',
+
+			// double space after sentences
+			'/(\W)  /'						=> '$1&nbsp; ',
+
+			// ampersands, if not a character entity
+			'/&(?!#?[a-zA-Z0-9]{2,};)/'		=> '&amp;'
+		];
+		if ($locale && !empty($locales[$locale])) {
+			foreach ($table as $key => $val) {
+				$table[$key] = str_replace($locales['default'], $locales[$locale], $val);
 			}
 			}
 		}
 		}
 
 

+ 1 - 1
src/View/Helper/UrlHelper.php

@@ -99,7 +99,7 @@ class UrlHelper extends CoreUrlHelper {
 		if (!isset($url['?'])) {
 		if (!isset($url['?'])) {
 			$url['?'] = [];
 			$url['?'] = [];
 		}
 		}
-		$url['?'] += $this->request->query;
+		$url['?'] += $this->request->getQuery();
 
 
 		return $url;
 		return $url;
 	}
 	}

+ 8 - 3
tests/phpstan.neon

@@ -4,7 +4,12 @@ parameters:
 	excludes_analyse:
 	excludes_analyse:
 		- %rootDir%/../../../src/TestSuite/*
 		- %rootDir%/../../../src/TestSuite/*
 		- %rootDir%/../../../src/View/Helper/TreeHelper
 		- %rootDir%/../../../src/View/Helper/TreeHelper
+		- %rootDir%/../../../src/Utility/Mime
 	ignoreErrors:
 	ignoreErrors:
-		- '#Call to an undefined method Cake\\Controller\\Component\\FlashComponent::success\(\)#'
-		- '#Tools\\Utility\\Mime::\_\_construct\(\) does not call parent constructor from Cake\\Http\\Response#'
-		- '#Undefined variable: \$i#'
+		- '#Access to an undefined property .+Table::\$belongsTo#'
+		- '#Call to an undefined method .+TimeHelper::.+\(\)#'
+		- '#Access to protected property .+ServerRequest::\$.+#'
+
+services:
+    -
+        class: Cake\PHPStan\AssociationTableMixinClassReflectionExtension