diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5301470..bb7c780 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f6a7ea0..af94e92 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ - + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 53d6cf0..7213f69 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,17 +1,9 @@ - - - - $input['type'] - - + - - $data['name'] - $data['templateVars'] + + + - - $data['type'] - diff --git a/src/Dashboard/Module/LinkItem.php b/src/Dashboard/Module/LinkItem.php index 767846a..2ad4b96 100644 --- a/src/Dashboard/Module/LinkItem.php +++ b/src/Dashboard/Module/LinkItem.php @@ -56,7 +56,7 @@ public function __construct(string|array $title, string|array|null $url, array $ */ protected function _setTitle(string|array|null $title): string|array { - if (empty($title)) { + if ($title === null || $title === '') { throw new InvalidArgumentException('Missing title for LinkItem action'); } @@ -72,7 +72,7 @@ protected function _setTitle(string|array|null $title): string|array */ protected function _setUrl(string|array|null $url): string|array { - if ($url === null || empty($url)) { + if ($url === null || $url === '') { throw new InvalidArgumentException('Invalid url specified for LinkItem'); } @@ -87,10 +87,6 @@ protected function _setUrl(string|array|null $url): string|array */ protected function _setOptions(array $options): string|array { - if (empty($options)) { - $options = []; - } - $url = $this->get('url'); if (!is_array($url)) { $isHttp = substr($url, 0, 7) === 'http://'; diff --git a/src/Listener/ViewListener.php b/src/Listener/ViewListener.php index 46c4881..4a8731b 100644 --- a/src/Listener/ViewListener.php +++ b/src/Listener/ViewListener.php @@ -171,7 +171,7 @@ protected function _getPageTitle(): string } $primaryKeyValue = $this->_primaryKeyValue(); - if (empty($primaryKeyValue)) { + if ($primaryKeyValue === null) { return sprintf('%s %s', $actionName, $controllerName); } @@ -612,7 +612,7 @@ protected function _deriveFieldFromContext(string $field): mixed $request = $this->_request(); $value = $entity->get($field); - if ($value) { + if ($value !== null) { return $value; } diff --git a/src/Listener/ViewSearchListener.php b/src/Listener/ViewSearchListener.php index e82d82e..5f53be8 100644 --- a/src/Listener/ViewSearchListener.php +++ b/src/Listener/ViewSearchListener.php @@ -151,6 +151,7 @@ public function fields(): array $input['options'][$input['value']] = $input['value']; } + /** @psalm-suppress PossiblyInvalidOperand */ $input += [ 'data-input-type' => 'text', 'data-tags' => 'true', @@ -188,6 +189,12 @@ public function fields(): array return $fields; } + /** + * Get placeholder text for a field. + * + * @param string $field Field name. + * @return string + */ protected function getPlaceholder(string $field): string { if (str_contains($field, '.')) { diff --git a/src/View/Cell/TablesListCell.php b/src/View/Cell/TablesListCell.php index 3bd1d6d..9f5b82a 100644 --- a/src/View/Cell/TablesListCell.php +++ b/src/View/Cell/TablesListCell.php @@ -18,13 +18,14 @@ class TablesListCell extends Cell */ public function display(?array $tables = null, ?array $blacklist = null): void { - if (empty($tables)) { + if ($tables === null) { /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get('default'); $schema = $connection->getSchemaCollection(); $tables = $schema->listTables(); ksort($tables); + /** @psalm-suppress RiskyTruthyFalsyComparison */ if (!empty($blacklist)) { $tables = array_diff($tables, $blacklist); } diff --git a/src/View/Helper/CrudViewHelper.php b/src/View/Helper/CrudViewHelper.php index 5a17535..0dbcbd0 100644 --- a/src/View/Helper/CrudViewHelper.php +++ b/src/View/Helper/CrudViewHelper.php @@ -3,12 +3,15 @@ namespace CrudView\View\Helper; +use BackedEnum; +use Cake\Database\Type\EnumLabelInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\SchemaInterface; use Cake\Utility\Inflector; use Cake\Utility\Text; use Cake\View\Helper; use Cake\View\Helper\FormHelper; +use UnitEnum; use function Cake\Core\h; use function Cake\I18n\__d; @@ -156,6 +159,10 @@ public function introspect(string $field, mixed $value, array $options = []): ar return $this->formatTime($field, $value, $options); } + if ($type !== null && str_starts_with($type, 'enum-')) { + return $this->formatEnum($field, $value, $options); + } + $value = $this->formatString($field, $value); if ($field === $this->getViewVar('displayField')) { @@ -230,6 +237,23 @@ public function formatTime(string $field, mixed $value, array $options): string return $value; } + /** + * Format an enum for display + * + * @param string $field Name of field. + * @param \UnitEnum|\BackedEnum|string|int $value Value of field. + * @return string + */ + public function formatEnum(string $field, UnitEnum|BackedEnum|string|int $value, array $options): string + { + if (is_scalar($value)) { + return (string)$value; + } + + return $value instanceof EnumLabelInterface ? + $value->label() : Inflector::humanize(Inflector::underscore($value->name)); + } + /** * Format a string for display * diff --git a/tests/Fixture/BlogsFixture.php b/tests/Fixture/BlogsFixture.php index fa6da5e..8ac126f 100644 --- a/tests/Fixture/BlogsFixture.php +++ b/tests/Fixture/BlogsFixture.php @@ -7,16 +7,7 @@ class BlogsFixture extends TestFixture { - public $fields = [ - 'id' => ['type' => 'integer'], - 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], - 'name' => ['type' => 'string', 'length' => 255, 'null' => false], - 'body' => ['type' => 'text', 'null' => false], - 'user_id' => ['type' => 'integer'], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - public $records = [ + public array $records = [ ['name' => '1st post', 'body' => '1st post body', 'user_id' => 1], ['name' => '2nd post', 'body' => '2nd post body', 'user_id' => 1], ['name' => '3rd post', 'body' => '3rd post body', 'user_id' => 1], diff --git a/tests/TestCase/Listener/ViewSearchListenerTest.php b/tests/TestCase/Listener/ViewSearchListenerTest.php index 8b3c905..e17d2ac 100644 --- a/tests/TestCase/Listener/ViewSearchListenerTest.php +++ b/tests/TestCase/Listener/ViewSearchListenerTest.php @@ -15,17 +15,17 @@ */ class ViewSearchListenerTest extends TestCase { - protected $fixtures = ['plugin.CrudView.Blogs']; + protected array $fixtures = ['plugin.CrudView.Blogs']; /** * @var \Cake\Controller\Controller; */ - protected $controller; + protected Controller $controller; /** * @var \CrudView\Listener\ViewSearchListener */ - protected $listener; + protected ViewSearchListener $listener; public function setUp(): void { @@ -39,7 +39,8 @@ public function setUp(): void 'params' => ['controller' => 'Blogs', 'action' => 'index', 'plugin' => null, '_ext' => null], ]); - $this->controller = new Controller($request, null, 'Blogs'); + $this->controller = new Controller($request, 'Blogs'); + $this->controller->loadComponent('Crud.Crud'); $this->listener = new ViewSearchListener($this->controller); @@ -67,6 +68,7 @@ public function testFields() 'data-tags' => 'true', 'data-allow-clear' => 'true', 'data-placeholder' => '', + 'empty' => 'Name', ], 'is_active' => [ 'required' => false, diff --git a/tests/schema.php b/tests/schema.php new file mode 100644 index 0000000..9715345 --- /dev/null +++ b/tests/schema.php @@ -0,0 +1,16 @@ + 'blogs', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], + 'name' => ['type' => 'string', 'length' => 255, 'null' => false], + 'body' => ['type' => 'text', 'null' => false], + 'user_id' => ['type' => 'integer'], + ], + 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ], +];