From 36d32a9dede9da7f66f94b2999dc99b5d5863691 Mon Sep 17 00:00:00 2001 From: Josef Stich Date: Wed, 3 Apr 2024 08:32:38 +0200 Subject: [PATCH] various fixes for phpstan level9 code analysis #3 --- Controller/Component/FilterComponent.php | 85 +++++++++++++++---- Model/Behavior/FilteredBehavior.php | 77 ++++++++++++----- Test/Case/AllTest.php | 2 +- .../Component/FilterComponentTest.php | 60 ++++++++----- Test/Case/MockObjects/Document.php | 19 +++++ Test/Case/MockObjects/Document2.php | 25 +++++- Test/Case/MockObjects/Document3.php | 25 +++++- Test/Case/MockObjects/DocumentCategory.php | 16 +++- .../MockObjects/DocumentTestsController.php | 16 +++- Test/Case/MockObjects/Item.php | 7 ++ Test/Case/MockObjects/Metadata.php | 7 ++ .../Model/Behaviors/FilteredBehaviorTest.php | 79 +++++++++++------ Test/Fixture/DocumentCategoryFixture.php | 6 ++ Test/Fixture/DocumentFixture.php | 6 ++ Test/Fixture/ItemFixture.php | 6 ++ Test/Fixture/MetadataFixture.php | 9 ++ View/Helper/FilterHelper.php | 32 +++++-- phpstan.neon | 5 ++ phpstan/stubs/CakePHP.stub | 16 ++++ 19 files changed, 401 insertions(+), 97 deletions(-) create mode 100644 phpstan/stubs/CakePHP.stub diff --git a/Controller/Component/FilterComponent.php b/Controller/Component/FilterComponent.php index 32c380f..81413b7 100644 --- a/Controller/Component/FilterComponent.php +++ b/Controller/Component/FilterComponent.php @@ -20,20 +20,46 @@ */ class FilterComponent extends Component { + /** + * @var string[] + */ public $components = array('Session'); + /** + * @var array> + */ public $settings = array(); + + /** + * @var array + */ public $nopersist = array(); + + /** + * @var array> + */ public $formData = array(); + + /** + * @var array + */ protected $_request_settings = array(); + /** + * @param ComponentCollection $collection + * @param array $settings + */ public function __construct(ComponentCollection $collection, $settings = array()) { parent::__construct($collection, $settings); $this->_request_settings = $settings; } - public function initialize(Controller $controller) + /** + * @param Controller $controller + * @return void + */ + public function initialize(Controller $controller): void { if (!isset($controller->filters)) { return; @@ -50,7 +76,10 @@ public function initialize(Controller $controller) foreach ($settings as $model => $filter) { if (!isset($controller->{$model})) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -64,6 +93,7 @@ public function startup(Controller $controller) return; } + /** @var array> $settings */ $settings = $this->settings[$controller->name][$controller->request->action]; if (!in_array('Filter.Filter', $controller->helpers)) { @@ -73,7 +103,10 @@ public function startup(Controller $controller) $sessionKey = sprintf('FilterPlugin.Filters.%s.%s', $controller->name, $controller->request->action); $filterFormId = $controller->request->query('filterFormId'); if ($controller->request->is('get') && !empty($filterFormId)) { - $this->formData = $controller->request->query('data'); + $dataFromQuery = $controller->request->query('data'); + if (is_array($dataFromQuery)) { + $this->formData = $dataFromQuery; + } } elseif (!$controller->request->is('post') || !isset($controller->request->data['Filter']['filterFormId'])) { $persistedData = array(); @@ -81,7 +114,7 @@ public function startup(Controller $controller) $persistedData = $this->Session->read($sessionKey); } - if (empty($persistedData)) { + if (empty($persistedData) || !is_array($persistedData)) { return; } @@ -95,7 +128,10 @@ public function startup(Controller $controller) foreach ($settings as $model => $options) { if (!isset($controller->{$model})) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -109,12 +145,16 @@ public function beforeRender(Controller $controller) return; } + /** @var array>> $models */ $models = $this->settings[$controller->name][$controller->request->action]; $viewFilterParams = array(); foreach ($models as $model => $fields) { if (!isset($controller->$model)) { - trigger_error(__('Filter model not found: %s', $model)); + $errMsg = __('Filter model not found: %s', $model); + if (is_string($errMsg)) { + trigger_error($errMsg); + } continue; } @@ -172,6 +212,7 @@ public function beforeRender(Controller $controller) $options['type'] = 'select'; $selectOptions = array(); + /** @var Model $workingModel */ $workingModel = ClassRegistry::init($fieldModel); if (isset($settings['selectOptions'])) { @@ -180,15 +221,15 @@ public function beforeRender(Controller $controller) if (isset($settings['selector'])) { if (!method_exists($workingModel, $settings['selector'])) { - trigger_error - ( - __( - 'Selector method "%s" not found in model "%s" for field "%s"!', - $settings['selector'], - $fieldModel, - $fieldName - ) + $errMsg = __( + 'Selector method "%s" not found in model "%s" for field "%s"!', + $settings['selector'], + $fieldModel, + $fieldName ); + if (is_string($errMsg)) { + trigger_error($errMsg); + } return; } @@ -246,8 +287,7 @@ public function beforeRender(Controller $controller) $options['value'] = $settings['default']; } - $viewFilterParams[] = array - ( + $viewFilterParams[] = array( 'name' => sprintf('%s.%s', $fieldModel, $fieldName), 'options' => $options ); @@ -273,10 +313,18 @@ public function beforeRender(Controller $controller) $controller->set('viewFilterParams', $viewFilterParams); } - private function __updatePersistence($controller, $settings) + /** + * @param Controller $controller + * @param array $settings + * @return void + */ + private function __updatePersistence(Controller $controller, array $settings): void { if ($this->Session->check('FilterPlugin.NoPersist')) { - $this->nopersist = $this->Session->read('FilterPlugin.NoPersist'); + $nopersistFromSession = $this->Session->read('FilterPlugin.NoPersist'); + if (is_array($nopersistFromSession)) { + $this->nopersist = $nopersistFromSession; + } } if (isset($settings['nopersist'])) { @@ -299,6 +347,7 @@ private function __updatePersistence($controller, $settings) $actions = array(); } + /** @var string[] $actions */ if (empty($actions) && $controller->name != $nopersistController) { if ($this->Session->check(sprintf('FilterPlugin.Filters.%s', $nopersistController))) { $this->Session->delete(sprintf('FilterPlugin.Filters.%s', $nopersistController)); diff --git a/Model/Behavior/FilteredBehavior.php b/Model/Behavior/FilteredBehavior.php index 805d9c0..aa166bc 100644 --- a/Model/Behavior/FilteredBehavior.php +++ b/Model/Behavior/FilteredBehavior.php @@ -18,10 +18,15 @@ class FilteredBehavior extends ModelBehavior /** * Keeps current values after filter form post. * - * @var array + * @var array> */ protected $_filterValues = array(); + /** + * @param Model $Model + * @param array $settings + * @return void + */ public function setup(Model $Model, $settings = array()) { foreach ($settings as $key => $value) { @@ -44,7 +49,12 @@ public function setup(Model $Model, $settings = array()) $this->_filterValues[$Model->alias] = array(); } - public function beforeFind(Model $Model, $query) + /** + * @param Model $Model + * @param array $query + * @return array + */ + public function beforeFind(Model $Model, $query): array { if (isset($query['nofilter']) && $query['nofilter'] === true) { return $query; @@ -64,12 +74,14 @@ public function beforeFind(Model $Model, $query) } $settings = $this->settings[$Model->alias]; + /** @var array> $values */ $values = $this->_filterValues[$Model->alias]; foreach ($settings as $field => $options) { $this->addFieldToFilter($Model, $query, $settings, $values, $field, $options); } + /** @var Model $Model */ if (method_exists($Model, 'afterDataFilter')) { $callbackOptions['values'] = $this->_filterValues[$Model->alias]; $callbackOptions['settings'] = $this->settings[$Model->alias]; @@ -84,7 +96,16 @@ public function beforeFind(Model $Model, $query) return $query; } - protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field, $field_options) + /** + * @param Model $Model + * @param array $query + * @param array $settings + * @param array> $values + * @param string $field + * @param array $field_options + * @return void + */ + protected function addFieldToFilter(Model &$Model, array &$query, array $settings, array $values, string $field, array $field_options): void { $configurationModelName = $Model->alias; $configurationFieldName = $field; @@ -99,7 +120,10 @@ protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field if ($field_options['required'] && !isset($values[$configurationModelName][$configurationFieldName])) { // TODO: implement a bit of a user friendly handling of this scenario.. - trigger_error(__('No value present for required field %s and default value not present', $field)); + $errMsg = __('No value present for required field %s and default value not present', $field); + if (is_string($errMsg)) { + trigger_error($errMsg); + } return; } @@ -175,9 +199,10 @@ protected function addFieldToFilter(&$Model, &$query, $settings, $values, $field * * @param Model $Model * @param Model $relatedModel - * @return array Cake join array + * @param string $linkModelName + * @return array Cake join array */ - protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkModelName) + protected function buildFilterJoin(Model &$Model, Model &$relatedModel, string $linkModelName): array { $conditions = array(); $relationTypes = array('hasMany', 'hasOne', 'belongsTo', 'hasAndBelongsToMany'); @@ -240,7 +265,14 @@ protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkMod $customConditions = array($customConditions); } - $filterConditions = preg_replace(sprintf('#(?alias), $relatedModelAlias, $customConditions); + $filterConditions = preg_replace( + sprintf( + '#(?alias + ), + (is_string($relatedModelAlias) ? $relatedModelAlias : ''), + $customConditions + ); $conditions = array_merge($conditions, $filterConditions); } @@ -280,12 +312,12 @@ protected function buildFilterJoin(Model &$Model, Model &$relatedModel, $linkMod /** * Build query conditions and add them to $query. * - * @param array $query Cake query array. + * @param array> $query Cake query array. * @param string $field Filter field. - * @param array $options Configuration options for this field. + * @param array $options Configuration options for this field. * @param mixed $value Field value. */ - protected function buildFilterConditions(array &$query, $field, $options, $value) + protected function buildFilterConditions(array &$query, string $field, array $options, $value): void { $conditionFieldFormats = array ( 'like' => '%s like', @@ -310,6 +342,7 @@ protected function buildFilterConditions(array &$query, $field, $options, $value switch ($options['type']) { case 'text': + /** @var bool|float|int|string|null $value */ if (strlen(trim(strval($value))) == 0) { break; } @@ -354,15 +387,16 @@ protected function buildFilterConditions(array &$query, $field, $options, $value /** * Makes a string SQL-safe. * - * @param string $string String to sanitize. + * @param mixed $string String to sanitize. * @param string $connection Database connection being used. - * @return string SQL safe string. + * @return mixed SQL safe string. */ - private function __escape($string, $connection = 'default') + private function __escape($string, string $connection = 'default') { - if (is_numeric($string) || $string === null || is_bool($string)) { + if (!is_string($string)) { return $string; } + /** @var DboSource $db */ $db = ConnectionManager::getDataSource($connection); $string = $db->value($string, 'string'); $start = 1; @@ -375,11 +409,11 @@ private function __escape($string, $connection = 'default') /** * Makes an array SQL-safe. * - * @param string|array $data Data to sanitize. - * @param string $options DB connection being used. + * @param mixed $data Data to sanitize. + * @param string $connection DB connection being used. * @return mixed Sanitized data. */ - private function __clean($data, $connection = 'default') + private function __clean($data, string $connection = 'default') { if (empty($data)) { return $data; @@ -397,9 +431,9 @@ private function __clean($data, $connection = 'default') * Sets filter values. * * @param Model $Model Current model. - * @param array $values Filter values. + * @param array $values Filter values. */ - public function setFilterValues(&$Model, $values = array()) + public function setFilterValues(Model &$Model, array $values = array()): void { $values = $this->__clean($values, $Model->useDbConfig); $this->_filterValues[$Model->alias] = array_merge($this->_filterValues[$Model->alias], (array)$values); @@ -408,10 +442,9 @@ public function setFilterValues(&$Model, $values = array()) /** * Gets filter values. * - * @param Model $Model Current model. - * @return array + * @return array */ - public function getFilterValues($Model) + public function getFilterValues(): array { return $this->_filterValues; } diff --git a/Test/Case/AllTest.php b/Test/Case/AllTest.php index a3d0209..909a24d 100644 --- a/Test/Case/AllTest.php +++ b/Test/Case/AllTest.php @@ -14,7 +14,7 @@ class AllFilterTests extends CakeTestSuite { - public static function suite() + public static function suite(): CakeTestSuite { $suite = new CakeTestSuite('All FilterPlugin tests'); diff --git a/Test/Case/Controller/Component/FilterComponentTest.php b/Test/Case/Controller/Component/FilterComponentTest.php index b5c53c6..0513343 100644 --- a/Test/Case/Controller/Component/FilterComponentTest.php +++ b/Test/Case/Controller/Component/FilterComponentTest.php @@ -23,6 +23,9 @@ class FilterComponentTest extends CakeTestCase { + /** + * @var string[] + */ public $fixtures = array ( 'plugin.filter.document_category', @@ -31,7 +34,10 @@ class FilterComponentTest extends CakeTestCase 'plugin.filter.metadata', ); - public $Controller = null; + /** + * @var \DocumentTestsController + */ + public $Controller; public function startTest($method) { @@ -61,17 +67,21 @@ public function startTest($method) public function endTest($method) { $this->Controller->Session->destroy(); - $this->Controller = null; + unset($this->Controller); } /** * Test bailing out when no filters are present. */ - public function testNoFilters() + public function testNoFilters(): void { $this->Controller->Components->trigger('initialize', array($this->Controller)); $this->assertEmpty($this->Controller->Filter->settings); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertFalse($isBehaviorEnabled); + } $this->Controller->Components->trigger('startup', array($this->Controller)); $this->assertFalse(in_array('Filter.Filter', $this->Controller->helpers)); @@ -81,7 +91,7 @@ public function testNoFilters() * Test bailing out when a filter model can't be found * or when the current action has no filters. */ - public function testNoModelPresentOrNoActionFilters() + public function testNoModelPresentOrNoActionFilters(): void { $testSettings = array( 'index' => array( @@ -106,7 +116,11 @@ public function testNoModelPresentOrNoActionFilters() $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertFalse($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertFalse($isBehaviorEnabled); + } $testSettings = array( 'index' => array( @@ -118,13 +132,17 @@ public function testNoModelPresentOrNoActionFilters() $this->Controller->filters = $testSettings; $this->Controller->Components->trigger('initialize', array($this->Controller)); - $this->assertTrue($this->Controller->Document->Behaviors->enabled('Filtered')); + $isBehaviorEnabled = $this->Controller->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorEnabled); + if (is_bool($isBehaviorEnabled)) { + $this->assertTrue($isBehaviorEnabled); + } } /** * Test basic filter settings. */ - public function testBasicFilters() + public function testBasicFilters(): void { $testSettings = array( 'index' => array( @@ -146,7 +164,7 @@ public function testBasicFilters() /** * Test running a component with no filter data. */ - public function testEmptyStartup() + public function testEmptyStartup(): void { $testSettings = array( 'index' => array( @@ -165,7 +183,7 @@ public function testEmptyStartup() /** * Test loading filter data from session (both full and empty). */ - public function testSessionStartupData() + public function testSessionStartupData(): void { $testSettings = array( 'index' => array( @@ -210,7 +228,7 @@ public function testSessionStartupData() /** * Test loading filter data from a post request. */ - public function testPostStartupData() + public function testPostStartupData(): void { $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -244,7 +262,7 @@ public function testPostStartupData() /** * Test exiting beforeRender when in an action with no settings. */ - public function testBeforeRenderAbort() + public function testBeforeRenderAbort(): void { $testSettings = array( 'veryMuchNotIndex' => array( @@ -266,7 +284,7 @@ public function testBeforeRenderAbort() * Test triggering an error when the plugin runs into a setting * for filtering a model which cannot be found. */ - public function testNoModelFound() + public function testNoModelFound(): void { $testSettings = array( 'index' => array( @@ -291,7 +309,7 @@ public function testNoModelFound() * Test the view variable generation for very basic filtering. * Also tests model name detection and custom label. */ - public function testBasicViewInfo() + public function testBasicViewInfo(): void { $testSettings = array( 'index' => array( @@ -333,7 +351,7 @@ public function testBasicViewInfo() * Test passing additional inputOptions to the form * helper, used to customize search form. */ - public function testAdditionalInputOptions() + public function testAdditionalInputOptions(): void { $testSettings = array( 'index' => array( @@ -386,7 +404,7 @@ public function testAdditionalInputOptions() * Test data fetching for select input when custom selector * and custom options are provided. */ - public function testCustomSelector() + public function testCustomSelector(): void { $testSettings = array( 'index' => array( @@ -427,7 +445,7 @@ public function testCustomSelector() /** * Test checkbox input filtering. */ - public function testCheckboxOptions() + public function testCheckboxOptions(): void { $testSettings = array( 'index' => array( @@ -463,7 +481,7 @@ public function testCheckboxOptions() /** * Test basic filter settings. */ - public function testSelectMultiple() + public function testSelectMultiple(): void { $testSettings = array( 'index' => array( @@ -488,7 +506,7 @@ public function testSelectMultiple() /** * Test select input for the model filtered. */ - public function testSelectInputFromSameModel() + public function testSelectInputFromSameModel(): void { $testSettings = array( 'index' => array( @@ -530,7 +548,7 @@ public function testSelectInputFromSameModel() * Test disabling persistence for single action * and for the entire controller. */ - public function testPersistence() + public function testPersistence(): void { $testSettings = array( 'index' => array( @@ -564,7 +582,7 @@ public function testPersistence() * Test whether filtering by belongsTo model text field * works correctly. */ - public function testBelongsToFilteringByText() + public function testBelongsToFilteringByText(): void { $testSettings = array( 'index' => array( diff --git a/Test/Case/MockObjects/Document.php b/Test/Case/MockObjects/Document.php index c8856ed..3627bf2 100644 --- a/Test/Case/MockObjects/Document.php +++ b/Test/Case/MockObjects/Document.php @@ -1,9 +1,28 @@ > + */ public $hasOne = array('Metadata'); } diff --git a/Test/Case/MockObjects/Document2.php b/Test/Case/MockObjects/Document2.php index f2d0c92..9c90952 100644 --- a/Test/Case/MockObjects/Document2.php +++ b/Test/Case/MockObjects/Document2.php @@ -2,14 +2,37 @@ class Document2 extends CakeTestModel { + /** + * @var string + */ public $name = 'Document'; + + /** + * @var string + */ public $alias = 'Document'; + + /** + * @var string[] + */ public $belongsTo = array('DocumentCategory'); + + /** + * @var string[] + */ public $hasMany = array('Item'); + /** + * @var bool + */ public $returnValue = false; - public function beforeDataFilter($query, $options) + /** + * @param array $query + * @param array $options + * @return bool + */ + public function beforeDataFilter(array $query, array $options): bool { return $this->returnValue; } diff --git a/Test/Case/MockObjects/Document3.php b/Test/Case/MockObjects/Document3.php index 220dd63..9921e9d 100644 --- a/Test/Case/MockObjects/Document3.php +++ b/Test/Case/MockObjects/Document3.php @@ -2,14 +2,37 @@ class Document3 extends CakeTestModel { + /** + * @var string + */ public $name = 'Document'; + + /** + * @var string + */ public $alias = 'Document'; + + /** + * @var string[] + */ public $belongsTo = array('DocumentCategory'); + + /** + * @var string[] + */ public $hasMany = array('Item'); + /** + * @var null|string + */ public $itemToUnset = null; - public function afterDataFilter($query, $options) + /** + * @param array> $query + * @param array $options + * @return array + */ + public function afterDataFilter(array $query, array $options): array { if (!is_string($this->itemToUnset)) { return $query; diff --git a/Test/Case/MockObjects/DocumentCategory.php b/Test/Case/MockObjects/DocumentCategory.php index 14d5e2c..403c03c 100644 --- a/Test/Case/MockObjects/DocumentCategory.php +++ b/Test/Case/MockObjects/DocumentCategory.php @@ -1,11 +1,25 @@ > $options + * @return array|int|null + */ + public function customSelector(array $options = array()) { $options['conditions']['DocumentCategory.title LIKE'] = '%T%'; $options['nofilter'] = true; diff --git a/Test/Case/MockObjects/DocumentTestsController.php b/Test/Case/MockObjects/DocumentTestsController.php index 620e8f6..ee78ce1 100644 --- a/Test/Case/MockObjects/DocumentTestsController.php +++ b/Test/Case/MockObjects/DocumentTestsController.php @@ -2,16 +2,30 @@ App::uses('Controller', 'Controller'); +/** + * @property array $filters + * @property FilterComponent $Filter + * @property Document $Document + */ class DocumentTestsController extends Controller { + /** + * @var string + */ public $name = 'DocumentTests'; - public function index() + public function index(): void { } // must override this or the tests never complete.. // @TODO: mock partial? + /** + * @param array|string $url + * @param array $status + * @param bool $exit + * @return CakeResponse|null + */ public function redirect($url, $status = null, $exit = true) { } diff --git a/Test/Case/MockObjects/Item.php b/Test/Case/MockObjects/Item.php index ffb28af..1f50608 100644 --- a/Test/Case/MockObjects/Item.php +++ b/Test/Case/MockObjects/Item.php @@ -2,6 +2,13 @@ class Item extends CakeTestModel { + /** + * @var string + */ public $name = 'Item'; + + /** + * @var string[] + */ public $belongsTo = array('Document'); } diff --git a/Test/Case/MockObjects/Metadata.php b/Test/Case/MockObjects/Metadata.php index c9bf609..d7262ce 100644 --- a/Test/Case/MockObjects/Metadata.php +++ b/Test/Case/MockObjects/Metadata.php @@ -2,6 +2,13 @@ class Metadata extends CakeTestModel { + /** + * @var string + */ public $name = 'Metadata'; + + /** + * @var string[] + */ public $hasOne = array('Document'); } diff --git a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php index 6409821..5a0d5d3 100644 --- a/Test/Case/Model/Behaviors/FilteredBehaviorTest.php +++ b/Test/Case/Model/Behaviors/FilteredBehaviorTest.php @@ -22,6 +22,9 @@ class FilteredBehaviorTest extends CakeTestCase { + /** + * @var string[] + */ public $fixtures = array ( 'plugin.filter.document_category', @@ -30,24 +33,38 @@ class FilteredBehaviorTest extends CakeTestCase 'plugin.filter.metadata', ); - public $Document = null; + /** + * @var Document + */ + public $Document; + /** + * @param $model + * @return void + * @throws Exception + */ public function startTest($model) { - $this->Document = ClassRegistry::init('Document'); + $Document = ClassRegistry::init('Document'); + + if ($Document instanceof Document) { + $this->Document = $Document; + } else { + throw new Exception('Can not create Document model'); + } } public function endTest($model) { - $this->Document = null; + unset($this->Document); } /** * Detach and re-attach the behavior to reset the options. * - * @param array $options Behavior options. + * @param array $options Behavior options. */ - protected function _reattachBehavior($options = array()) + protected function _reattachBehavior(array $options = array()): void { $this->Document->Behaviors->detach('Filtered'); $this->Document->Behaviors->attach('Filter.Filtered', $options); @@ -56,16 +73,20 @@ protected function _reattachBehavior($options = array()) /** * Test attaching without options. */ - public function testBlankAttaching() + public function testBlankAttaching(): void { $this->Document->Behaviors->attach('Filter.Filtered'); - $this->assertTrue($this->Document->Behaviors->enabled('Filtered')); + $isBehaviorAttached = $this->Document->Behaviors->enabled('Filtered'); + $this->assertInternalType('bool', $isBehaviorAttached); + if (is_bool($isBehaviorAttached)) { + $this->assertTrue($isBehaviorAttached); + } } /** * Test attaching with options. */ - public function testInitSettings() + public function testInitSettings(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like'), @@ -85,7 +106,7 @@ public function testInitSettings() /** * Test init settings when only a single field is given, with no extra options. */ - public function testInitSettingsSingle() + public function testInitSettingsSingle(): void { $testOptions = array('Document.title'); $this->_reattachBehavior($testOptions); @@ -99,7 +120,7 @@ public function testInitSettingsSingle() /** * Test setting the filter values for future queries. */ - public function testSetFilterValues() + public function testSetFilterValues(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -122,7 +143,7 @@ public function testSetFilterValues() /** * Test detecting an error in options - when a field is 'required' but no value is given for it. */ - public function testLoadingRequiredFieldValueMissing() + public function testLoadingRequiredFieldValueMissing(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -144,7 +165,7 @@ public function testLoadingRequiredFieldValueMissing() /** * Test filtering with conditions from current model and belongsTo model. */ - public function testFilteringBelongsTo() + public function testFilteringBelongsTo(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -167,7 +188,7 @@ public function testFilteringBelongsTo() $this->assertEquals($expected, $result); } - public function testFilteringBelongsToTextField() + public function testFilteringBelongsToTextField(): void { $testOptions = array( 'DocumentCategory.title' => array('type' => 'text') @@ -191,7 +212,7 @@ public function testFilteringBelongsToTextField() * Test filtering with conditions from current model and belongsTo model, * same as testFilteringBelongsTo() except for a change in filterField format. */ - public function testFilteringBelongsToFilterFieldTest() + public function testFilteringBelongsToFilterFieldTest(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -217,7 +238,7 @@ public function testFilteringBelongsToFilterFieldTest() /** * Test various conditions for the type 'text' in filtering (less than, equal, like, etc..) */ - public function testFilteringBelongsToDifferentConditions() + public function testFilteringBelongsToDifferentConditions(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => '='), @@ -264,7 +285,7 @@ public function testFilteringBelongsToDifferentConditions() * Test filtering with conditions on current model, the belongsTo model * and hasMany model (behavior adds an INNER JOIN in query). */ - public function testFilteringBelongsToAndHasMany() + public function testFilteringBelongsToAndHasMany(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -329,7 +350,7 @@ public function testFilteringBelongsToAndHasMany() * Test filtering with join which has some custom * condition in the relation (both string and array). */ - public function testCustomJoinConditions() + public function testCustomJoinConditions(): void { $testOptions = array( 'Metadata.weight' => array('type' => 'text', 'condition' => '>'), @@ -367,7 +388,7 @@ public function testCustomJoinConditions() /** * Test for any possible conflicts with Containable behavior. */ - public function testFilteringBelongsToAndHasManyWithContainable() + public function testFilteringBelongsToAndHasManyWithContainable(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -427,7 +448,7 @@ public function testFilteringBelongsToAndHasManyWithContainable() /** * Test filtering by text input with hasOne relation. */ - public function testHasOneAndHasManyWithTextSearch() + public function testHasOneAndHasManyWithTextSearch(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -458,7 +479,7 @@ public function testHasOneAndHasManyWithTextSearch() /** * Test filtering with Containable and hasOne Model.field. */ - public function testHasOneWithContainable() + public function testHasOneWithContainable(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -506,7 +527,7 @@ public function testHasOneWithContainable() * Test filtering when a join is already present in the query, * this should prevent duplicate joins and query errors. */ - public function testJoinAlreadyPresent() + public function testJoinAlreadyPresent(): void { $testOptions = array( 'title' => array('type' => 'text', 'condition' => 'like', 'required' => true), @@ -551,7 +572,7 @@ public function testJoinAlreadyPresent() /** * Test the 'nofilter' query param. */ - public function testNofilterFindParam() + public function testNofilterFindParam(): void { $testOptions = array( 'Document.title' => array('type' => 'text', 'condition' => 'like'), @@ -580,7 +601,7 @@ public function testNofilterFindParam() /** * Test bailing out if no settings exist for the current model. */ - public function testExitWhenNoSettings() + public function testExitWhenNoSettings(): void { $this->Document->DocumentCategory->Behaviors->attach('Filter.Filtered'); @@ -608,7 +629,7 @@ public function testExitWhenNoSettings() /** * Test beforeDataFilter() callback, used to cancel filtering if necessary. */ - public function testBeforeDataFilterCallbackCancel() + public function testBeforeDataFilterCallbackCancel(): void { $this->Document = ClassRegistry::init('Document2'); @@ -641,10 +662,16 @@ public function testBeforeDataFilterCallbackCancel() /** * Test afterDataFilter() callback, used to modify the conditions after * filter conditions have been applied. + * @throws Exception */ - public function testAfterDataFilterCallbackQueryChange() + public function testAfterDataFilterCallbackQueryChange(): void { - $this->Document = ClassRegistry::init('Document3'); + $document = ClassRegistry::init('Document3'); + if ($document instanceof Document) { + $this->Document = $document; + } else { + throw new Exception('Can not create Document model'); + } $this->Document->itemToUnset = 'FilterDocumentCategory.id'; $testOptions = array( diff --git a/Test/Fixture/DocumentCategoryFixture.php b/Test/Fixture/DocumentCategoryFixture.php index 32fbe31..83d46e4 100644 --- a/Test/Fixture/DocumentCategoryFixture.php +++ b/Test/Fixture/DocumentCategoryFixture.php @@ -15,12 +15,18 @@ class DocumentCategoryFixture extends CakeTestFixture { public $name = 'DocumentCategory'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 100, 'null' => false), 'description' => array('type' => 'string', 'length' => 255) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'title' => 'Testing Doc', 'description' => 'It\'s a bleeding test doc!'), array('id' => 2, 'title' => 'Imaginary Spec', 'description' => 'This doc does not exist'), diff --git a/Test/Fixture/DocumentFixture.php b/Test/Fixture/DocumentFixture.php index 8d9a685..90b4adb 100644 --- a/Test/Fixture/DocumentFixture.php +++ b/Test/Fixture/DocumentFixture.php @@ -15,6 +15,9 @@ class DocumentFixture extends CakeTestFixture { public $name = 'Document'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => '255', 'null' => false), @@ -25,6 +28,9 @@ class DocumentFixture extends CakeTestFixture 'updated' => array('type' => 'datetime', 'null' => true) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'title' => 'Testing Doc', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-06-28 10:39:23', 'updated' => '2010-06-29 11:22:48'), array('id' => 2, 'title' => 'Imaginary Spec', 'document_category_id' => 1, 'owner_id' => 1, 'is_private' => 0, 'created' => '2010-03-28 12:19:13', 'updated' => '2010-04-29 11:23:44'), diff --git a/Test/Fixture/ItemFixture.php b/Test/Fixture/ItemFixture.php index ad510f5..9a95fb0 100644 --- a/Test/Fixture/ItemFixture.php +++ b/Test/Fixture/ItemFixture.php @@ -15,12 +15,18 @@ class ItemFixture extends CakeTestFixture { public $name = 'Item'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'document_id' => array('type' => 'integer', 'null' => false), 'code' => array('type' => 'string', 'length' => '20', 'null' => false) ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'document_id' => 1, 'code' => 'The item #01'), array('id' => 2, 'document_id' => 1, 'code' => 'The item #02'), diff --git a/Test/Fixture/MetadataFixture.php b/Test/Fixture/MetadataFixture.php index c942775..e538ccc 100644 --- a/Test/Fixture/MetadataFixture.php +++ b/Test/Fixture/MetadataFixture.php @@ -13,8 +13,14 @@ class MetadataFixture extends CakeTestFixture { + /** + * @var string + */ public $name = 'Metadata'; + /** + * @var array> + */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), 'document_id' => array('type' => 'integer', 'null' => false), @@ -23,6 +29,9 @@ class MetadataFixture extends CakeTestFixture 'permissions' => array('type' => 'string', 'length' => 10, 'null' => false), ); + /** + * @var array> + */ public $records = array( array('id' => 1, 'document_id' => 1, 'weight' => 5, 'size' => 256, 'permissions' => 'rw-r--r--'), array('id' => 2, 'document_id' => 2, 'weight' => 0, 'size' => 45, 'permissions' => 'rw-------'), diff --git a/View/Helper/FilterHelper.php b/View/Helper/FilterHelper.php index b3e276c..b68edd7 100644 --- a/View/Helper/FilterHelper.php +++ b/View/Helper/FilterHelper.php @@ -14,14 +14,27 @@ class FilterHelper extends AppHelper { + /** + * @var View + */ protected $_view = null; - public function __construct(View $view, $settings = array()) + /** + * @param View $view + * @param array $settings + */ + public function __construct(View $view, array $settings = array()) { + parent::__construct($view, $settings); $this->_view = $view; } - public function filterForm($modelName, $options) + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function filterForm(string $modelName, array $options): string { $view =& $this->_view; @@ -50,7 +63,12 @@ public function filterForm($modelName, $options) return $output; } - public function beginForm($modelName, $options) + /** + * @param string $modelName + * @param array $options + * @return string + */ + public function beginForm(string $modelName, array $options): string { $view =& $this->_view; $output = $view->element( @@ -66,7 +84,11 @@ public function beginForm($modelName, $options) return $output; } - public function inputFields($fields = array()) + /** + * @param array $fields + * @return string + */ + public function inputFields(array $fields = array()): string { $view =& $this->_view; $output = $view->element( @@ -81,7 +103,7 @@ public function inputFields($fields = array()) return $output; } - public function endForm() + public function endForm(): string { $view = $this->_view; $output = $view->element( diff --git a/phpstan.neon b/phpstan.neon index 04e3e63..b870430 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,3 +2,8 @@ parameters: level: 9 scanDirectories: - vendor + stubFiles: + - phpstan/stubs/CakePHP.stub + ignoreErrors: + # False positive as __() function can accept more than 2 parameters. + - '/Function __ invoked with [0-9_]+ parameters, 1-2 required./' diff --git a/phpstan/stubs/CakePHP.stub b/phpstan/stubs/CakePHP.stub new file mode 100644 index 0000000..35cff21 --- /dev/null +++ b/phpstan/stubs/CakePHP.stub @@ -0,0 +1,16 @@ +