{"id":19441017,"url":"https://github.com/pixney/pyrocms-cheatsheet","last_synced_at":"2025-04-24T23:32:21.631Z","repository":{"id":129070323,"uuid":"85045725","full_name":"pixney/pyrocms-cheatsheet","owner":"pixney","description":"Pyro CMS - Cheat Sheet","archived":false,"fork":false,"pushed_at":"2020-02-05T08:15:08.000Z","size":71,"stargazers_count":20,"open_issues_count":1,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-03T13:11:09.347Z","etag":null,"topics":["addon","cheatsheet","grid","mix","php","pyrocms","seed","stream"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pixney.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-15T08:08:56.000Z","updated_at":"2024-07-14T10:01:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"e6cc46a8-bc5b-413e-b28a-4fceaf846a6e","html_url":"https://github.com/pixney/pyrocms-cheatsheet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixney%2Fpyrocms-cheatsheet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixney%2Fpyrocms-cheatsheet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixney%2Fpyrocms-cheatsheet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixney%2Fpyrocms-cheatsheet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pixney","download_url":"https://codeload.github.com/pixney/pyrocms-cheatsheet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250727803,"owners_count":21477373,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["addon","cheatsheet","grid","mix","php","pyrocms","seed","stream"],"created_at":"2024-11-10T15:34:01.370Z","updated_at":"2025-04-24T23:32:21.612Z","avatar_url":"https://github.com/pixney.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pyro - A Laravel Development Platform\n\n[Installing Pyrocms 3.4](installing_pyrocms_3.md)  \n[Using mix with pyrocms](using_mix.md)  \n[Organising fields in tabs for pages and posts](https://github.com/designbywilliam/pyrocms-cheatsheet/wiki/Organising-fields-in-tabs-for-pages-and-posts)  \n[Customising Repeater Views](https://github.com/designbywilliam/pyrocms-cheatsheet/wiki/How-to-customise-repeater-views)  \n[Creating a block](Creating_a_block.md)  \n[Visual Studio Code Field Type Snippets](https://github.com/Pixney-William/fieldtype-snippets/blob/master/stream-fieldtypes.code-snippets)\n\n## Upgrading\nAlways read Ryans blog posts. But in general just use the composer.json file of the version you want to upgrade to. Then:\n\n```\ncomposer update\nphp artisan migrate --path=vendor/anomaly/streams-platform/migrations/application\nphp artisan migrate --all-addons\nphp artisan assets:clear\nphp artisan cache:clear\nphp artisan view:clear\nphp artisan twig:clear\n```\n\n## Regular composer stuff\n`composer clearcache` is a good one to have handy when updating.\n\n## Migrations\n[Create a grid and attach it to default_posts stream](migration_grid_to_posts.md)  \n[Create a grid with image field type](create_grid.php)  \n\n## Seeders\n\n[Seed page and blocks](AboutSeeder.php)  \n\n\n## Seeding\n\n* **Create seed** `php artisan make:seeder GridSeeder`  \n* **Run seed** `php artisan db:seed --class=GridSeeder`\n* **Seed Addon** `php artisan db:seed --addon=[addon]`\n\n## Module/Extension\n* **Create Addon**  `php artisan make:addon my_project.module.library`  \n* **Create Stream**  `php artisan make:stream books my_project.module.library`  \n* **Install Module**  `php artisan module:install my_project.module.library`  \n* **UnInstall Module**  `php artisan module:uninstall my_project.module.library`  \n* **ReInstall Module**  `php artisan module:reinstall my_project.module.library`  \n* **ReInstall \u0026 Seed Module**  `php artisan module:reinstall my_project.module.library --seed`  \n* **Publish Addon**  `php artisan addon:publish library`\n\n## Migration\n* **Default Migration** : `php artisan make:migration create_more_fields --addon=example.module.test`\n* **Field Migration** : `php artisan make:migration create_more_fields --addon=example.module.test --fields`\n* **Stream Migrations** : `php artisan make:migration create_example_stream --addon=example.module.test --stream=widgets`\n* **Run Migration** : `php artisan migrate --all-addons`\n\n## Addon\n* **Create Addon** `php artisan make:addon my_project.plugin.widget`\n\n## Publish Commands\n* **Publish all addons to the resources/{ref}/addons folder** `php artisan addon:publish`\n* **Publish streams configuration to resources/streams** `php artisan streams:publish`\n* **Publish posts module only** `php artisan addon:publish anomaly.module.posts`\n* **Publish for an app other than the default one** `php artisan addon:publish anomaly.module.posts --app=my_ref`\n* **Publish streams config for an app other than your default one** `php artisan streams:publish --app=my_ref`\n\n## Images\n* **Crop image from top instead of center** `{{ thumbnail.image().fit(400, 400, null,'top')|raw }}` [Position](http://image.intervention.io/api/fit)\n\n## Overriding templates values\nA common problem is not being able to override a block within the metadata partial from an extended view. This is something you may wanna do to change the og/twitter image for example. So what you can do is setting a value to template like this:\n\n** In metadata.twig **\n```\n\u003cmeta property=\"og:image\" content=\"{{template.og_image | default( img(\"theme::images/og.jpg\").url() }}\" /\u003e\n```\n\n** In another view where you want another image to represent the page **\n```\n{% do template.set('og_image', img(\"theme::images/og.jpg\").url() ) %} \n```\n\n## Field Types\n### WYSIWYG - Changing Redactor configuration.\nTo simplify for a user you might want to edit the buttons displayed. It can be done by\npublishing the field type using the following command `php artisan addon:publish anomaly.field_type.wysiwyg`.\n\nBut you could also do it in a migration when creating the field type. The important step here, is\nto make sure you clear the configuration. And then use plugins and buttons to set what should be \navailable. \n\n```\n\"example\" =\u003e [\n    \"type\"   =\u003e \"anomaly.field_type.wysiwyg\",\n    \"config\" =\u003e [\n        \"default_value\" =\u003e null,\n        \"configuration\" =\u003e \"default\",\n        \"line_breaks\"   =\u003e false,\n        \"sync\"          =\u003e true,\n        \"height\"        =\u003e 500,\n        \"buttons\"       =\u003e ['bold','lists','link'],\n        \"plugins\"       =\u003e ['source','fullscreen'],\n    ]\n]\n```\n\n### Modify field type configuration\n``` \n    public function up()\n    {\n        $metaDescriptionConfig = [\n            'min' =\u003e null,\n            'max' =\u003e 290,\n        ];\n\n        if ($field = $this-\u003efields()-\u003efindBySlugAndNamespace('meta_description', 'pages')) {\n            $field-\u003esetAttribute('instructions', 'Meta descriptions are HTML attributes that provide concise summaries of webpages. They are between one sentence to a short paragraph and appear underneath the blue clickable links in a search engine results page (SERP).')-\u003esave();\n            $field-\u003esetAttribute('warning', 'When left empty, the website will try to use the page introduction field. However, depending on a user\\'s query, Google might pull meta description text from other areas on your page (in an attempt to better answer the searcher\\'s query)')-\u003esave();\n            $field-\u003esetAttribute('config', $metaDescriptionConfig)-\u003esave();\n        };\n\n        if ($field = $this-\u003efields()-\u003efindBySlugAndNamespace('meta_title', 'pages')) {\n            $field-\u003esetAttribute('instructions', \"A title tag is an HTML element that specifies the title of a web page. Title tags are displayed on search engine results pages (SERPs) as the clickable headline for a given result, and are important for usability, SEO, and social sharing. The title tag of a web page is meant to be an accurate and concise description of a page's content.\")-\u003esave();\n            $field-\u003esetAttribute('warning', 'When field is left empty, the website will try using the main title of the page.')-\u003esave();\n        };\n    }\n```\n\n## Files / Images \n* **/app/project/files/local/images/d61b5c3d5ea2bb829cbfbd05e68477b3.jpg?v=1513155180** `{{image('local://images/blackjaq.jpg').fit(500,300).quality(100).url()}}`\n## Request\n\n### Get current paths\n* **http://domain.com** `{{ request_root() }}`\n* **http://domain.com/path** `{{request_url()}}`\n* **http://domain.com/path** `{{request_fullUrl()}}`\n* **path/to/a/post** `{{ request_path() }}`\n\n### Url Parameters\n\n* **http://domain.com?foo=bar**  `{{ request_get(\"foo\") }} == bar`\n* **GET**  `{{ request_method() }}`\n* **http://domain.com/post/number-one**  `{{ request_segment(1) }} == post`\n\n### Booleans\n* **boolean**  `{{ request_is(\"myaccount/*\", \"account/*\") }} `\n* **boolean**  `{{ request_ajax() }} `\n \n## Route\n\n* **bar**   : `{{ route_parameter(\"foo\", \"default\") }} `\n* **[\"foo\" =\u003e \"bar\"]**   : `{{ route_parameters() }} `\n* **/the/path/example**   : `{{ route_uri() }} `\n* **boolean**   : `{{ route_secure() }} `\n* **example.com**   : `{{ route_domain() }} `\n* **the route name if any**   : `{{ route_get_name() }} `\n* **Back to previous page** : `url_previous()`\n\n## Url\n\n* **\"http://domain.com/example\"** : `{{ url_to(\"example\") }} `\n* **\"https://domain.com/example\"** : `{{ url_secure(\"example\") }} `\n* **\"users/password/forgot\"** : `{{ url_route(\"anomaly.module.users::password.forgot\") }} `\n\n## Session\n* **\"bar\"**   : `{{ session_get(\"foo\") }} `\n* **\"bar\"**   : `{{ session_pull(\"foo\") }} `\n* **null**   : `{{ session_pull(\"foo\") }} `\n* **boolean**   : `{{ session_has(\"foo\") }} `\n\n## Settings\n* **Get a settings value in twig**  :  `{{ setting('streams::name') }}`\n\nGetting a settings value in php:\n\n```\n$settings = app(SettingRepositoryInterface::class);\n$listId = $settings-\u003evalue('pixney.module.campaigns::listId');\n```\n\n## Available Events\nThanks @squatto\n```\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('entry_created')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('entry_creating')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('entry_deleted')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('entry_saved')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('entry_updated')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('form_posted')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('form_posting')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('form_saved')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('form_saving')\nAnomaly\\Streams\\Platform\\Addon\\Addon-\u003efire('registered')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('built')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('make')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('post')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('posted')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('posting')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('querying')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('ready')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('saved')\nAnomaly\\Streams\\Platform\\Ui\\Form\\FormBuilder-\u003efire('saving')\nAnomaly\\Streams\\Platform\\Ui\\Table\\Component\\View\\View-\u003efire('querying')\nAnomaly\\Streams\\Platform\\Ui\\Table\\TableBuilder-\u003efire('built')\nAnomaly\\Streams\\Platform\\Ui\\Table\\TableBuilder-\u003efire('querying')\nAnomaly\\Streams\\Platform\\Ui\\Table\\TableBuilder-\u003efire('ready')\nAnomaly\\Streams\\Platform\\Ui\\Table\\TableBuilder-\u003efire('row_deleted')\nAnomaly\\Streams\\Platform\\Ui\\Table\\TableBuilder-\u003efire('rows_deleted')\n```\n\n\n## String\n### Hello World\n```\n{{ str_humanize(\"hello_world\") }} \n{{ str_truncate(string, 100) }}\n\n{% if str_is(\"*.module.*\", addon(\"users\").namespace) %}\n    That's a valid module namespace!\n{% endif %}\n```\n\n* **\"someSlug\"** : `{{ str_camel(\"some_slug\") }} `\n* **\"SomeSlug\"** : `{{ str_studly(\"some_slug\") }} `\n* **4sdf87yshs** : `{{ str_random(10) }}`\n \n## Translator\n\n* **\"Users Module\"** : `{{ trans(\"anomaly.module.users::addon.name\") }} `\n* **boolean** : `{{ trans_exists(\"anomaly.module.users::field.bogus.name\") }} `\n\n## Hreflang\n```\n{# {% for locale in config_get('streams::locales.enabled') %} #}\n{% set lq = request_getQueryString() %}\n{% for locale in ['en', 'sv'] %}\n\u003clink rel=\"alternate\" href=\"{{ url_locale(request_path(), locale) }}\" hreflang=\"{{locale}}\" title=\"\"/\u003e\n{% endfor %}\n```\n\n## Locale switch\n```\n\u003cdiv class=\"locales\"\u003e\n    {% set query = request_getQueryString() %} \n    {# {% for locale in config_get('streams::locales.enabled') %} #}\n    {% for locale in ['en','sv',] %}\n    \u003ca \n    {{ config( 'app.locale')==locale ? 'class=\"current\"'}}\n    href=\"{{ url_locale(request_path(), locale) }}{{ query ? '?'~query }}\"\u003e\n        {{ locale }}\n    \u003c/a\u003e\n    {% endfor %}\n\u003c/div\u003e\n```\n\n## Redirecting WWW.DOMAIN.COM -\u003e DOMAIN.COM\nCreate a middle wear as below, and then simply add it to the middlewarecollection. This is done by using the middleware attribute in your service provider.\n\n```\nprotected $middleware = [\n    Http\\Middleware\\NonWWWMiddleware::class\n];\n```\n\n```\n\u003c?php\n\nnamespace Pixney\\ThidrandiTheme\\Http\\Middleware;\n\nuse Closure;\n\n/**\n * Class NonWWWMiddleware\n *\n *  @author Pixney AB \u003chello@pixney.com\u003e\n *  @author William Åström \u003cwilliam@pixney.com\u003e\n *  \n *  @link https://pixney.com\n */\nuse Illuminate\\Support\\Facades\\Redirect;\n\n\nclass NonWWWMiddleware\n{\n    public function handle($request, Closure $next)\n    {\n        if (starts_with($request-\u003eheader('host'), 'www.')) {\n            $host = str_replace('www.', '', $request-\u003eheader('host'));\n            $request-\u003eheaders-\u003eset('host', $host);\n\n            return Redirect::to($request-\u003efullUrl(), 301);\n        }\n\n        return $next($request);\n    }\n}\n```\n\n\n## Other kinky stuff\n_The `memory_usage ` function returns the memory used by the request._  \n`{{ memory_usage() }}`\n\n_The `request_time ` function returns the elapsed time for the request._  \n`{{ request_time(3) }}`  \n\n## Minify Html\n`composer require nochso/html-compress-twig`\n\nEdit : `config/twigbridge.php`\n\n```\n'enabled' =\u003e [\n    'TwigBridge\\Extension\\Loader\\Functions',\n    function() {\n        return new \\nochso\\HtmlCompressTwig\\Extension(true);\n    }\n]\n```\n\nAdd this to your layout(s)\n\n```\n{% htmlcompress %}\nYour html\n{% endhtmlcompress %}\n```\n\n# Custom\n* **Svgstore tag** `\u003csvg\u003e\u003cuse xlink:href=\"#logo\" /\u003e\u003c/svg\u003e`\n\n### Twitter\n\nhttps://dev.twitter.com/cards/types/summary-large-image\n\n```\n\u003cmeta name=\"twitter:card\" content=\"summary_large_image\"\u003e\n\u003cmeta name=\"twitter:site\" content=\"@nytimes\"\u003e\n\u003cmeta name=\"twitter:creator\" content=\"@SarahMaslinNir\"\u003e\n\u003cmeta name=\"twitter:title\" content=\"Parade of Fans for Houston’s Funeral\"\u003e\n\u003cmeta name=\"twitter:description\" content=\"NEWARK - The guest list and parade of limousines with celebrities emerging from them seemed more suited to a red carpet event in Hollywood or New York than than a gritty stretch of Sussex Avenue near the former site of the James M. Baxter Terrace public housing project here.\"\u003e\n\u003cmeta name=\"twitter:image\" content=\"http://graphics8.nytimes.com/images/2012/02/19/us/19whitney-span/19whitney-span-articleLarge.jpg\"\u003e\n```\n\n### SEO Cheat Sheets\n\nhttps://moz.com/blog/seo-cheat-sheet  \nhttp://www.sharelinkgenerator.com/\n\n### Other development related links\n* [Meta tags](https://megatags.co/) \n* [Sharing buttons](http://sharingbuttons.io/)\n\n\n\n### Laravel MIX 4 and SVG Spritemap plugin\n```\nnpm uninstall svg-spritemap-webpack-plugin\nnpm install svg-spritemap-webpack-plugin --save-dev\nconst svgSourcePath = \"resources/assets/svgs/*.svg\";\nconst svgSpriteDestination=\"resources/views/svgs.blade.php\";\n\nmix.js('resources/assets/js/app.js', 'public/js')\n   .sass('resources/assets/sass/app.scss', 'public/css')\n   .webpackConfig({\n      plugins: [\n         new SVGSpritemapPlugin(\n            svgSourcePath, {\n               output: {\n                  filename: svgSpriteDestination,\n                  svgo: true\n               },\n            }\n         )\n      ]\n   })\n   .version([]).sourceMaps();\n```\n\n## Add svg sprite to your page\n```\n\u003cdiv style=\"display:none\"\u003e\n{% include 'theme::partials/svgs/svgs' %}\n\u003c/div\u003e\n```\n## Use the svg\n```\n\u003csvg class=\"icon icon--linkedin\"\u003e\n\u003cuse xlink:href=\"#sprite-linkedin\"/\u003e\n\u003c/svg\u003e\n```\n\n\n### Remove index.php from urls, using runcloud.io\n```\n// nano pixney.location.root.indexphp.conf\nif ($request_uri ~* \"^(.*/)index\\.php(/?)(.*)\") {\n    return 301 $1$3;\n}\n```\n\n```\n// nano pixney.location.main.indexphp.conf\nif ($request_uri ~* \"^(.*/)index\\.php$\") {\n    return 301 $1;\n}\n```\n\n\n### Russian Stuff\n```\nalias composer=\"php ~/composer.phar -vv\"\nalias gitp=\"bash ~/scripts/gitpush.sh\"\n\nalias artis=\"php artisan -vvv\"\nalias pserve=\"php -S localhost:8800\"\nalias arclearall=\"artis view:clear \u0026\u0026 artis route:clear \u0026\u0026 artis cache:clear \u0026\u0026 artis twig:clean \u0026\u0026 artis asset:clear \u0026\u0026 artis clear-compiled\"\n\nalias ace='node ace'\n\nalias perm_d=\"find . -type d -exec chmod 755 {} +\"\nalias perm_f=\"find . -type f -exec chmod 644 {} +\"\n\n## Forms Module\nIf we are using the (PRO) Forms module and want to render our own custom stuff we can simply do :\n\n```\n\n```\n// Get the form and set it to redirect to our homepage if its successfully submitted.\n{% set form = form('forms','your_form_slug').redirect('/').get() %}\n\n// Catch the errors\n{% if form.hasErrors %}\n\tErrors\n\t{% for key,errors in form.errors.messages %}\n\t\t{{key}}\n\t\t{% for error in errors %}\n\t\t    {{error}}\n\t\t{% endfor %}\n\t{% endfor %}\n{% endif %}\n\n// Our form output, that you will use to style your form your way\n{{ form.open({'class':'williams_contact_form'})|raw }}\n\t{{ form.fields.name|raw }}\n\t{{ form.fields.email|raw }}\n\t{{ form.fields.message|raw }}\n\t\u003cbutton\u003eSend\u003c/button\u003e\n{{ form.close|raw }}\n```\n\nhttps://laravel-news.com/bash-aliases\n\n## Custom Form Validations where instances are a concern\nCards is the stream, * will gets replaced with the instance number\n```\n    protected $rules = [\n        'entry_cards_*_fall_back' =\u003e [\n            'required_with:entry_cards_*_featured'\n        ]\n    ];\n\n    public function onReady(){\n        $rules = [];\n\n        foreach ($this-\u003egetRules() as $field =\u003e $field_rules){\n            $field = str_replace('*',$this-\u003egetOption('repeater_instance'), $field);\n            $field_rules = str_replace('*',$this-\u003egetOption('repeater_instance'), $field_rules);\n            $rules[$field] = $field_rules;\n        }\n\n        $this-\u003esetRules($rules);\n    }\n```\n\n## Available .env settings\n```\nADMIN_THEME\nAPP_DEBUG\nAPP_NAME\nAPP_TIMEZONE\nAPPLICATION_DOMAIN\nDATE_FORMAT\nDB_CACHE\nDB_CACHE_TTL\nDEBUG_BAR\nDEFAULT_LOCALE\nDOMAIN_PREFIX [non-www or www]\nENABLED_LOCALES\nFORCE_SSL\nFROM_ADDRESS\nFROM_NAME\nHTTP_CACHE\nHTTP_CACHE_ALLOW_BOTS\nHTTP_CACHE_EXCLUDED\nHTTP_CACHE_RULES\nHTTP_CACHE_TTL\nIP_WHITELIST\nLOCKING_ENABLED\nMAIL_DRIVER [smtp || mail || sendmail || mailgun || mandrill || log]\nMAIL_HOST\nMAIL_PASSWORD\nMAIL_PORT\nMAIL_USERNAME\nMAINTENANCE_AUTH\nMAINTENANCE_MODE\nRESULTS_PER_PAGE\nSTANDARD_THEME\nTIME_FORMAT\nUNIT_SYSTEM [imperial or metric]\n```\n\n## Validate a textarea that should contains a list of email addresses separated by comma.\n\n__By: Eric Leversen__\n\n```\n'email_list' =\u003e [\n    'instructions' =\u003e 'Separate each address using a comma',\n    'label'        =\u003e 'Bulk Subscribe',\n    'placeholder'  =\u003e 'Enter the email addresses to subscribe.',\n    'type'         =\u003e 'anomaly.field_type.textarea',\n    'rules'        =\u003e [\n        'required',\n        'valid_email_list',\n    ],\n    'validators'   =\u003e [\n        'valid_email_list' =\u003e [\n            'message' =\u003e false,\n            'handler' =\u003e ValidateEmailList::class,\n        ],\n    ],\n],\n```\n\n```\npublic function handle(FormBuilder $builder, $attribute, $value)\n{\n    $invalid_emails=[];\n    foreach (explode(',',$value) as $email) {\n        if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {\n            $invalid_emails[]=$email;\n        }\n    }\n    if (! empty($invalid_emails) ) {\n        $builder-\u003eaddFormError('email_list', 'These are not valid addresses: '.implode(', ',$invalid_emails));\n        return false;\n    }\n    return true;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpixney%2Fpyrocms-cheatsheet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpixney%2Fpyrocms-cheatsheet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpixney%2Fpyrocms-cheatsheet/lists"}