From c8e0b4e94b89f6f813dfd7a04e1124fe6de30bbf Mon Sep 17 00:00:00 2001 From: Tom Davies Date: Fri, 5 May 2023 15:12:05 +0100 Subject: [PATCH] improved: structure, functionality and consistency of twig extension (#12) --- .github/workflows/ci.yml | 4 +- .github/workflows/run-tests.yml | 36 ----------- composer.json | 3 +- docs/.vitepress/config.js | 1 + docs/01-utility-fns.md | 6 +- docs/01.5-string-helpers.md | 35 +++++++++++ docs/03-query-helpers.md | 12 ++-- docs/05-debugging-helpers.md | 2 +- docs/06-operators.md | 11 +++- src/Helpers/DataHelper.php | 15 +++++ src/Helpers/StringHelper.php | 11 ++++ src/TwigExtensions/ToolbeltTwigExtension.php | 66 ++++++++++++++------ 12 files changed, 131 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/run-tests.yml create mode 100644 docs/01.5-string-helpers.md create mode 100644 src/Helpers/DataHelper.php create mode 100644 src/Helpers/StringHelper.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c59e2dd..e1d8d79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,10 @@ jobs: php_version: latest args: --profile --ignore-platform-reqs - name: Run phpstan - uses: php-actions/composer@v6 + uses: php-actions/phpstan@v3 with: - command: run-script phpstan php_version: latest + memory_limit: 1G - name: Run Check ECS uses: php-actions/composer@v6 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index 00da5b3..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Tests - -on: [] - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - os: [ubuntu-latest, windows-latest] - php: [8.0, 8.1, 8.2] - stability: [prefer-lowest, prefer-stable] - - name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo - coverage: none - - - name: Setup problem matchers - run: | - echo "::add-matcher::${{ runner.tool_cache }}/php.json" - echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction - - - name: Execute tests - run: vendor/bin/pest diff --git a/composer.json b/composer.json index 5e4a95d..c75fcb6 100644 --- a/composer.json +++ b/composer.json @@ -26,11 +26,12 @@ "newridetech/php-classnames": "^1.2" }, "require-dev": { + "roave/security-advisories": "dev-latest" + , "craftcms/cms": "^4.1", "craftcms/ecs": "dev-main", "phpstan/phpstan": "^1.7", "nunomaduro/collision": "^5.10", - "phpstan/phpstan": "^1.7", "pestphp/pest": "^1.2", "symplify/easy-coding-standard": "^10.2" }, diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 8fe7d66..9549ae4 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -19,6 +19,7 @@ export default { text: 'Usage', items: [ { text: 'Utility Functions', link: '/01-utility-fns' }, + { text: 'String Helpers', link: '/01.5-string-helpers' }, { text: 'SVG Helpers', link: '/02-svg-helpers' }, { text: 'Query / Collection Helpers', link: '/03-query-helpers' }, { text: 'Eager Loading Helpers', link: '/04-eager-loading-helpers' }, diff --git a/docs/01-utility-fns.md b/docs/01-utility-fns.md index a6e78e8..dd12cc8 100644 --- a/docs/01-utility-fns.md +++ b/docs/01-utility-fns.md @@ -1,8 +1,4 @@ -# Utility functions - -## `parseUrl(string $url)` - -Wraps PHP's native [`parse_url()`](https://www.php.net/manual/en/function.parse-url.php) +# Utility Template Functions ## `classNames` / `cx()` * diff --git a/docs/01.5-string-helpers.md b/docs/01.5-string-helpers.md new file mode 100644 index 0000000..600886a --- /dev/null +++ b/docs/01.5-string-helpers.md @@ -0,0 +1,35 @@ +# String Helpers + +Toolbelt provides a wide number of Twig filters and functions for manipulating strings. + +## Wrapped PHP Functions + +The following functions/filters are wrappers for PHP native functions: + +| Twig name | Available in Twig as | Wrapped function / signature | Docs | +|-------------|----------------------|--------------------------------------------------------------------------------|------------------------------------------------------------| +| `parse_url` | `function` | `parse_url(string $url, int $component = -1): int\|string\|array\|null\|false` | [🔗](https://www.php.net/manual/en/function.parse-url.php) | +| `dirname` | `function` | `dirname(string $path, int $levels = 1): string` | [🔗](https://www.php.net/manual/en/function.dirname.php) | +| `pathinfo` | `function` | `pathinfo(string $path, int $flags = PATHINFO_ALL): array\|string` | [🔗](https://www.php.net/manual/en/function.pathinfo.php) | +| `md5` | `function`, `filter` | `md5(string $string, bool $binary = false)` | [🔗](https://www.php.net/manual/en/function.md5.php) | + +## Wrapped `craft\helpers\StringHelper` Functions + +The following functions/filters are wrappers for functions in Craft's [`craft\helpers\StringHelper`](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html) class: + +| Twig name | Available in Twig as | Wrapped method / signature | Docs | +|---------------------|----------------------|-------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| +| `UUID` | `function` | `UUID(): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#method-uuid) | +| `basename` | `function`, `filter` | `basename(string $path, string $suffix = ''): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#public-methods) | +| `humanize` | `function`, `filter` | `humanize(string $str): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#method-humanize) | +| `slugify` | `function`, `filter` | `slugify(string $str, string $replacement = '-', ?string $language = null): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#method-slugify) | +| `titleize` | `function`, `filter` | `titleize(string $str, ?[] $ignore = null): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#method-titleize) | +| `titleizeForHumans` | `function`, `filter` | `titleizeForHumans(string $str, ?[] $ignore = null): string` | [🔗](https://docs.craftcms.com/api/v4/craft-helpers-stringhelper.html#method-titleizeForHumans) | + +## Extra String Helpers + +The following functions/filters are custom to Toolbelt: + +| Twig name | Available in Twig as | Signature | Docs | +|----------------------|----------------------|------------------------------------------|-------------------------------------------------------------------------------------| +| `stringify()`, `s()` | `function`, `filter` | stringify(string $str): \Stringy\Stringy | Converts a string to a [Stringy instance](https://github.com/danielstjules/Stringy) | diff --git a/docs/03-query-helpers.md b/docs/03-query-helpers.md index f12fa9c..59cd053 100644 --- a/docs/03-query-helpers.md +++ b/docs/03-query-helpers.md @@ -1,13 +1,14 @@ -# Query / Collection helpers +# Query & Collection Helpers `take()` / `takeOne` allow your templates to indifferently consume `ElementQuery`s, `Collections`, plain `array`s, single `Model` instances and even hashes / assoc arrays, and handle them all in the same way when you want to consume them. - ## `take()` `take()` accepts any of the above types and intelligently returns a Collection based on what you provided. -It also accepts an optional second `limit` parameter that will limit the quantity of items in the returned collection: +It also accepts an optional second `limit` parameter that will limit the quantity of items in the returned collection. + +It is available as both a Twig function and a filter. ```twig {% set featuredItems = take(aQueryOrArrayOrCollection, 4) %} @@ -44,8 +45,7 @@ Expanded example: ## `takeOne()` -`takeOne()` returns the first item from a array-like set, `null` if the set was empty, or just the item itself in any other case. - +`takeOne()` returns the first item from a array-like set, `null` if the set was empty, or just the item itself in any other case. It is available as both a Twig function and a filter. That means ugly code like this: @@ -94,7 +94,7 @@ Expanded example: ## `fill()` -`fill()` is useful when you want to be sure to end up with a set number of list items in total, drawing from a series of sources in preferential order: +`fill()` is useful when you want to be sure to end up with a set number of list items in total, drawing from a series of sources in preferential order. It is available as a Twig function. ```twig {% set featuredItems = craft.entries.section('news').isFeatured(true) %} diff --git a/docs/05-debugging-helpers.md b/docs/05-debugging-helpers.md index e03cb52..66ca10f 100644 --- a/docs/05-debugging-helpers.md +++ b/docs/05-debugging-helpers.md @@ -1,6 +1,6 @@ # Debugging helpers -Out of the box, Craft gives us a `{% dd %}` tag in addition to Twig's native `dump` filter. Both are fine, but neither are great, particularly when you want to quick modify existing code to sanity check something. +Out of the box, Craft gives us a `{% dd %}` tag in addition to Twig's native `dump` filter. Both are fine, but neither are great, particularly when you want to quickly modify existing code to sanity check something. ## `dd()` / `d()` all the things diff --git a/docs/06-operators.md b/docs/06-operators.md index d0b686f..b04cda7 100644 --- a/docs/06-operators.md +++ b/docs/06-operators.md @@ -5,4 +5,13 @@ The plugin adds the following operators to Twig: ## Empty Coalesce / `???` -[This is lifted from the plugin of the same name by nystudio107](https://github.com/nystudio107/craft-emptycoalesce) +The `???` operator will return the first thing that is defined, not null, and not empty. + +```twig +{% set foo = '' %} +{% set bar = 'bar' %} + +{{ foo ??? bar }} {# outputs 'bar' #} +``` + +[This operator is lifted from the plugin of the same name by nystudio107](https://github.com/nystudio107/craft-emptycoalesce). diff --git a/src/Helpers/DataHelper.php b/src/Helpers/DataHelper.php new file mode 100644 index 0000000..4fe9392 --- /dev/null +++ b/src/Helpers/DataHelper.php @@ -0,0 +1,15 @@ + dd(...$args)), - // string helpers - new TwigFunction('slugify', [StringHelper::class, 'slugify']), - new TwigFunction('basename', [StringHelper::class, 'basename']), - new TwigFunction('UUID', [StringHelper::class, 'UUID']), - new TwigFunction('md5', fn($value) => md5($value)), - - // template helpers + // Wrapped native PHP functions + new TwigFunction('dirname', fn(string $path, int $levels = 1): string => dirname($path, $levels)), + new TwigFunction('md5', [ToolbeltStringHelper::class, 'md5']), + new TwigFunction('parse_url', fn(string $url) => parse_url($url), ['is_safe' => ['html']]), + new TwigFunction('pathinfo', fn(string $path, int $flags = PATHINFO_ALL): array|string => pathinfo($path, $flags)), + + // Wrapped Craft StringHelper fns + new TwigFunction('UUID', [CraftStringHelper::class, 'UUID']), + new TwigFunction('humanize', [CraftStringHelper::class, 'humanize']), + new TwigFunction('slugify', [CraftStringHelper::class, 'slugify']), + new TwigFunction('basename', [CraftStringHelper::class, 'basename']), + new TwigFunction('titleize', [CraftStringHelper::class, 'titleize']), + new TwigFunction('titleizeForHumans', [CraftStringHelper::class, 'titleizeForHumans']), + + // Extra String helpers + new TwigFunction('stringify', [Stringy::class, 'create']), + new TwigFunction('s', [Stringy::class, 'create']), + + // Template helpers new TwigFunction('classNames', [$this, 'classNames']), new TwigFunction('cx', [$this, 'classNames']), - new TwigFunction('parseUrl', fn(string $url) => parse_url($url), ['is_safe' => ['html']]), - new TwigFunction('pathinfo', fn(string $path) => pathinfo($path)), // SVG helpers new TwigFunction('inlineSvg', [SvgHelper::class, 'renderInline'], ['is_safe' => ['html']]), @@ -55,27 +67,43 @@ public function getFunctions(): array new TwigFunction('svgSlug', [SvgHelper::class, 'svgSlug'], ['is_safe' => ['html']]), // Query / Collection helpers + new TwigFunction('eagerLoad', [ToolbeltElementHelper::class, 'eagerLoad']), + new TwigFunction('fill', [ToolbeltElementHelper::class, 'fill']), new TwigFunction('take', [ToolbeltElementHelper::class, 'take']), new TwigFunction('takeOne', [ToolbeltElementHelper::class, 'takeOne']), - new TwigFunction('fill', [ToolbeltElementHelper::class, 'fill']), - new TwigFunction('eagerLoad', [ToolbeltElementHelper::class, 'eagerLoad']), // Data helpers - new TwigFunction('json_decode', fn($value, $assoc = false, $depth = 512, $options = 0) => json_decode(html_entity_decode($value), $assoc, $depth, $options)), + new TwigFunction('json_decode', [DataHelper::class, 'json_decode']), ]; } public function getFilters(): array { return [ + // Debugging helpers new TwigFilter('d', [$this, 'dump']), new TwigFilter('dump', [$this, 'dump']), + + // Wrapped native PHP functions + new TwigFilter('md5', [ToolbeltStringHelper::class, 'md5']), + + // Craft String helpers + new TwigFilter('basename', [CraftStringHelper::class, 'basename']), + new TwigFilter('humanize', [CraftStringHelper::class, 'humanize']), + new TwigFilter('slugify', [CraftStringHelper::class, 'slugify']), + new TwigFilter('titleize', [CraftStringHelper::class, 'titleize']), + new TwigFilter('titleizeForHumans', [CraftStringHelper::class, 'titleizeForHumans']), + // Query / Collection helpers new TwigFilter('take', [ToolbeltElementHelper::class, 'take']), new TwigFilter('takeOne', [ToolbeltElementHelper::class, 'takeOne']), - // string helpers - new TwigFilter('slugify', [StringHelper::class, 'slugify']), - new TwigFilter('basename', [StringHelper::class, 'basename']), + + // Extra String helpers + new TwigFilter('stringify', [Stringy::class, 'create']), + new TwigFilter('s', [Stringy::class, 'create']), + + // Data helpers + new TwigFilter('json_decode', [DataHelper::class, 'json_decode']), ]; }