Have you seen our new video tutorials? Check it out!

Streams Platform

The Streams Platform acts as Pyro's engine. It's the foundation of PyroCMS.

Getting Started

This section will help you get started in understanding what the Streams Platform is, it's role in PyroCMS, and how to use it.

Introduction

The Streams Platform is a Composer package that acts as the foundation or engine for PyroCMS. Every addon in PyroCMS uses the Streams Platform to build it's tables, forms, nested lists, to install and uninstall, and to do many other things.

Installation

The Streams Platform comes setup and ready right out of the box with PyroCMS. It's highly recommended to start projects that may not leverage the CMS aspects of PyroCMS with a general installation and remove what you don't need from there.

However, if you would like to install it in an existing Laravel application there are a couple things you need to do.

Notice: If you are using PyroCMS the Streams Platform is already installed for you!

Install with Composer

First require the composer package by running composer require anomaly/streams-platform or adding the following line to your composer.json file:

"anomaly/streams-platform": "1.1.x-dev"

Register the service provider

Next you need to register the service provider. To do this add the following service provider to the end of the providers section in config/app.php:

Anomaly\Streams\Platform\StreamsServiceProvider::class

Register the kernels

The Streams Platform adds low level functionality to the HTTP and console kernels. In order for this to work properly you must register the kernels in the bootstrap/app.php file:

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    'Anomaly\Streams\Platform\Http\Kernel'
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    'Anomaly\Streams\Platform\Console\Kernel'
);

You're all set! You can now use addons just like PyroCMS as well as all of the other services and utilities within the Streams Platform.

The Basics

This section will introduce you to the basics of the Streams Platform to help you implement them effectively in your projects.

Streams Plugin

This section will go over how to use the built-in plugin that comes with the Streams Platform.

Mapping functions to services

The streams plugin leverages function mapping to handle defining multiple functions within the same object.

For example all functions called that start with agent_ will be mapped to the \Jenssegers\Agent\Agent class. When function mapping is being used the suffix will be camel cased into a method name.

For example agent_is_mobile() becomes $agent->isMobile() and any parameters are passed along accordingly.

Addon

The addon functions provide access to the \Anomaly\Streams\Platform\Addon\AddonCollection.

addon

The addon function returns a decorated addon instance.

Returns: \Anomaly\Streams\Platform\Addon\AddonPresenter
Arguments
Key Required Type Default Description

$identifier

true

string

none

The slug or dot namespace of the addon.

Twig
// Specify as a dot namespace.
{{ addon('anomaly.module.users').name }} // Users Module

// Or you can pass the
// slug if it's unique.
{{ addon('pages').name }} // Users Module
addons

The addons method returns a decorated collection of addons.

Returns: \Anomaly\Streams\Platform\Addon\AddonCollection
Arguments
Key Required Type Default Description

type

false

string

null

The type of addons to return.

Twig
{{ addons() }} // A collection of all addons.

// A collection of all modules.
{% for module in addons('modules') %}
    <p>{{ module.name }} is enabled.</p>
{% endfor %}

Agent

The agent_* functions map directly to the \Jenssegers\Agent\Agent class.

{{ agent_is("iPhone") }} // boolean

{{ agent_is_mobile() }} // boolean

{{ agent_platform() }} // "OS X"

For more information on usage refer to the jenssegers/agent documentation.

Asset

The asset_* functions map directly to the ``Anomaly\Streams\Platform\Asset\Asset` class. For more information on usage please refer to asset documentation.

{{ asset_add("theme.js", "theme::js/vendor/*") }}
{{ asset_add("theme.js", "theme::js/libraries/sortable.js", ["min"]) }}

{{ asset_script("theme.js", ["min"]) }}

{% for script in asset_scripts("scripts.js") %}
    {{ script|raw }}
{% endfor %}
Including javascript constants

The constants function returns a number of required javascript constants necessary for field types and potentially other components to work correctly. Make sure you include it in your themes!

{{ constants() }}

Example of the included JavaScript constants:

<script type="text/javascript">

    var APPLICATION_URL = "{{ url() }}";
    var APPLICATION_REFERENCE = "{{ env('APPLICATION_REFERENCE') }}";
    var APPLICATION_DOMAIN = "{{ env('APPLICATION_DOMAIN') }}";

    var CSRF_TOKEN = "{{ csrf_token() }}";
    var APP_DEBUG = "{{ config_get('app.debug') }}";
    var APP_URL = "{{ config_get('app.url') }}";
    var REQUEST_ROOT = "{{ request_root() }}";
    var REQUEST_ROOT_PATH = "{{ parse_url(request_root()).path }}";
    var TIMEZONE = "{{ config_get('app.timezone') }}";
    var LOCALE = "{{ config_get('app.locale') }}";
</script>

Auth

The auth_* functions provide limited access to the \Illuminate\Contracts\Auth\Guard class.

{% if auth_check() %}
    Hello {{ auth_user().display_name }}!
{% endif %}

{% if auth_guest() %}
    Welcome guest!
{% endif %}

Breadcrumb

The breadcrumb functions returns the \Anomaly\Streams\Platform\Ui\Breadcrumb\BreadcrumbCollection.

You can use this function to automatically generate a Bootstrap 3/4 breadcrumb:

{{ breadcrumb() }} // Returns Bootstrap breadcrumb

You can use the same function to generate your own breadcrumb:

<ol class="breadcrumb">
    {% for breadcrumb, url in breadcrumb() %}
        {% if loop.last %}
            <li class="breadcrumb-item active">{{ trans(breadcrumb) }}</li>
        {% else %}
            <li class="breadcrumb-item"><a href="{{ url }}">{{ trans(breadcrumb) }}</a></li>
        {% endif %}
    {% endfor %}
</ol>

Carbon

The carbon function provides access to the \Carbon\Carbon class.

Returns: /Carbon/Carbon
Arguments
Key Required Type Default Description

$time

false

string

null

A date/time string.

$timezone

false

string

Configured default timezone.

A timezone string.

Twig
{{ carbon().today() }} // 2016-06-24 00:00:00

{{ carbon('-1 day', config('app.timezone')) }} // "2016-08-17 15:05:26"

{{ carbon('-1 day', config('app.timezone')).diffInHours() }} // 24

Config

The config functions provide limited access to the \Illuminate\Contracts\Config\Repository class.

{{ config_get("app.name") }} // PyroCMS

{{ config_get("streams::locales.default") }} // en

{{ config_has("foo") }} // boolean

CSRF

The CSRF functions provide access to CSRF information.

csrf_token

The csrf_token method returns the CSRF token value.

Returns: string
Twig
{{ csrf_token() }} // The CSRF token
csrf_field

The csrf_field method returns the name of the CSRF field.

Returns: string
Twig
{{ csrf_field() }} // The CSRF field name

Entries

The entries and query functions provide you with a convenient, fluent interface to fetch streams and non-streams database records respectively.

Introduction

The Streams Platform comes with a clean, extendable, fluent API for building database queries within the view layer.

entries

The entries function starts a model criteria query for database records powered by Streams. Being that nearly everything in PyroCMS is a stream this is your primary entry point to retrieving database records.

Returns: \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$namespace

true

string

none

The stream namespace.

$slug

false

string

The namespce provided.

The stream slug.

Twig
<ul>
    {% for category in entries('posts', 'categories').get() %}
    <li>
        {{ category.slug }}
    </li>
    {% endfor %}
</ul>
Pro Tip: The entry criteria is extendable! To learn how to add your own functionality to queries refer to the criteria documetation.
query

The query function starts a model criteria query for database records that are not powered by Streams though it works all the same for Streams powered database records.

Returns: \Anomaly\Streams\Platform\Model\EloquentCriteria
Arguments
Key Required Type Default Description

$model

false

string

null

The model to start the query from.

Twig
{% set users = query()
    .from('test_table')
    .where('active', true)
    .get() %}

// Using a model
{% set users = query('App\Example\TestModel')
    .where('active', true)
    .get() %}

Note: To use a custom criteria you must provide a model because the model returns it's criteria.
Retrieving Results

This section will show you how to return results from the model criteria returned by entries and query functions.

EloquentCriteria::get()

The get method returns the results of the query.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCollection or \Anomaly\Streams\Platform\Entry\EntryCollection
Arguments
Key Required Type Default Description

$columns

false

array

["*"]

The columns to select.

Twig
{% set users = entries('users', 'users')
    .where('email', 'LIKE', '[email protected]%')
    .where('activated', true)
    .get() %}

{% for user in users %}
    {{ user.display_name }}
{% endfor %}
EloquentCriteria::first()

The first method returns the first matching query result.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentPresenter or \Anomaly\Streams\Platform\Entry\EntryPresenter
Arguments
Key Required Type Default Description

$columns

false

array

["*"]

The columns to select.

Twig
{% set user = entries('users').where('display_name', 'Ryan Thompson').first() %}

{{ user.email }}
EloquentCriteria::find()

The find method allows you to return a single record by it's ID.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentPresenter or \Anomaly\Streams\Platform\Entry\EntryPresenter
Arguments
Key Required Type Default Description

$identifier

true

integer

none

The ID of the result to return.

$columns

false

array

["*"]

The columns to select.

Twig
{% user = entries('users').find(1) %}

{{ user.email.mailto|raw }}
EloquentCriteria::findBy()

The findBy by method allows you to find a single query result by a column value. This comes in handy for finding records by slug for example.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentPresenter or \Anomaly\Streams\Platform\Entry\EntryPresenter
Arguments
Key Required Type Default Description

$column

true

string

none

The column to test.

$value

true

mixed

none

The value to test the column by.

$columns

false

array

["*"]

The columns to select.

Twig
{% admin = entries('roles', 'users').findBy('slug', 'admin') %}

// You can also map the column into the method name.
{% admin = entries('roles', 'users').findBySlug('admin') %}
EloquentCriteria::paginate()

The paginate method returns the result of the entries and query functions as a pagination object.

Returns: \Illuminate\Pagination\LengthAwarePaginator
Arguments
Key Required Type Default Description

$perPage

false

string

15

The number of entries per page.

$columns

false

array

["*"]

The columns to select.

Twig
{% posts = entries('posts').paginate() %}

{% for post in posts %}
    <p>
        {{ post.title }}
    </p>
{% endfor %}

{{ posts.links|raw }}
Aggregates

The model criteria also provide a variety of aggregate methods such as count, max, min, avg, and sum. You can call any of these methods after constructing your query.

EloquentCriteria::count()

The count method returns the total number of query results.

Returns: integer
Arguments
Key Required Type Default Description

$columns

false

array

["*"]

The collection to add the asset to.

Twig
{% activate = entries('users').where('activated', true).count() %}
EloquentCriteria::sum()

The sum method returns the sum of the column value.

Returns: integer
Arguments
Key Required Type Default Description

$column

true

string

none

The column to summarize.

Twig
{% orders = entries('store', 'orders').where('finalized', true).sum('subtotal') %}
EloquentCriteria::max()

The max method returns the highest column value.

Returns: mixed
Arguments
Key Required Type Default Description

$column

true

string

none

The column to find the max for.

Twig
{% price = entries('store', 'products').where('enabled', true).max('price') %}
EloquentCriteria::min()

The min method returns the lowest column value.

Returns: mixed
Arguments
Key Required Type Default Description

$column

true

string

none

The column to find the min for.

Twig
{% price = entries('store', 'products').where('enabled', true).min('price') %}
EloquentCriteria::avg()

The avg method returns the average value of the column.

Returns: integer
Arguments
Key Required Type Default Description

$column

true

string

none

The column to average.

Twig
{% mean = entries('store', 'products').where('enabled', true).avg('price') %}
Where Clauses

This section will go over where clauses for the entries and query model criteria functions.

EloquentCriteria::where()

The where method adds a where clauses to the query. The most basic call to where requires three arguments. The first argument is the name of the column. The second argument is an operator, which can be any of the database's supported operators. Finally, the third argument is the value to evaluate against the column.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
// Example that verifies the value of the "votes" column is equal to 100:
{% set users = entries('users').where('votes', '=', 100).get() %}

// Assuming the "=" operator.
{% set users = entries('users').where('votes', 100).get() %}
EloquentCriteria::orWhere()

You may chain where constraints together as well as add or where clauses to the query.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% set = users = entries('users')
    .where('votes', '>', 100)
    .orWhere('name', 'John')
    .get() %}
EloquentCriteria::whereBetween()

The whereBetween method verifies that a column's value is between two values.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$values

true

array

none

The values to test the column against.

Example
{% set users = entries('users').whereBetween('votes', [1, 100]).get() %}
EloquentCriteria::whereNotBetween()

The whereNotBetween method verifies that a column's value lies outside of two values.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$values

true

array

none

The values to test the column against.

Twig
{% set users = entries('users').whereNotBetween('votes', [1, 100]).get() %}
EloquentCriteria::whereIn()

The whereIn method verifies that a given column's value is contained within the given array.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$values

true

array

none

The array of values to find.

Twig
{% set users = entries('users').whereIn('id', [1, 2, 3]).get() %}
EloquentCriteria::whereNotIn()

The whereNotIn method verifies that a given column's value is not contained within the given array.

Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$values

true

array

none

The array of values to exclude.

Twig
{% set users = entries('users').whereNotIn('id', [1, 2, 3]).get() %}
EloquentCriteria::whereNull()

The whereNull method verifies that the value of the given column is NULL.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

Twig
{% set users = entries('users').whereNull('updated_at').get() %}
EloquentCriteria::whereNotNull()

The whereNotNull method verifies that the column's value is not NULL.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

Twig
{% set users = entries('users').whereNotNull('updated_at').get() %}
EloquentCriteria::whereDate()

The whereDate method may be used compare a column's value against a date.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% set users = entries('users').whereDate('created_at', '2016-10-10').get() %}
EloquentCriteria::whereMonth()

The whereMonth method may be used compare a column's value against a specific month of an year.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% set users = entries('users').whereMonth('created_at', '10').get() %}
EloquentCriteria::whereDay()

The whereDay method may be used compare a column's value against a specific day of a month.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% set users = entries('users').whereDay('created_at', '10').get() %}
EloquentCriteria::whereYear()

The whereYear method may be used compare a column's value against a specific year.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% set users = entries('users').whereYear('created_at', '2016').get() %}
EloquentCriteria::whereColumn()

The whereColumn method may be used to test two values with an operator.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column to test.

$operator|$compare

true

string

none

The test operator or the column to compare.

$compare

false

string

null

The column to compare.

Twig
// Assumes "=" operator
{% set users = entries('users').whereColumn('first_name', 'last_name').get() %}

// Use a different operator.
{% set users = entries('users').whereColumn('updated_at', '>', 'created_at').get() %}
JSON Where Clauses

Laravel supports querying JSON column types on databases that provide support for JSON column types. You can leverage this the in the criteria queries too. Currently, this includes MySQL 5.7 and Postgres. To query a JSON column, use the -> operator:

{% set users = entries('users')
    .where('options->language', 'en')
    .get() %}

{% set users = entries('users')
    .where('preferences->dining->meal', 'salad')
    .get() %}
Ordering, Grouping, Limit, & Offset

The Streams Platform supports a number of Laravel methods for ordering, grouping, limit, and offsetting records.

EloquentCriteria::orderBy()

The orderBy method allows you to sort the result of the query by a given column.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$direction

true

string

none

The direction to order column values.

Twig
{% set users = entries('users').orderBy('name', 'desc').get() %}
EloquentCriteria::inRandomOrder()

The inRandomOrder method may be used to sort the query results randomly. For example, you may use this method to fetch a random record.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Twig
{% set user = entries('users').inRandomOrder().first() %}
EloquentCriteria::groupBy()

The groupBy method can be used to group the query results.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

Twig
{% set users = entries('users').groupBy('category').get() %}
EloquentCriteria::having()

The having method is used often in conjunction with the groupBy method.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$column

true

string

none

The name of the column.

$operator|$value

true

string

none

The where operator.

$value

false

string

null

The value if an operator is defined.

Twig
{% users = entries('users').groupBy('account_id').having('account_id', '>', 100).get() %}
EloquentCriteria::skip()

The skip method is an alias for limit.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$offset

true

integer

none

The number of results to skip.

Twig
{% users = entries('users').skip(10).get() %}
EloquentCriteria::offset()

The offset method skips a number of results from the query.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$number

true

integer

none

The number of results to skip.

Twig
{% set users = entries('users').offset(10).get() %}
EloquentCriteria::take()

The take method is an alias for limit.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$number

true

integer

none

The number of results to return.

EloquentCriteria::limit()

The limit method specifies the number of results to return.

Returns: \Anomaly\Streams\Platform\Eloquent\EloquentCriteria or \Anomaly\Streams\Platform\Entry\EntryCriteria
Arguments
Key Required Type Default Description

$number

true

integer

none

The number of results to return.

Twig
{% set users = entries('users').limit(5).get() %}
Searching

This section will show you how to search for results from the search criteria returned by entries and query functions.

EloquentCriteria::search()

The search method returns a new search criteria instance which can be used just like the entry and eloquent criteria.

Returns: \Anomaly\Streams\Platform\Search\SearchCriteria
Arguments
Key Required Type Default Description

$term

true

string

none

The term you would like to search for.

Twig
// The search method must be used after entries/query
{% set results = entries('users').search('gmail').get() %}

// You can still chain criteria methods after search.
{% set results = entries('users').search('gmail').where('active', true).get() %}
Searchable Models

In order to leverage model searching you must make your model searchable using the \Laravel\Scout\Searchable trait:

use \Laravel\Scout\Searchable;

For Streams entry models you can also simply define the searchable flag since the base models implement this trait already:

protected $searchable = true;
Searchable Streams

Defining Streams as searchable can be done just like a model. However you may want to include this option in your streams migration as well:

protected $stream = [
    'slug'         => 'users',
    'title_column' => 'username',
    'searchable'   => true,
    'trashable'    => true,
];

Env

You can access environmental values with the env function. This function behaves just like the Laravel helper function.

{% if env("APP_DEBUG") %}
    You are debugging!
{% endif %}

Footprint

The footprint functions provide information about load time and footprint.

request_time

The request_time function returns the elapsed time for the request.

Returns: float
Arguments
Key Required Type Default Description

$decimal

false

integer

2

The number of decimals to return.

Twig
{{ request_time(3) }} // 0.551
memory_usage

The memory_usage function returns the memory used by the request.

Returns: string
Arguments
Key Required Type Default Description

$precision

false

integer

1

The number of decimals to return.

Twig
{{ memory_usage() }} // 6.5 m

Request

The request_* functions map directly to the \Illuminate\Http\Request class.

For more information on usage please refer to documentation for requests in Laravel.

{{ request_get("foo") }} // bar

{{ request_method() }} // GET

{{ request_root() }} // http://domain.com/

{{ request_segment(1) }} // foo

{{ request().route('id') }} // 123

Session

The session_* functions provide limited access to the \Illuminate\Session\Store class.

{{ session_get("foo") }} // "bar"

{{ session_pull("foo") }} // "bar"
{{ session_pull("foo") }} // null

{{ session_has("foo") }} // boolean

String

The str_* functions map directly to the \Anomaly\Streams\Platform\Support\Str class which extends Laravel's \Illuminate\Support\Str class.

For more information on usage please refer to the String service.

{{ str_humanize("hello_world") }} // "Hello World"

{{ str_truncate(string, 100) }}

{% if str_is("*.module.*", addon("users").namespace) %}
    That's a valid module namespace!
{% endif %}

{{ str_camel("some_slug") }} // "someSlug"

{{ str_studly("some_slug") }} // "SomeSlug"

{{ str_random(10) }} // 4sdf87yshs

Translator

The translator_* functions provide access to the \Illuminate\Translation\Translator class.

{{ trans("anomaly.module.users::addon.name") }} // "Users Module"

{{ trans_exists("anomaly.module.users::field.bogus.name") }} // boolean

URL

The url_* functions map directly to the \Illuminate\Contracts\Routing\UrlGenerator.

You can also refer to Laravel URL helpers for more information.

{{ url_to("example") }} // "http://domain.com/example"

{{ url_secure("example") }} // "https://domain.com/example"

{{ url_route("anomaly.module.users::password.forgot") }} // "users/password/forgot"

View

View functions help leverage the view engine.

view

The view function returns a rendered view.

The single most important detail of this function versus using Twig's include is that the view is passed through the view composer in order to allow overriding. Overriding on the other hand is not supported with the include tag.

Returns: string
Arguments
Key Required Type Default Description

$view

true

string

none

The view you wish to render.

$data

false

array

null

The data to pass along to the view.

Twig
{{ view('example.module.test::example/view', {'foo': 'Bar'}) }}
parse

The parse function parses a string template.

Returns: string
Arguments
Key Required Type Default Description

$template

true

string

none

The template string to parse.

$data

false

array

null

The data to pass along to the view.

Twig
{{ parse("This is a template {{ foo }}", {"foo": "bar"}) }}
layout

The layout method checks for a theme layout and returns a default if it's not found.

Returns: string
Arguments
Key Required Type Default Description

$layout

true

string

none

The layout to look for.

$default

false

string

default

The default layout to fallback to.

Twig
{% extends layout("posts") %} // extends "theme::layotus/default" if not found

Presenters

Presenters help you organize view layer (presentation) logic for the objects. Logic in the presenter should only be for display purposes.

An important secondary purpose for presenters is to map the decorated object's API in a clean way for views. This is why the __get method has logic in it which you'll read about further down.

Note: All presentable objects including entry models and collections are automatically decorated with a presenter in the view layer.

Basic Usage

The \Robbo\Presenter\PresentableInterface interface tells the Streams Platform that the class is presentable and has a getPresenter method. By default getPresenter return an instance of \Anomaly\Streams\Platform\Support\Presenter or the nearest presenter to the class.

public function newPresenter()
{
    return new Presenter($this);
}
Presenter::getObject()

The getObject returns the decorated object instance.

Returns: mixed
Example
$presenter->getObject();
Twig
{{ presenter.getObject() }}
Pro Tip: This method is very useful when working with API objects in the view layer where the instance is generally required and not the presenter.
Presenter::__get()

The __get magic method will look for the following available methods in order from top to bottom and return the first matching scenario:

Check the presenter for a method where the method is the camel case of the {attribute_name}.
$presenter->display_name; // Returns: $presenter->displayName();

{{ presenter.display_name }} // Returns: {{ presenter.displayName() }}
Check the presenter for a getter method where the method is the camel case of get_{attribute_name}.
$presenter->display_name; // Returns: $presenter->getDisplayName();

{{ presenter.display_name }} // Returns: {{ presenter.getDisplayName() }}
Check the presenter for a getter method where the method is the camel case of is_{attribute_name}.
$presenter->enabled; // Returns: $presenter->isEnabled();

{{ presenter.enabled }} // Returns: {{ presenter.isEnabled() }}
Check the decorated object for a getter method where the method is the camel case of get_{attribute_name}.
$presenter->display_name; // Returns: $presenter->getObject()->getDisplayName();

{{ presenter.display_name }} // Returns: {{ presenter.getObject().getDisplayName() }}
Check the decorated object for a boolean getter method where the method is the camel case of is_{attribute_name}.
$presenter->enabled; // Returns: $presenter->getObject()->isEnabled();

{{ presenter.enabled }} // Returns: {{ presenter.getObject().isEnabled() }}
Check the decorated object for a method where the method is the camel case of the {attribute_name}.
$presenter->display_name; // Returns: $presenter->getObject()->displayName();

{{ presenter.display_name }} // Returns: {{ presenter.getObject().displayName() }}
Check the decorated object for a getter style hook where the hook is get_{attribute_name}.
$presenter->display_name; // Returns: $presenter->getObject()->call('get_display_name');

{{ presenter.display_name }} // Returns: {{ presenter.getObject().call('get_display_name') }}
Check the decorated object for a standard hook where the hook is {attribute_name}.
$presenter->display_name; // Returns: $presenter->getObject()->call('display_name');

{{ presenter.display_name }} // Returns: {{ presenter.getObject().call('display_name') }}
Return the decorated object's attribute named {attribute_name}.
$presenter->display_name; // Returns: $presenter->getObject()->display_name;

{{ presenter.display_name }} // Returns: {{ presenter.getObject().display_name }}
Returns: mixed
Arguments
Key Required Type Default Description

$name

true

string

none

The attribute name being attempted.

Example
$presenter->example; // Where example is not a property of $presenter
Twig
{{ presenter.example }}
Presenter::__call()

The __call magic method will try running the method on the decorated object. Remember the __call function is only invoked when the method does not exist on the presenter itself. So this effectively maps decorated object methods to the presenter.

Returns: mixed
Arguments
Key Required Type Default Description

$method

true

string

none

The method name.

$arguments

true

array

null

An array of method arguments passed.

Example
$presenter->exampleMethod(); // Returns: $presenter->getObject()->exampleMethod();
Twig
{{ presenter.exampleMethod() }}
Caution: Presenters globally block calls to delete, save, and update.
Manually Decorating Objects

You can manually decorate an object with the \Anomaly\Streams\Platform\Support\Decorator class.

Decorator::decorate()

The decorate method decorates the object. It can also decorate an entire collection of presentable items.

Returns: \Anomaly\Streams\Platform\Support\Presenter or \Illuminate\Database\Eloquent\Collection
Arguments
Key Required Type Default Description

$value

true

mixed

none

The presentable object or collection of presentable items.

Example
$decorated = $decorator->decorate($model);
Note: Non-presentable values will be returned as is.
Undecorating Values

The \Anomaly\Streams\Platform\Support\Decorator class can also be used for obtaining the original undecorated values.

Decorator::undecorate()

The undecorate reverses the decoration method's action. This is helpful for wrapping API methods for objects inside the view layer.

Returns: mixed
Arguments
Key Required Type Default Description

$value

true

mixed

none

The presenter or collection of presenters.

Example
$original = $decorator->undecorate($model);
Note: Non-presentable values will be returned as is.

Writing Presenters

Presenters are automatically generated for you when using the make stream command:

php artisan make:stream stream_slug example.module.test

You can also create your own addon presenter in most cases by transforming the model class name into it's corresponding presenter class name:

TestModel -> TestPresenter

In all other cases all you need to do is define your own newPresenter method on the presentable class:

<?php namespace Anomaly\ExampleModule\Test;

use Robbo\Presenter\PresentableInterface;
use Anomaly\Streams\Platform\Support\Presenter;

class TestObject implements PresenterInterface
{

    /**
     * Return the presenter.
     *
     * @return string
     */
    public function newPresenter()
    {
        return new Presenter($this);
    }
}

An example presenter looks like this:

<?php namespace Anomaly\ExampleModule\Test;

use Anomaly\Streams\Platform\Support\Presenter;

class TestPresenter extends Presenter
{

    /**
     * Return the full name.
     *
     * @return string
     */
    public function name()
    {
        return implode(' ', array_filter([$this->object->getFirstName(), $this->object->getLastName()]));
    }
}

Now you can use this method in your API:

$test->name();

Or view layer:

{{ test.name() }}

Core Concepts

This section will go over the core concepts of the Streams Platform so that you can identify, understand, and apply them in your development.

Service Container

The service container can be used exactly the same as described in Laravel documentation.

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.

Let's look at a simple example:

<?php namespace Anomaly\UsersModule\Http\Controller;

use Anomaly\Streams\Platform\Http\Controller\PublicController;
use Anomaly\UsersModule\User\Contract\UserRepositoryInterface;

class UsersController extends PublicController
{

    public function view(UserRepositoryInterface $users)
    {
        if (!$user = $users->findByUsername($this->route->getParameter('username'))) {
            abort(404);
        }

        return $this->view->make('anomaly.module.users::users/view', compact('user'));
    }
}

In this example, the UserController needs to retrieve a specific user from a data source. So, we will inject a service that is able to retrieve users. In this context, our UserRepositoryInterface to retrieve user information from the database. However, since the repository interface is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the UserRepository when testing our application.

A deep understanding of the service container is essential to building a large powerful application.

Bindings

This section will go over how to define a bindings in the service container.

Almost all of your service container bindings in PyroCMS will be registered within addon service providers, so most of these examples will demonstrate binding in that context.

For more information on registering bindings with native Laravel service providers please refer to Laravel documentation on Service Container > Binding.

The Bindings Property

Within an addon service provider you can register a binding by defining them in the $bindings array. Bindings should be defined in an Abstract => Instance format:

protected $bindings = [
    'login'                                                     => 'Anomaly\UsersModule\User\Login\LoginFormBuilder',
    'register'                                                  => 'Anomaly\UsersModule\User\Register\RegisterFormBuilder',
    'reset_password'                                            => 'Anomaly\UsersModule\User\Password\ResetPasswordFormBuilder',
    'forgot_password'                                           => 'Anomaly\UsersModule\User\Password\ForgotPasswordFormBuilder',
    'Illuminate\Auth\Middleware\Authenticate'                   => 'Anomaly\UsersModule\Http\Middleware\Authenticate',
    'Anomaly\Streams\Platform\Http\Middleware\Authenticate'     => 'Anomaly\UsersModule\Http\Middleware\Authenticate',
    'Anomaly\Streams\Platform\Model\Users\UsersUsersEntryModel' => 'Anomaly\UsersModule\User\UserModel',
    'Anomaly\Streams\Platform\Model\Users\UsersRolesEntryModel' => 'Anomaly\UsersModule\Role\RoleModel',
];

Note that we can also bind simple strings instead of interfaces. This will let us easily resolve the binding out of the container later.

Using the container directly

Within an addon service provider, you always have access to the container via the $this->app property. We can register also bindings using the bind method, passing the class or interface name that we wish to register along with a Closure that returns an instance of the class:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Note that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building.

An example of using this method within the context of an addon service provider might look like this:

public function register() {

    $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
}

Binding a Singleton

The singleton method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:

protected $singletons = [
    'Anomaly\UsersModule\User\Contract\UserRepositoryInterface'               => 'Anomaly\UsersModule\User\UserRepository',
    'Anomaly\UsersModule\Role\Contract\RoleRepositoryInterface'               => 'Anomaly\UsersModule\Role\RoleRepository',
    'Anomaly\UsersModule\Authenticator\Authenticator'                         => 'Anomaly\UsersModule\Authenticator\Authenticator',
];

You can also define them by access the container directly:

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Binding an existing object

You may also bind an existing object instance into the container using the instance method. The given instance will always be returned on subsequent calls into the container:

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\Api', $api);

Binding Primitives

Sometimes you may have a class that receives some injected classes, but also needs an injected primitive value such as an integer. You may easily use contextual binding to inject any value your class may need:

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

Binding Interfaces To Implementations

A very powerful feature of the service container is its ability to bind an interface to a given implementation. For example, let's assume we have an UserRepositoryInterface interface and a CustomUserRepository implementation. Once we have coded our CustomUserRepository implementation of this interface, we can register it with the service container like so:

$this->app->bind(
    'Anomaly\UsersModule\User\Contract\UserRepositoryInterface',
    'App\User\CustomUserRepository'
);

This statement tells the container that it should inject the CustomUserRepository when a class needs an implementation of UserRepositoryInterface. Now we can type-hint the UserRepositoryInterface interface in a constructor, or any other location where dependencies are injected by the service container:

<?php namespace Anomaly\UsersModule\Http\Controller;

use Anomaly\Streams\Platform\Http\Controller\PublicController;
use Anomaly\UsersModule\User\Contract\UserRepositoryInterface;

class UsersController extends PublicController
{

    public function view(UserRepositoryInterface $users)
    {
        if (!$user = $users->findByCustomMethod($this->route->getParameter('username'))) {
            abort(404);
        }

        return $this->view->make('anomaly.module.users::users/view', compact('user'));
    }
}

Contextual Bindings

Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, two controllers may depend on different implementations of the Illuminate\Contracts\Filesystem\Filesystem contract. You can leverage Laravel's contextual binding to accomplish this behavior:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
      ->needs(Filesystem::class)
      ->give(function () {
          return Storage::disk('local');
      });

$this->app->when(VideoController::class)
      ->needs(Filesystem::class)
      ->give(function () {
          return Storage::disk('s3');
      });

Service Providers

Service providers are the central place of all PyroCMS addon bootstrapping. Your own custom website or application, the Streams Platform, all addons, and all of Laravel's core services are bootstrapped via service providers.

But, what do we mean by "bootstrapped"? In general, we mean registering things, including registering service container bindings, event listeners, middleware, and even routes. Service providers are the central place to configure your application and addons.

PyroCMS uses primarily addon service providers to register things because Pyro has a modular addon-based infrastructure. However you can still use Laravel service providers all the same.

In this section you will learn how to write your own addon service providers and register various services with them.

Writing Service Providers

All addon service providers extend the Anomaly\Streams\Platform\Addon\AddonServiceProvider class. Addon service providers contain a number of properties to quickly define bindings, routes, and even views. They also contain the Laravel register and a boot methods.

Note: The register and boot method are both called with the service container and support method injection.

When creating an addon the service provider will automatically be created for you:

php artisan make:addon example.module.test

You can also create your own addon service providers by transforming the addon class name into it's corresponding service provider class name:

TestModule -> TestModuleServiceProvider

Here is what a generated addon service provider looks like:

<?php namespace Example\TestModule;

use Anomaly\Streams\Platform\Addon\AddonServiceProvider;

class TestModuleServiceProvider extends AddonServiceProvider
{

    /**
     * The addon plugins.
     *
     * @var array
     */
    protected $plugins = [];

    /**
     * The addon routes.
     *
     * @var array
     */    
    protected $routes = [];

    /**
     * The addon middleware.
     *
     * @var array
     */
    protected $middleware = [];

    /**
     * The addon event listeners.
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * The addon alias bindings.
     *
     * @var array
     */
    protected $aliases = [];

    /**
     * The addon simple bindings.
     *
     * @var array
     */
    protected $bindings = [];

    /**
     * Other addon service providers.
     *
     * @var array
     */
    protected $providers = [];

    /**
     * The addon singleton bindings.
     *
     * @var array
     */
    protected $singletons = [];

    /**
     * The addon view overrides.
     *
     * @var array
     */
    protected $overrides = [];

    /**
     * The addon mobile-only view overrides.
     *
     * @var array
     */
    protected $mobile = [];

    /**
     * Register the addon.
     *
     * @var void
     */
    public function register()
    {
    }

    /**
     * Boot the addon.
     *
     * @var void
     */
    public function map()
    {
    }
}
AddonServiceProvider::$plugins

The $plugins property let's you easily define plugins provided by the addon. This is helpful if you develop a module that has specific plugin functions to accomodate it's use.

Example
protected $plugins = [
    \Anomaly\UsersModule\UsersModulePlugin::class,
];
AddonServiceProvider::$routes

The routes property let's you quickly define basic addon routes. Routes defined here are very similar to the arguments you would typically pass Laravel's Router class:

Learn More: For more information on route definitions checkout the documentation on routing.
Example
protected $routes = [
    'login' => 'Anomaly\UsersModule\Http\Controller\[email protected]',
];
AddonServiceProvider::$middleware

The $middleware property let's you define middleware to push into the MiddlewareCollection. Middleware in this collection are ran for every request:

Example
protected $middleware = [
    \Anomaly\UsersModule\Http\Middleware\CheckSecurity::class
];
AddonServiceProvider::$listeners

The $listeners property let's you easily define event listeners. Event listeners are defined in an Event => (array)Listeners format.

Pro Tip: You can also dictate the listener's priority by including a priority value. Listeners are ran in order of highest to lowest priority.
Example
protected $listeners = [
    'Anomaly\UsersModule\User\Event\UserWasLoggedIn'                  => [
        'Anomaly\UsersModule\User\Listener\TouchLastLogin',
    ],
    'Anomaly\Streams\Platform\Application\Event\ApplicationHasLoaded' => [
        'Anomaly\UsersModule\User\Listener\TouchLastActivity' => -100,
    ],
];
AddonServiceProvider::$aliases

The $aliases property lets you quickly define alias bindings.

Example
protected $aliases = [
    'users' => \Anomaly\UsersModule\User\Contract\UserRepositoryInterface::class
];
AddonServiceProvider::$bindings

The $bindings property lets you quickly define simple bindings.

Example
protected $bindings = [
    'login' => 'Anomaly\UsersModule\User\Login\LoginFormBuilder',
];
AddonServiceProvider::$providers

Sometimes you might ship another package dependency with your addon. Or split up registration tasks between multiple service providers. The $providers property let's you do this.

Example
protected $providers = [
    \TeamTNT\Scout\TNTSearchScoutServiceProvider::class
];
AddonServiceProvider::$singletons

The $singletons let's you easily define singleton bindings.

Example
protected $singletons = [
    'Anomaly\UsersModule\User\Contract\UserRepositoryInterface' => 'Anomaly\UsersModule\User\UserRepository',
];
AddonServiceProvider::$overrides

The $overrides property allows you to define specific view override definitions for the view composer.

Example
protected $overrides = [
    'streams::form/partials/wrapper' => 'example.theme.test::overrides/field_wrapper',
];
AddonServiceProvider::$mobile

The $mobile property allows you to define specific mobile-only view override definitions for the view composer.

Example
protected $mobile = [
    'streams::form/partials/wrapper' => 'example.theme.test::mobile/field_wrapper',
];
AddonServiceProvider::register()

As mentioned previously, within the register method, you should only bind things into the service container. Typically, you should never attempt to register any event listeners, routes, or any other piece of functionality within the register method. Otherwise, you may accidentally use a service that is provided by a service provider which has not loaded yet.

In PyroCMS however there is a predictable loading order within the addon itself so you may inject any bindings defined in the properties for use. They have already been registered.

Let's take a look at a basic service provider. Within any of your service provider methods, you always have access to the $app property which provides access to the service container:

Returns: void or null
Example
<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}
AddonServiceProvider::boot()

So, what if we need to register a view composer within our service provider? This should be done within the boot method. This method is called after all other addon service providers have been registered, meaning you have access to all other services that have been registered by all other addons.

Returns: void
Example
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer('view', function () {
            //
        });
    }
}

Services

PyroCMS comes with a number of classes or services that perform a wide variety of helpful tasks when developing websites and applications.

Asset

The \Anomaly\Streams\Platform\Asset\Asset class built over the Assetic framework by Kris Wallsmith provides a fluent API for managing collections of assets.

It can be used both in the API:

app(\Anomaly\Streams\Platform\Asset\Asset::class)
    ->add('theme.css', 'theme::example/text.less')
    ->add('theme.css', 'theme::example/foo.less')
    ->path(); // Returns the path of the concatenated theme.css file.

And also in Twig:

{{ asset_add("theme.css", "theme::less/fonts/fonts.less") }}
{{ asset_add("theme.css", "theme::less/theme/theme.less") }}
{{ asset_add("build.css", "theme::scss/theme/theme.scss") }}

{{ asset_add("theme.js", "theme::js/libraries/jquery.min.js") }}
{{ asset_add("theme.js", "theme::js/libraries/tether.min.js") }}
{{ asset_add("theme.js", "theme::js/libraries/bootstrap.min.js") }}
{{ asset_add("theme.js", "theme::js/libraries/prism.min.js") }}
{{ asset_add("theme.js", "theme::js/libraries/mark.min.js") }}
{{ asset_add("theme.js", "theme::js/theme/*") }}

{{ asset_style("theme.css", ["min"]) }}
{{ asset_style("build.css", ["live"]) }}

{% for style in asset_styles("styles.css") %}
    {{ style|raw }}
{% endfor %}

{{ asset_script("theme.js") }}

Introduction

This section will introduce you to the Asset class and and how to use it.

Assets

An asset is a file with filterable content that can be loaded and dumped.

Collections

Collections are used to organize the assets you are working with. Assets in a collection can be combined or used individually.

Note: Collections are always named such that it reflects the desired output name.
$asset->add("collection.css", "theme::example.scss");
$asset->add("collection.js", "theme::example.js");
Filters

Filters are used to mutate the content of the assets.

Note: Filters can be applied to individual assets in a collection as well as the entire collection.
$asset->add("collection.css", "theme::example.scss", ["min", "live"]);
Available Filters
  • min: minifies content
  • less: parses LESS into CSS
  • styl: parses STYL into CSS
  • scss: parses SCSS into CSS
  • parse: parses content with Twig
  • coffee: compiles CoffeeScript into Javascript
  • embed: embeds image data in your stylesheets
  • live: refreshes content when LIVE_ASSETS is enabled
Automatic Filters

scss, less, styl, and coffee filters are are applied automatically to matching files.

You may wish to use files that use an alternate syntax like LESS for CSS or Coffee for Javascript. In most cases you do not need to manually add filters to compile these assets to relevant syntax for output. Simply add them along with your other assets.

Example

$asset
    ->add('theme.css', 'example::styles/example.css')
    ->add('theme.css', 'example::styles/example.less')
    ->add('theme.css', 'example::styles/example.scss')
    ->add('theme.css', 'example::styles/example.styl');

Twig

{{ asset_add('theme.css', 'example::styles/example.css') }}
{{ asset_add('theme.css', 'example::styles/example.less') }}
{{ asset_add('theme.css', 'example::styles/example.scss') }}
{{ asset_add('theme.css', 'example::styles/example.styl') }}
Path Hints

To avoid having to use full paths to your assets there are a number of path hints available. Hints are a namespace that prefixes the asset path.

"theme::js/initialize.js" // path-to-your-active-theme/resources/js/initialize.js

"anomaly.module.products::js/initialize.js" // path-to-products-module/resources/js/initialize.js
Available Path Hints

All paths are relative to your application's base path.

  • public: public/
  • node: node_modules/
  • asset: public/app/{app_reference}/
  • storage: storage/streams/{app_reference}/
  • download: public/app/{app_reference}/assets/downloads/
  • streams: vendor/anomaly/streams-platform/resources/
  • bower: bin/bower_components/
  • theme: {active_theme_path}/resources/
  • module: {active_module_path}/resources/
Note: Every single addon also registers a prefix for it's resources path like vendor.module.example
Registering Path Hints

Registering path hints is easy. Just inject the \Anomaly\Streams\Platform\Asset\Asset class into your service provider or function and use the addPath method:

$asset->addPath("foo", base_path("example/path"));

Now you can use that path:

{{ asset_add('foo::example/test.js') }}

Configuration

Configuration can be found in the Streams Platform under config/resources/assets.php. You can publish the Streams Platform with php artisan streams:publish to override configuration or use .env variables to control them as available.

Configuring additional path hints

You can configure additional path hints for the asset service with the streams::assets.paths configuration value:

'paths' => [
    'example' => 'some/local/path',
    's3'      => 'https://region.amazonaws.com/bucket'
]

You can now use these hints just like all the others:

{{ asset_add('theme.js', 'example::example/test.js') }}
Configuring additional output hints

Output hints help the system interpret the correct output file extension for an asset. For example the hint for .less is .css:

'hints' => [
    'css' => [
        'less',
        'scss',
        'sass',
        'styl',
    ],
    'js'  => [
        'coffee',
    ],
]
Configuring compilers

By default Pyro leverages PHP compilers. You can opt into other compilers if you like:

'filters' => [
    'less' => env('LESS_COMPILER', 'php'),
    'sass' => env('SASS_COMPILER', 'php'),
]
Forcing asset compiling

You can force assets with the live filter to compile for every page load by defining the LIVE_ASSETS flag. This is helpful during development to control assets that change often:

'live' => env('LIVE_ASSETS', false)

Use true to compile all live assets. Use admin to compile all live admin assets. Use public to compile all live public assets.

Basic Usage

Inject the Anomaly\Streams\Platform\Asset\Asset class into your own class or method to get started. You can also use the Streams Plugin to interact with the asset class.

Asset::add()

The add method let's you add a single asset or glob pattern of assets to a collection.

Returns: Anomaly\Streams\Platform\Asset\Asset
Arguments
Key Required Type Default Description

$collection

true

string

none

The collection to add the asset to.

$file

true

string

none

The file or glob pattern to add to the collection.

$filters

false

array

null

An array of filters to apply to the single asset or pattern.

Example
$asset->add('theme.css', 'theme::css/*', ['parse']);
Twig
{{ asset_add('theme.css', 'theme::css/*', ['parse']) }}
Asset::download()

The download method lets you cache a remote resource on your server. This might be an edge case scenario but it sure it handy when you need it!

Returns: string
Arguments
Key Required Type Default Description

$url

true

string

none

The url to add the remote asset.

$ttl

false

integer

3600

The number of seconds to cache the resource for.

$path

false

string

{host}/{filename}

The path in downloads to put the cached asset.

Example
$path = $asset->download('http://shakydomain.com/js/example.js', 60*60*24);

$asset->add('theme.js', $path);
Twig
{{ asset_add('theme.js', asset_download('http://shakydomain.com/js/example.js', 60*60*24)) }}
Asset::inline()

The inline method returns the contents of a collection for including inline or dumping from a controller response.

Returns: string
Arguments
Key Required Type Default Description

$collection

true

string

none

The collection return contents of.

$filters

false

array

null

The filters to apply to the collection.

Example
echo $asset->inline('theme.js', ['min']);
Twig
<script type="text/javascript">
    {{ asset_inline('theme.js', ['min']) }}
</script>
Asset::url()

The url method returns the full URL to the collection output file.

Returns: string
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the URL for.

$filters

false

array

null

An array of filters to apply to the entire collection.

$parameters

false

array

null

Query string parameters to append to the URL.

$secure

false

boolean

true or false depending on if current request is HTTP/HTTPS.

Whether to return HTTP or secure HTTPS URL.

Example
$asset->url('theme.js', ['min']);
Twig
<script type="text/javascript" src="{{ asset_url('theme.js', ['min']) }}"></script>
Asset::path()

The path method returns the URL path to the collection output file.

Returns: string
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the URL for.

$filters

false

array

null

An array of filters to apply to the entire collection.

Example
$asset->path('theme.js');
Twig
<script type="text/javascript" src="{{ asset_path('theme.js') }}"></script>
Asset::asset()

The asset method returns the path with the root prefix included. This is helpful if you are installed and serving from a directory and not a virtual host.

Returns: string
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the URL for.

$filters

false

array

null

An array of filters to apply to the entire collection.

Example
$asset->asset('theme.js');
Twig
<script type="text/javascript" src="{{ asset_asset('theme.js') }}"></script>
Asset::script()

The script method returns a <script> tag including the collection output file.

Returns:
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the tag for.

$filters

false

array

null

An array of filters to apply to the entire collection.

$attributes

false

array

null

A key=>value array of tag attributes.

Example
$asset->script('theme.js', ['parse']);
Twig
{{ asset_script('theme.js', ['parse']) }}

You can also pass the URL of an arbitrary asset to include it as well.

{{ asset_script('public::example/test.js') }}
Asset::style()

The style method returns a <link> tag linking the collection output file.

Returns:
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the tag for.

$filters

false

array

null

An array of filters to apply to the entire collection.

$attributes

false

array

null

A key=>value array of tag attributes.

Example
$asset->style('theme.css', ['min'], ['media' => 'print']);
Twig
{{ asset_style('theme.css', ['min'], ['media' => 'print']) }}

You can also pass the URL of an arbitrary asset to include it as well.

{{ asset_style('public::example/test.css') }}
Asset::scripts()

The scripts method return an array of <script> tags for each asset added to the collection.

Returns: array
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the asset tags for.

$filters

false

array

null

An array of filters to apply to each asset.

$attributes

false

array

null

A key=>value array of tag attributes.

Example
$asset->scripts('scripts.js');
Twig
{% for script in asset_scripts('scripts.js') %}
    {{ script|raw }}
{% endfor %}
Heads Up: Addons leverage the scripts.js collection for per page inclusion of assets. Be sure to include it in your theme!
Asset::styles()

The styles method returns an array of <link> tags for each asset in the collection.

Returns: array
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return asset tags for.

$filters

false

array

null

An array of filters to apply to each asset.

$attributes

false

array

null

A key=>value array of tag attributes.

Example
$asset->styles('theme.css', ['min']);
Twig
{% for style in styles('theme.css', ['min']) %}
    {{ style|raw }}
{% endfor %}
Heads Up: Addons leverage the styles.css collection for per page inclusion of assets. Be sure to include it in your theme!
Asset::paths()

The path method returns an array of URL paths for each asset in the collection.

Returns: array
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the asset paths for.

$filters

false

array

null

An array of filters to apply to each asset.

Example
$asset->paths('styles.css');
Twig
{% for path in asset_paths('styles.css') %}
    {{ html_style(path) }}
{% endfor %}
Asset::urls()

The urls method returns an array of URLs for each asset in the collection.

Returns: array
Arguments
Key Required Type Default Description

$collection

true

string

null

The collection return the URL asset paths for.

$filters

false

array

null

An array of filters to apply to the each asset.

$parameters

false

array

null

Query string parameters to append to all URLs.

$secure

false

boolean

true or false depending on if current request is HTTP/HTTPS.

Whether to return HTTP or secure HTTPS URLs.

Example
$asset->urls('styles.css');
Twig
{% for url in asset_urls('styles.css') %}
    {{ html_style(url) }}
{% endfor %}
Asset::addPath()

The addPath let's you register your own path hint. This is generally done during the boot process. You can hint local path and remote paths.

Returns: Anomaly\Streams\Platform\Asset\Asset
Arguments
Key Required Type Default Description

$namespace

true

string

none

The path hint.

$path

true

string

none

The path the hint is refercing.

Example
$asset
    ->addPath('example', 'some/local/path')
    ->addPath('s3', 'https://region.amazonaws.com/bucket');

$asset->style('s3::styles.css');

Artisan Commands

The asset service comes with Artisan support.

Clearing Asset Cache

You can use the asset:clear command to clear the asset cache files.

php artisan asset:clear

Authorization

Authorization in PyroCMS work exactly the same as authorization in Laravel.

Important: All authorization services and permissions are bound to the Users Module by default.

Artisan

The Artisan Console in PyroCMS work exactly the same as the Artisan Console in Laravel.

We have however, added a few cool things to it!

Specifying an Application

PyroCMS supports multiples applications running from a single PyroCMS installation. Use the --app={reference} flag for specifying the application when running artisan commands.

php artisan asset:clear --app=test_app

Callbacks

A callback is a type of event in PyroCMS. Callbacks differ from events in that the scope is relative to an instance of an object. Whereas events are broadcast across the entire application.

Callbacks consist of a trigger and the callback.

Introduction

This section will introduce you to callbacks and how they work.

Triggers

The trigger is what causes the callback to fire.

Callbacks

The callback is the callable string or Closure that is fired when the trigger is.. triggered.

Listeners

Listeners are the same as callbacks but for one major difference; they apply to all instances of the class. Whereas standard callbacks only apply to the instance they are registered on.

Basic Usage

Use the \Anomaly\Streams\Platform\Traits\FiresCallbacks trait in your class to get started.

FiresCallbacks::on()

The on method registers a simple callback.

Returns: $this
Arguments
Key Required Type Default Description

$trigger

true

string

none

The trigger for the callback.

$callback

true

string|Closure

none

The callback logic or callable string.

Example
// Example of using a callable string
$callable->on('before_starting', 'App\[email protected]');

$callable->beforeStarting();

// Example of using a Closure
$callable->on('querying', function(Builder $query) {
    $query->where('modifier_id', $this->example->getId());
});

$callable->sayHello('Ryan!'); // Hello Ryan!
Note: Callbacks are called with the Service Container so all dependencies are resolved automatically.
FiresCallbacks::listen()

The listen method registers callbacks very similar to on except the callback applies to all instances of the class.

Returns: $this
Arguments
Key Required Type Default Description

$trigger

true

string

none

The trigger for the callback.

$callback

true

string|Closure

none

The callback logic or callable string.

Example
// Example of using a callable string
$callable->on('before_starting', 'App\[email protected]');

$callable->beforeStarting();

// Example of using a Closure
$callable->on('say_hello', function($name) {
    return 'Hello ' . $name;
});

$callable->sayHello('Ryan!'); // Hello Ryan!
FiresCallbacks::fire()

The fire method does just as it's name suggests. It fires a callback.

Returns: mixed
Arguments
Key Required Type Default Description

$trigger

true

string

none

The trigger for the callback to fire.

$parameters

false

array

null

Parameters to pass to the callback.

Example
$callable->fire('querying', compact('builder', 'query'));
FiresCallbacks::hasCallback()

The hasCallback method returns whether or not a callback exists on the instance.

Returns: boolean
Arguments
Key Required Type Default Description

$trigger

true

string

none

The trigger for the callback to existance of.

Example
$callable->hasCallback('querying');
FiresCallbacks::hasListener()

The hasListener method returns whether or not the class has a listener.

Returns: boolean
Arguments
Key Required Type Default Description

$trigger

true

string

none

The trigger for the listener to existance of.

Example
$this->hasListener('querying');
Method Handlers

Method handlers are specific methods in a class that are named after a callback trigger. If the trigger is before_querying the handler method will be onBeforeQuerying.

// First register the callback.
$callable->on('querying', function(Builder $query) {
    $query->where('modifier_id', $this->example->getId());
});

// Now fire using the handler method.
$callable->onQuerying(compact('builder'));
Self Handling Callbacks

If using a callable string like Example\[email protected] without an @method then @handle will be assumed.

$callable->on('querying', \Example\Test::class); // Assumes 'Example\[email protected]'

Collections

Collections in PyroCMS work exactly the same as collections in Laravel.

Basic Usage

PyroCMS comes with it's own \Anomaly\Streams\Platform\Support\Collection class that extends Laravel's base collection.

Collection::pad()

The pad method pads the items to assure the item array is a certain size.

Returns: $this
Arguments
Key Required Type Default Description

$size

true

integer

none

The size to pad the items to.

$value

false

mixed

null

The value to use for the pad items.

Example
$collection->pad(10, 'No item.');
Twig
{% for item in collection.pad(10, 'No item.') %}
    {{ item }}
{% endfor %}
Collection::skip()

The skip method is a shortcut alias for slice.

Returns: Collection
Arguments
Key Required Type Default Description

$offset

true

integer

none

The number of items to skip.

Example
$collection->skip(10);
Twig
{% for item in collection.skip(10) %}
    {{ item }}
{% endfor %}
Collection::__get()

The __get method has been mapped to try for an item otherwise calls the camel cased attribute method name.

Returns: mixed
Arguments
Key Required Type Default Description

$name

true

string

none

The index of the item to get or snake_case of the method to call.

Example
// A collection of people is keyed by snake case first name
$people->ryan_thompson->favorite_color;
Twig
{{ people.ryan_thompson.favorite_color }}

Config

Configuration in PyroCMS work exactly the same as configuration in Laravel.

Overriding Configuration Files

The Streams Platform and usually addons contain their own configuration. So you might ask; How can I edit those configuration files without modifying the addon or package? The answer is that you publish them first.

Publishing streams configuration

In order to configure the Streams Platform without modifying core files you will need to publish the Streams Platform with the following command:

 php artisan streams:publish

You can then find the Streams Platform configuration files in resources/{application}/streams/config.

Publishing addon configuration

In order to configure addons without modifying core files you will need to publish the addon with the following command:

 php artisan addon:publish vendor.type.slug

You can then find the addon configuration files in resources/{application}/{vendor}/{slug}-{type}/config.

Currency

The currency service is a simple class that helps work with money formats. The currency service uses the streams::currencies configuration.

Basic Usage

You can use the currency class by including the \Anomaly\Streams\Platform\Support\Currency class.

Currency::format()

The format method returns a formatted currency string.

Returns: string
Arguments
Key Required Type Default Description

$number

true

float|integer

none

The number to format.

$currency

false

string

The configured default "streams::currencies.default"

The currency code to format for.

Example
$currency->format(15000.015); // $1,500.01

$currency->format(15000.015, 'RUB'); // ₽1,500.01
Twig
{{ currency_format(15000.015) }}

{{ currency_format(15000.015, 'RUB') }}
Currency::normalize()

The normalize method returns the number as a formatted float. This is important because it rounds values down as currency should be.

Returns: float
Arguments
Key Required Type Default Description

$number

true

float|integer

none

The number to format.

$currency

false

string

The configured default "streams::currencies.default"

The currency code to format for.

Example
$currency->normalize(15000.015); // 1500.01
Twig
{{ currency_normalize(15000.015) }}
Currency::symbol()

The symbol method returns the symbol for a currency.

Returns: string
Arguments
Key Required Type Default Description

$currency

false

string

The configured default "streams::currencies.default"

The currency code to format for.

Example
$currency->symbol(); // $

$currency->symbol('RUB'); // ₽
Twig
{{ currency_symbol() }}

{{ currency_symbol('RUB') }}

Evaluator

The evaluator service is a simple class that recursively resolves values from a mixed target.

Basic Usage

You can evaluate your values by using the \Anomaly\Streams\Platform\Support\Evaluator class.

Evaluator::evaluate()

The evaluate method evaluates a mixed value.

Returns: mixed
Arguments
Key Required Type Default Description

$target

true

mixed

none

The value to evaluate.

$arguments

false

array

null

The arguments to pass to the value resolvers.

Example
$entry = new Person(['name' =>'Ryan']);

$evaluator->evaluate('{entry.name}', compact('entry')); // Ryan

$evaluator->evaluate(
    function($entry) {
        return $entry->name;
    },
    compact('entry')
); // Ryan

Filesystem

Filesystem services in PyroCMS work exactly the same as the filesystem in Laravel.

The Files module integrates seamlessly into both Laravel's Filesystem and the included Flysystem package from https://thephpleague.com/.

Learn More: Please refer to the Files Module documentation for more information.

Hooks

A hook is a method of directly adding functionality to a class from the outside and without having to extend it or directly modify it.

Introduction

This section will help you get started with hooks.

Hooks

A hook is similar to a callback trigger. A hook applies to any instance of a class and any parent that inherits that class.

Bindings

By binding a hook you can change the context of $this so that the hook behaves like an actual method of the class. Where $this refers to the class the hook is being run on and not where the hook is being defined (like normal Closure behavior).

Basic Usage

Use the \Anomaly\Streams\Platform\Traits\Hookable trait in your class to get started.

Hookable::hook()

The hookable method let's you register a hook on a hookable class.

Returns: $this
Arguments
Key Required Type Default Description

$hook

true

string

none

The hook name.

$callback

true

string|Closure

none

The callback logic or callable string.

Example
$hookable->hook(
    'avatar',
    function ($email) {
        return 'https://www.gravatar.com/avatar/' . md5($email);
    }
);

$hookable->avatar('[email protected]');
Hookable::bind()

The bind method is very similar to hook but the callback is available for all instances of the class as well as any parents of the class.

Returns: $this
Arguments
Key Required Type Default Description

$hook

true

string

none

The hook name.

$callback

true

string|Closure

none

The callback logic or callable string.

Example
$hookable->bind(
    'customer',
    function () {

        /* @var UserModel $this */
        return $this->hasOne(CustomerModel::class, 'user_id');
    }
);

$hookable->bind(
    'get_customer',
    function () {

        /* @var UserModel $this */
        return $this->customer()->first();
    }
);
Hookable::call()

The call method fires the hook.

Returns: mixed
Arguments
Key Required Type Default Description

$hook

true

string

none

The hook to call.

$parameters

false

array

null

Parameters to pass to the callback.

Example
$hookable->call('get_customer')->billing_address;
Twig
{{ user().call('get_customer').billing_address }}
Hookable::hasHook()

The hasHook method returns whether a hook exists or not for the object.

Returns: boolean
Arguments
Key Required Type Default Description

$hook

true

string

none

The hook name to check existance of.

Example
$hookable->hasHook('get_customer');
Method Handlers

Not always, but generally classes in the Streams Platform that use the Hookable trait will map the __call method through hooks.

What this means is that hooks will be checked for when the __call method is triggered.

If the hook is get_customer the method will be getCustomer.

// First bind the hook.
$hookable->bind(
    'get_customer',
    function () {

        /* @var UserModel $this */
        return $this->customer()->first();
    }
);

// Now fire using the handler method.
$hookable->getCustomer()->billing_address;
Getter Behavior

Not always, but generally classes in the Streams Platform that use the Hookable trait will map the __get method through hooks.

What this means is that hooks prefixed with get_ will be checked for when the __get method is triggered.

// Register the hook
$hookable->bind(
    'get_customer',
    function () {

        /* @var UserModel $this */
        return $this->customer()->first();
    }
);

// Call the hook with method handler.
$hookable->customer->billing_address;

This method is very helpful in the view layer:

{{ user().customer.billing_address }} 

Image

The image service provides powerful image manipulation and management with zero setup. The Image class is built over the Intervention Image framework by Oliver Vogel.

<?php namespace Anomaly\Streams\Platform\Image;

use Anomaly\Streams\Platform\Image\Image;

class ImageController
{

    public function thumb(Image $image)
    {
        $image
            ->make('theme::users/avatar.jpg')
            ->fit(100, 100)
            ->quality(60)
            ->data();
    }
}

An example in Twig might look like this:

{{ image('theme::users/avatar.jpg').fit(100, 100).quality(60).url() }}

Introduction

This section will introduce you to the Image class and it's components.

Sources

The source for making an image instance can be nearly anything. We'll explore this more later in the make method.

Alterations

An alteration method modifies the image. To apply alterations to an image simply call the method on the image instance. Examples of an alteration might be blur or fit.

Combining Alterations

Alterations as well as any other method but output methods can be chained together:

$image
    ->make('theme::img/logo.jpg')
    ->fit(100, 100)
    ->brightness(15)
    ->greyscale()
    ->class('img-rounded');

You can enjoy the same fluent API in Twig:

{{ image('theme::img/logo.jpg')
    .fit(100, 100)
    .brightness(15)
    .greyscale()
    .class('img-rounded')|raw }}
Path Hints

To avoid having to use full paths to your images there are a number of path hints available. Hints are a namespace that prefixes the image path.

"theme::img/logo.jpg" // path-to-your-active-theme/resources/img/logo.jpg

"anomaly.module.products::img/no-image.jpg" // path-to-products-module/resources/img/no-image.jpg
Available Path Hints

All paths are relative to your application's base path.

  • public: public/
  • node: node_modules/
  • asset: public/app/{app_reference}/
  • storage: storage/streams/{app_reference}/
  • download: public/app/{app_reference}/assets/downloads/
  • streams: vendor/anomaly/streams-platform/resources/
  • bower: bin/bower_components/
  • theme: {active_theme_path}/resources/
  • module: {active_module_path}/resources/
Note: Every single addon also registers a prefix for it's resources path like vendor.module.example
Registering Path Hints

Registering path hints is easy. Just inject the \Anomaly\Streams\Platform\Image\Image class into your service provider or function and use the addPath method:

$image->addPath("foo", base_path("example/path"));

Now you can use that path:

{{ image('foo::example/image.png') }}

Basic Usage

To get started simply include the \Anomaly\Streams\Platform\Image\Image class in your own class or method.

Image::make()

The make method is the entry point to the Image class. It returns a unique instance of the image class ready for alteration and output.

Returns: Anomaly\Streams\Platform\Image\Image
Arguments
Key Required Type Default Description

$source

true

mixed

none

The source to make the image from.

$output

false

string

url

The output method. Any valid output method name can be used.

Example
$image = $image->make('theme::img/logo.jpg', 'path');
Twig
// Set output to tag
{{ image('theme::img/logo.jpg') }}

// Set output to tag
{{ img('theme::img/logo.jpg') }}

// Set output to URL
{{ image_url('theme::img/logo.jpg') }}

// Set output to path
{{ image_path('theme::img/logo.jpg') }}
Pro Tip: The input method can always be overriden by calling the output method manually. The initial output setting only applies to the output method and __toString.
Image::output()

The output method returns the output as defined during the make call. This method is typically triggered from __toString.

Returns: mixed
Example
$image = $image->make('theme::img/logo.jpg', 'path');

$image->output(); // the image path
Twig
// Set output to tag
{{ image_url('theme::img/logo.jpg').output() }}

// Same output because of __toString
{{ image_url('theme::img/logo.jpg') }}

// Also same output.
{{ image('theme::img/logo.jpg').url() }}
Image::rename()

The rename method renames the output file. Generally images will retain their original name unless modified in which the file names are hashed by default.

Returns: Anomaly\Streams\Platform\Image\Image
Arguments
Key Required Type Default Description

$filename

false

string

The name of the source image.

The name / path of the desired output image.

Example
$image->rename('example.jpg');
Image::path()

The path method returns the path for the cached image.

Returns: string
Example
$image->path();
Twig
{{ image('theme::img/logo.jpg').path() }}
Image::url()

The url method returns the URL for the cached image.

Returns: string
Arguments
Key Required Type Default Description

$parameters

false

array

null

The query string parameters to append to the URL.

$secure

false

boolean

true or false depending on if current request is HTTP/HTTPS.

Whether to return HTTP or secure HTTPS URL.

Example
$image->url();
Twig
{{ image('theme::img/logo.jpg').url() }}
Image::image()

The image method returns an <image> tag referencing the cached image path.

Returns: string
Arguments
Key Required Type Default Description

$alt

false

string

null

The image alt tag.

$attributes

false

array

null

A key=>value array of tag attributes.

Example
$image->image('Logo', ['class' => 'image-rounded']);
Twig
// Inferred example.
{{ image('theme::img/logo.jpg') }}
Image::img()

The img method is an alias for image.

Returns: string
Image::data()

The data method returns the encoded image data.

Returns: string
Example
$image->data();
Image::srcsets()

The srcset method let's you define the srcset HTML5 attribute.

Returns: $this
Arguments
Key Required Type Default Description

$srcsets

true

array

none

An array of Descriptor => Alterations per srcset.

Example
$image->srcsets(
    [
        "1x" => [
            "resize"  => 400,
            "quality" => 60
        ],
        "2x" => [
            "resize"  => 800,
            "quality" => 90
        ],
        "640w" => [
            "resize"  => 800,
            "quality" => 90
        ]
    ]
);

// Output
$image->img();
Twig
{% set example = image('theme::img/logo.jpg').srcsets(
    {
        "1x": {
            "resize": 400,
            "quality": 60
        },
        "2x": {
            "resize": 800,
            "quality": 90
        },
        "640w": {
            "resize": 800,
            "quality": 90
        }
    }
) %}

// Output
{{ example.img|raw }}
Image::srcset()

The srcset returns the HTML5 srcset attribute value.

Returns: string
Example
$image->srcset();
Twig
{% set example = image('theme::img/logo.jpg').srcsets(
    {
        "1x": {
            "resize": 400,
            "quality": 60
        },
        "2x": {
            "resize": 800,
            "quality": 90
        },
        "640w": {
            "resize": 800,
            "quality": 90
        }
    }
) %}

<img src="{{ example.path }}" srcset="{{ example.srcset }}">
Image::sources()

The sources method allows you to set the sources for the HTML5 picture element.

Returns: $this
Arguments
Key Required Type Default Description

$sources

true

array

none

An array of Media => Alterations sources.

$merge

false

boolean

false

If true existing alterations will be merged into source alterations.

Example
$image->sources(
    [
        "(min-width: 600px)" => [
            "resize"  => 400,
            "quality" => 60
        ],
        "(min-width: 1600px)" => [
            "resize"  => 800,
            "quality" => 90
        ],
        "fallback" => [
            "resize"  => 1800
        ]
    ]
);

Twig
{{ image('theme::img/logo.jpg').sources(
    {
        "(min-width: 600px)": {
            "resize": 400,
            "quality": 60
        },
        "(min-width: 1600px)": {
            "resize": 800,
            "quality": 90
        },
        "fallback": {
            "resize": 1800
        }
    }
) }}
Image::picture()

The picture method returns the HTML5 picture element.

Returns: string
Arguments
Key Required Type Default Description

$attributes

false

array

none

An array of HTML tag attributes to include.

Example
$image->picture(['class' => 'example']);
Twig
{{ image('theme::img/logo.jpg').sources(
    {
        "(min-width: 600px)": {
            "resize": 400,
            "quality": 60
        },
        "(min-width: 1600px)": {
            "resize": 800,
            "quality": 90
        },
        "fallback": {
            "resize": 1800
        }
    }
).picture()|raw }}
Image::quality()

The quality method adjusts the quality of the output image.

Returns: $this
Arguments
Key Required Type Default Description

$quality

true

integer

none

The quality of the output image.

Example
$image->quality(60);
Twig
{{ image('theme::img/logo.jpg').quality(60) }}
Image::width()

The width method set's the HTML width attribute.

Returns: $this
Arguments
Key Required Type Default Description

$width

false

integer

The actual width of the image.

The value of the width attribute.

Example
$image->width(100);
Twig
{{ image('theme::img/logo.jpg').width(100) }}
Image::height()

The height method set's the HTML height attribute.

Returns: $this
Arguments
Key Required Type Default Description

$height

false

integer

The actual height of the image.

The value of the height attribute.

Example
$image->height(100);
Twig
{{ image('theme::img/logo.jpg').height(100) }}
Image::attr()

The attr method sets an HTML attribute for the image tag output.

Returns: $this
Arguments
Key Required Type Default Description

$attribute

true

string

none

The attribute name.

$value

true

string

none

The attribute value.

Example
$image->attr('data-toggle', 'example');
Twig
{{ image('theme::img/logo.jpg').attr('data-toggle', 'example') }}
Macros

Macros are stored procedures that can apply a single or multiple alterations to an image at once.

Creating Macros

Macros are stored in the streams::images.macros configuration. You can publish stream configuration with Artisan:

php artisan streams:publish

Macros can be set with an array just like srcset or picture sources:

"macros" => [
    "example" => [
        "resize"  => 800,
    "quality" => 90,
]
]

You can also define a macro as a Closure that accepts an Image $image argument. Closure macros are called from Laravel`s service container and as such, support method injection.

"macros" => [
    "pink" => function(\Anomaly\Streams\Platform\Image\Image $image) {
        $image->colorize(100, 0, 100);
    }
]
Image::macro()

The macro method runs a macro on the image.

Returns: $this
Arguments
Key Required Type Default Description

$macro

true

string

none

The name of the macro to run.

Example
$image->macro("thumb")->img();

$image
	->macro("thumb")
	->macro("desaturate")
	->macro("responsive") // Set's common srcsets maybe?
	->img();
Twig
{{ image('theme::img/logo.jpg').macro("thumb")|raw }}

{{ image('theme::img/logo.jpg')
	.macro("thumb")
	.macro("desaturate")
	.macro("responsive")|raw }}

Localization

Localization in PyroCMS works exactly the same as localization in Laravel.

Overriding Language Files

You must publish the Streams Platform or the addon in order to override it's language files. After publishing them you can then simply modify the files as needed in `resources/{application}/*

Publishing streams language files

In order to configure the Streams Platform without modifying core files you will need to publish the Streams Platform with the following command:

php artisan streams:publish

You can then find the Streams Platform configuration files in resources/{application}/streams/lang.

Publishing addon language files

In order to configure addons without modifying core files you will need to publish the addon with the following command:

php artisan addon:publish vendor.type.slug

You can then find the addon configuration files in resources/{application}/{vendor}/{slug}-{type}/config.

Parser

The Parser class is a simple service that parses data into a string. The parser leverages the (https://packagist.org/packages/nicmart/string-template) package.

Basic Usage

Include the Anomaly\Streams\Platform\Support\Parser class in your code to get started.

Parser::parse()

The parse method recursively parses the value with given data.

Returns: string
Arguments
Key Required Type Default Description

$target

true

mixed

none

The string or array of strings.

$data

false

array

none

An array of data to parse into the $target.

Example
$parser->parse('Hello {user.first_name} {user.last_name}!', ['user' => Auth::user()]);

Resolver

The Streams Platform provides a powerful value handler pattern that let's you defer the value or something to a handler. The resolver service makes it easy to resolve the value from such handlers.

The resolver is usually used in other parts of the system so it's helpful to understand how it works even though you may not use it directly.

Introduction

This section will show you what a resolver is and how to use it.

Handlers

Handlers is a generic term for a class that handles the value for something.

Where a typical attribute in an array might look like:

$array = [
    'example' => 'Test',
];

A value handler might look like this:

$array = [
    'example' => 'Example\[email protected]',
];

A value can also define a self handling handler:

$array = [
    'example' => Example\TestHandler::class, // Assumes 'Example\[email protected]'
];

Basic Usage

To start resolving values in your class you need to include the \Anomaly\Streams\Platform\Support\Resolver class.

Resolver::resolve()

The resolve method recursively resolves values within the target value. The target is called through the Service Container and supports class and method injection.

Returns: mixed
Arguments
Key Required Type Default Description

$target

true

string

none

The value handler.

$arguments

false

array

null

The arguments to pass to the handler.

$options

false

array

null

Options for the resolver.

Example
$resolver->resolve('Example\[email protected]', compact('entry'));
Available Options
  • method - The handler method when no method is defined. Defaults to handle.

String

The string service in the Streams Platform extends Laravel's \Illuminate\Support\Str class.

Basic Usage

To use the Streams Platform string manipulation first include the \Anomaly\Streams\Platform\Support\Str class.

Str::humanize()

The humanize method humanizes slug type strings.

Returns: string
Arguments
Key Required Type Default Description

$value

true

string

none

The value to humanize.

$separator

false

string

_

The slug separator used. This can help prevent breaking hyphenated words.

Example
$str->humanize('default_table_name_example'); // default table name example

// Humanize is commonly used with ucwords.
ucwords($str->humanize('default_page')); // Default Page
Twig
{{ str_humanize('default_table_name_example') }} // default table name example

// Humanize is commonly used with ucwords.
{{ ucwords(str_humanize('default_page')) }} // Default Page
Str::truncate()

The truncate is identical to Laravel's limit method except that it preserves words.

Returns: string
Arguments
Key Required Type Default Description

$value

true

string

none

The string value to truncate.

$limit

false

integer

100

The length to limit the value by.

$end

false

string

...

The ending for the string only if truncated.

Example
$str->truncate('The CMS built for everyone.', 10); // "The CMS..."
Twig
{{ str_truncate('The CMS built for everyone.', 10) }} // "The CMS..."
Str::linkify()

The linkify method wraps URLs in link tags.

Returns: string
Arguments
Key Required Type Default Description

$text

true

string

none

The text to linkify.

Example
$str->linkify('Checkout http://google.com!'); // Checkout <a href="http://google.com">http://google.com</a>!
Twig
{{ str_linkify('Checkout http://google.com!') }} // Checkout <a href="http://google.com">http://google.com</a>!

{{ 'Checkout http://google.com!'|str_linkify }} // Checkout <a href="http://google.com">http://google.com</a>!

Valuation

The value service is a powerful class that determines the value of a target based on a predictable procedure of processing.

While you may not use this service on your own much it's important to understand as it's used heavily in the Streams Platform specifically where UI is concerned.

Basic Usage

To get started you will need to include the \Anomaly\Streams\Platform\Support\Value class in your own.

Value::make()

The make method makes the model value from the target value definition. Below are the steps taken in order from first to last:

  1. Checks for view in the parameters and returns it with the entry if found.
  2. Checks for template in the parameters and parses it with theentry` if found.
  3. Checks if the entry is an instance of EntryInterface and has a field field with slug value.
    • If the value is a field it returns the field value.
    • If the value is a relation it returns the relations title_column.
  4. Decorate the entry.
  5. Checks if the value is like {term}.* and renders the string like: {{ {value}|raw }}
  6. Evaluates the value with \Anomaly\Streams\Platform\Support\Evaluator.
  7. If the entry is Arrayable then run toArray on it.
  8. Wraps the value in the wrapper parameter. By default this is simply {value}. Note the value can be an array here
  9. If the value is a string. Parse it with the entry again.
  10. If the value is *.*.*::* then try translating it.
  11. If the value is parsable then try parsing it.

As you can see this flow and built in manipulation can allow for very powerful values with only an array. Compound this with resolvers and evaluators and you can start deferring logic for values or parts of the value to closures and handlers.. pretty cool right?

Returns: mixed
Arguments
Key Required Type Default Description

$paramters

true

string|array

none

The value definition. If a string the parameters become the value string.

$entry

true

string|array

none

The subject model.

$term

false

string

entry

The term to use in valuation strings.

$payload

false

array

null

Additional payload to pass along during the process.

Example
// A simple example
$value->make('name', $entry); // Ryan Thompson

// A more complex example
$value = [
    'wrapper'     => '
            <strong>{value.file}</strong>
            <br>
            <small class="text-muted">{value.disk}://{value.folder}/{value.file}</small>
            <br>
            <span>{value.size} {value.keywords}</span>',
    'value'       => [
        'file'     => 'entry.name',
        'folder'   => 'entry.folder.slug',
        'keywords' => 'entry.keywords.labels|join',
        'disk'     => 'entry.folder.disk.slug',
        'size'     => 'entry.size_label',
    ],
];

$value->make($value, compact('entry'));

UI

The Streams Platform comes with a number of UI builders that help you manipulate the user interface. Working with and writing builders will incorporate know-how from this entire documentation and then some. Let's dig in!

Breadcrumbs

Breadcrumbs in general are automated. However they can be modified, disabled, and managed manually too.

Basic Usage

To add manage breadcrumbs for your own module you must first include the \Anomaly\Streams\Platform\Ui\Breadcrumb\BreadcrumbCollection class.

BreadcrumbCollection::add()

The add method adds a breadcrumb to the end of the collection.

Returns: \Anomaly\Streams\Platform\Ui\Breadcrumb\BreadcrumbCollection
Arguments
Key Required Type Default Description

$key

true

string

none

The breadcrumb key name. Translation keys are supported.

$url

true

string

none

The URL or path for the breadcrumb.

Example
$breadcrumbs->add('anomaly.module.products::breadcrumb.products', '/products');
Note: Breadcrumbs are available as a property in controllers by default.
<?php namespace Anomaly\ProductsModule\Http\Controller;

use Anomaly\ProductsModule\Category\Contract\CategoryRepositoryInterface;
use Anomaly\Streams\Platform\Http\Controller\PublicController;

class CategoriesController extends PublicController
{

    /**
     * View products within a category.
     *
     * @param CategoryRepositoryInterface $categories
     * @param UrlGenerator                $url
     * @return \Illuminate\Contracts\View\View
     */
    public function view(CategoryRepositoryInterface $categories, UrlGenerator $url)
    {
        $category = $categories->findByPath($this->route->getParameter('path'));

        $this->breadcrumbs->add(
            'module::breadcrumb.products',
            $url->route('anomaly.module.products::products.index')
        );

        $this->breadcrumbs->add($category->getName(), $url->make('anomaly.module.products::category', $category));

        return $this->view->make('anomaly.module.products::categories/view');
    }
}
Displaying Breadcrumbs

The breadcrumb collection is loaded into the template super variable letting you display breadcrumbs as needed.

<ol class="breadcrumb">
    {% for key, url in template.breadcrumbs %}

        {% if loop.last %}
            <li class="active">{{ trans(key) }}</li>
        {% else %}
            <li><a href="{{ url }}">{{ trans(key) }}</a></li>
        {% endif %}

    {% endfor %}
</ol>

Buttons

Button definitions are used often in other UI definitions. For example module sections can define buttons and form actions extend them.

Understanding the anatomy and general behavior of button definitions is integral in working proficiently with UI builders.

Introduction

This section will introduce you to buttons and how to define them.

Defining Buttons

Buttons are defined by simple arrays. These buttons are ran through various processing and is used to hydrate instances of the \Anomaly\Streams\Platform\Ui\Button\Button class for use by the Streams Platform.

The idea behind defining buttons is that you can provide minimal information about a button and the Streams Platform can do the rest for you. Saving you from having to define the instance yourself with all required parameters.

Button definitions are simply an array:

'buttons' => [
    'create'          => [
        'url' => '/admin/example/test/create',
        'text' => 'streams::button.create',
        'icon' => 'fa asterisk',
        'type' => 'success',
    ],
]

The Button Definition

Button definitions are ran through valuation, evaluation, and resolving, as well as other processes like guessing. These are the simple property descriptions for buttons.

Properties
Key Required Type Default Description

$url

false

string

{section.path}/{button.slug}/{entry.id}

The HREF attribute of the button.

$button

false

string

Anomaly\Streams\Platform\Ui\Button

The target button class to build.

$text

false

string

{vendor}.module.{module}::button.{button}.title

The button text.

$icon

false

string

null

A registered icon string or icon class.

$class

false

string

null

The CSS class to append ot the button class attribute.

$type

false

string

default

The button type or context. Bootstrap state colors (primary, success, etc) are supported by default.

$size

false

string

md

The button size. Bootstrap button sized are supported by default.

$attributes

false

string

null

An array of key => value HTML attributes. Any base level definition keys starting with data- will be pushed into attributes automatically.

$permission

false

string

null

The permission key required to display the button.

$disabled

false

boolean

false

Determines whether the button will be disabled or not.

$enabled

false

boolean

true

Determines whether the button will be rendered or not.

$dropdown

false

array

null

An array of item definitions. See below for more information.

$position

false

string

left

The position of the button's dropdown.

ButtonRegistry::register()

The register method adds a globally available button definition.

Returns: Anomaly\Streams\Platform\Ui\Button\ButtonRegistry
Arguments
Key Required Type Default Description

$button

true

string

none

The button key.

$parameters

true

array

none

The registered button parameters.

Example
$registery->register(
    'purchase', 
    [
        'type' => 'success',
        'text' => 'Purchase',
        'icon' => 'fa fa-credit-card',
        'href' => 'checkout/{entry.id}',
    ]
);

The Dropdown Definition

The button dropdown property can be used to define a dropdown menu for the button.

'buttons' => [
    'save' => [
        'dropdown' => [
            [
                'icon' => 'save',
                'text' => 'Save and exit',
            ],
            [
                'icon' => 'fa fa-sign-out',
                'text' => 'Save and continue',
            ]
        ]
    ]
]
Properties
Key Required Type Default Description

$text

true

string

null

The text or translation key.

$icon

false

string

null

A registered icon string or icon class.

$url

true

string

null

The button URL. This gets pushed into attributes automatically as HREF.

The Button Registry

Below are the basic registered buttons. Some definitions like form actions that extend buttons may add to these or use them in their own registry. In such cases they will be documented separately.

Registered buttons can be found in Anomaly\Streams\Platform\Ui\Button\ButtonRegistry.

/**
 * Default Buttons
 */
'default'       => [
    'type' => 'default'
],
'cancel'        => [
    'text' => 'streams::button.cancel',
    'type' => 'default'
],

/**
 * Primary Buttons
 */
'options'       => [
    'text' => 'streams::button.options',
    'type' => 'primary',
    'icon' => 'cog',
],

/**
 * Success Buttons
 */
'green'         => [
    'type' => 'success'
],
'success'       => [
    'icon' => 'check',
    'type' => 'success'
],
'save'          => [
    'text' => 'streams::button.save',
    'icon' => 'save',
    'type' => 'success'
],
'update'        => [
    'text' => 'streams::button.save',
    'icon' => 'save',
    'type' => 'success'
],
'create'        => [
    'text' => 'streams::button.create',
    'icon' => 'fa fa-asterisk',
    'type' => 'success'
],
'new'           => [
    'text' => 'streams::button.new',
    'icon' => 'fa fa-plus',
    'type' => 'success'
],
'new_field'     => [
    'text' => 'streams::button.new_field',
    'icon' => 'fa fa-plus',
    'type' => 'success'
],
'add'           => [
    'text' => 'streams::button.add',
    'icon' => 'fa fa-plus',
    'type' => 'success'
],
'add_field'     => [
    'text' => 'streams::button.add_field',
    'icon' => 'fa fa-plus',
    'type' => 'success'
],
'assign_fields' => [
    'text' => 'streams::button.assign_fields',
    'icon' => 'fa fa-plus',
    'type' => 'success'
],
'send'          => [
    'text' => 'streams::button.send',
    'icon' => 'envelope',
    'type' => 'success'
],
'submit'        => [
    'text' => 'streams::button.submit',
    'type' => 'success'
],
'install'       => [
    'text' => 'streams::button.install',
    'icon' => 'download',
    'type' => 'success'
],
'entries'       => [
    'text' => 'streams::button.entries',
    'icon' => 'list-ol',
    'type' => 'success'
],
'done'          => [
    'text' => 'streams::button.done',
    'type' => 'success',
    'icon' => 'check'
],
'select'        => [
    'text' => 'streams::button.select',
    'type' => 'success',
    'icon' => 'check'
],
'restore'       => [
    'text' => 'streams::button.restore',
    'type' => 'success',
    'icon' => 'repeat'
],
'finish'        => [
    'text' => 'streams::button.finish',
    'type' => 'success',
    'icon' => 'check'
],
'finished'      => [
    'text' => 'streams::button.finished',
    'type' => 'success',
    'icon' => 'check'
],

/**
 * Info Buttons
 */
'blue'          => [
    'type' => 'info'
],
'info'          => [
    'type' => 'info'
],
'information'   => [
    'text' => 'streams::button.info',
    'icon' => 'fa fa-info',
    'type' => 'info'
],
'help'          => [
    'icon'        => 'circle-question-mark',
    'text'        => 'streams::button.help',
    'type'        => 'info',
    'data-toggle' => 'modal',
    'data-target' => '#modal'
],
'view'          => [
    'text' => 'streams::button.view',
    'icon' => 'fa fa-eye',
    'type' => 'info'
],
'export'        => [
    'text' => 'streams::button.export',
    'icon' => 'download',
    'type' => 'info'
],
'fields'        => [
    'text' => 'streams::button.fields',
    'icon' => 'list-alt',
    'type' => 'info'
],
'assignments'   => [
    'text' => 'streams::button.assignments',
    'icon' => 'list-alt',
    'type' => 'info'
],
'settings'      => [
    'text' => 'streams::button.settings',
    'type' => 'info',
    'icon' => 'cog',
],
'configure'     => [
    'text' => 'streams::button.configure',
    'icon' => 'wrench',
    'type' => 'info'
],

/**
 * Warning Buttons
 */
'orange'        => [
    'type' => 'warning'
],
'warning'       => [
    'icon' => 'warning',
    'type' => 'warning'
],
'edit'          => [
    'text' => 'streams::button.edit',
    'icon' => 'pencil',
    'type' => 'warning'
],

/**
 * Danger Buttons
 */
'red'           => [
    'type' => 'danger'
],
'danger'        => [
    'icon' => 'fa fa-exclamation-circle',
    'type' => 'danger'
],
'remove'        => [
    'text' => 'streams::button.remove',
    'type' => 'danger',
    'icon' => 'ban'
],
'delete'        => [
    'icon'       => 'trash',
    'type'       => 'danger',
    'text'       => 'streams::button.delete',
    'attributes' => [
        'data-toggle'  => 'confirm',
        'data-message' => 'streams::message.confirm_delete'
    ]
],
'prompt'        => [
    'icon'       => 'trash',
    'type'       => 'danger',
    'button'     => 'delete',
    'text'       => 'streams::button.delete',
    'attributes' => [
        'data-match'   => 'yes',
        'data-toggle'  => 'prompt',
        'data-message' => 'streams::message.prompt_delete'
    ]
],
'uninstall'     => [
    'type'       => 'danger',
    'icon'       => 'times-circle',
    'text'       => 'streams::button.uninstall',
    'attributes' => [
        'data-toggle'  => 'confirm',
        'data-message' => 'streams::message.confirm_uninstall'
    ]
]

Using Registered Buttons

There are a number of buttons registered in the \Anomaly\Streams\Platform\Ui\Button\ButtonRegistry class. To use any of these buttons simply include their string slug and the button's registered definitions will be merged into your own.

'buttons' => [
    'save',
    'delete' => [
        'text' => 'Delete me!', // Overrides the default "Delete" text.
    ],
]

The above save and delete buttons are registered as:

'buttons' => [
    'save' => [
        'icon' => 'save',
        'type' => 'success',
        'text' => 'streams::button.save',
    ],
    'delete'        => [
        'icon' => 'trash',
        'type' => 'danger',
        'text' => 'streams::button.delete',
        'attributes' => [
            'data-toggle' => 'confirm',
            'data-message' => 'streams::message.confirm_delete'
        ]
    ],
]

Note that the delete text will be overridden since you defined your own text.

Control Panel

The control panel defines the upper most admin UI structure. Every page's control panel you view in the admin is defined by a module.

<?php namespace Anomaly\ProductsModule;

use Anomaly\Streams\Platform\Addon\Module\Module;

class ProductsModule extends Module
{

    /**
     * The module sections.
     *
     * @var array
     */
    protected $sections = [
        'products'   => [
            'buttons'  => [
                'new_product' => [
                    'data-toggle' => 'modal',
                    'data-target' => '#modal',
                    'href'        => 'admin/products/choose',
                ],
            ],
            'sections' => [
                'attributes' => [
                    'href'    => 'admin/products/attributes/{request.route.parameters.product}',
                    'buttons' => [
                        'add_attribute',
                    ],
                ],
                'modifiers'  => [
                    'href'    => 'admin/products/modifiers/{request.route.parameters.product}',
                    'buttons' => [
                        'add_modifier',
                    ],
                ],
                'options'    => [
                    'href'    => 'admin/products/options/{request.route.parameters.modifier}',
                    'buttons' => [
                        'add_option',
                    ],
                ],
                'variants'   => [
                    'href'    => 'admin/products/variants/{request.route.parameters.product}',
                    'buttons' => [
                        'new_variant',
                    ],
                ],
            ],
        ],
        'categories' => [
            'buttons' => [
                'new_category',
            ],
        ],
        'types'      => [
            'buttons'  => [
                'new_type' => [
                    'data-toggle' => 'modal',
                    'data-target' => '#modal',
                    'href'        => 'admin/products/types/choose',
                ],
            ],
            'sections' => [
                'assignments' => [
                    'href'    => 'admin/products/types/assignments/{request.route.parameters.type}',
                    'buttons' => [
                        'assign_fields' => [
                            'data-toggle' => 'modal',
                            'data-target' => '#modal',
                            'href'        => 'admin/products/types/assignments/{request.route.parameters.type}/choose',
                        ],
                    ],
                ],
            ],
        ],
        'fields'     => [
            'buttons' => [
                'new_field' => [
                    'data-toggle' => 'modal',
                    'data-target' => '#modal',
                    'href'        => 'admin/products/fields/choose',
                ],
            ],
        ],
    ];

}

Introduction

This section will introduce you to the control panel components and how to use them.

Sections

The control panel sections are the building blocks for a module's UI.

The Module Segment

Modules are the primary building block of the control panel and must be routed by their slug first.

admin/products // Products module
admin/settings // Settings module
The Section Segment

The third slug is reserved for sections. Each module is divided into sections. The first section, known as the default section does not require a URI segment.

admin/products              // default section of products module
admin/products/categories   // "categories" section of products module
admin/products/brands       // "brands" section of products module
Pro-tip: An excellent naming convention is to name your products after your primary stream. And your default section after your module and primary stream as well so everything aligns nicely.
Additional Segments

Aside from nesting sections the control panel no longer has any interest in your URI pattern after the section segment.

Buttons

Each section can define buttons that display when that section is active. Buttons can be used for anything! Very often they are used for displaying create a new entry buttons for example.

Basic Usage

The control panel is entirely defined in your Module class. When you create a module you can define the $sections property to define the control panel structure for that module.

Module::$sections

The sections property is used to define the sections for the module.

Example
protected $sections = [
    'products' => [
        'buttons' => [
            'create',
        ],
    ],
    'categories' => [
        'buttons' => [
            'create',
        ],
    ],
];

The Section Definition

Below is a list of all section definition properties available.

Properties
Key Required Type Default Description

$slug

true

string

The section array key.

The slug will become the URI segment and must be unique.

$title

false

string

{vendor}.module.{module}::section.{slug}.title

The section title or translation key.

$description

false

string

{vendor}.module.{module}::section.{slug}.description

The section description or translation key.

$buttons

false

array

null

An array of button definitions.

$icon

false

string

null

A registered icon string or icon class.

$class

false

string

null

A CSS class to append to the section.

$matcher

false

string

The section's URL

A string pattern to test against a request path to determine if the section is active or not. Example: admin/products/*/variants

$parent

false

string

null

The slug of the parent section if any. Sub-sections will not display in the navigation. Sub-sections highlight their parent when active but display their own buttons.

$sections

false

array

null

An array of child section definitions. These are placed in the base array and parent set on them automatically.

$attributes

false

array

null

An array of key => value HTML attributes. Any base level definition keys starting with data- will be pushed into attributes automatically.

$href

false

string

admin/{module}/{slug}

The HREF to the section. This gets pushed into attributes automatically.

$breadcrumb

false

string|false

The section title.

The breadcrumb text for the section.

$view

false

string

null

The view to delegate the section to.

$html

false

string

null

The HTML to display as the section.

$permission

false

string

{vendor}.module.{module}::{slug}.*

The permission string required to access the section. Note that builders within the section usually automate permissions so this may not be required if using said builders.

$permalink

false

string

The section URL.

The actual permalink for the section in the case that the HREF is used for something different. This is helpful when the HREF used for the section link needs to be different than the actual HREF for the section. Like a section link that opens a modal as in the above example to take you into the section.

Section Handlers

Sections also support handlers to dynamically control the sections of your module. Set the $sections property to a callable string or create a valid handler class next to your module class to be picked up automatically.

protected $sections = 'Anomaly\ProductsModule\[email protected]';

The handler is called from the service container and is passed the $builder.

<?php namespace Anomaly\UsersModule;

use Anomaly\Streams\Platform\Ui\ControlPanel\ControlPanelBuilder;

class UsersModuleSections
{
    public function handle(ControlPanelBuilder $builder)
    {
        $builder->setSections([
            'users',
            'roles',
            'fields',
        ]);
    }
}

Forms

Forms let you easily create and update model objects. Usually they are used with streams but are versatile enough to handle API forms, non-model forms, or any form requirements you have.

Builders

Form builders are the entry point for creating a form. All forms will use a builder to convert your basic component definitions into their respective classes.

Basic Usage

To get started building a form. First create a FormBuilder that extends the \Anomaly\Streams\Platform\Ui\Form\FormBuilder class.

When using the make:stream command form builders are created for you. A generated form builder looks like this:

<?php namespace Example\TestModule\Test\Form;

use Anomaly\Streams\Platform\Ui\Form\FormBuilder;

class TestFormBuilder extends FormBuilder
{

    /**
     * The form fields.
     *
     * @var array|string
     */
    protected $fields = [];

    /**
     * Fields to skip.
     *
     * @var array|string
     */
    protected $skips = [];

    /**
     * The form actions.
     *
     * @var array|string
     */
    protected $actions = [];

    /**
     * The form buttons.
     *
     * @var array|string
     */
    protected $buttons = [];

    /**
     * The form options.
     *
     * @var array
     */
    protected $options = [];

    /**
     * The form sections.
     *
     * @var array
     */
    protected $sections = [];

    /**
     * The form assets.
     *
     * @var array
     */
    protected $assets = [];

}
FormBuilder::$fields

Form fields are the primary building block of a form. Fields define the inputs for your form. If your form uses a stream model then most of the work can be automated for you. However you can also define fields 100% manually too.

Example
protected $fields = [
    'database_driver'       => [
        'label'        => 'anomaly.module.installer::field.database_driver.label',
        'instructions' => 'anomaly.module.installer::field.database_driver.instructions',
        'type'         => 'anomaly.field_type.select',
        'value'        => env('DB_DRIVER', 'mysql'),
        'required'     => true,
        'rules'        => [
            'valid_database',
        ],
        'validators'   => [
            'valid_database' => [
                'handler' => DatabaseValidator::class,
                'message' => false,
            ],
        ],
        'config'       => [
            'options' => [
                'mysql'    => 'MySQL',
                'pgsql' => 'Postgres',
                'sqlite'   => 'SQLite',
                'sqlsrv'   => 'SQL Server',
            ],
        ],
    ],
    'database_host'         => [
        'label'        => 'anomaly.module.installer::field.database_host.label',
        'placeholder'  => 'anomaly.module.installer::field.database_host.placeholder',
        'instructions' => 'anomaly.module.installer::field.database_host.instructions',
        'type'         => 'anomaly.field_type.text',
        'value'        => 'localhost',
        'required'     => true,
    ],
    'database_name'         => [
        'label'        => 'anomaly.module.installer::field.database_name.label',
        'placeholder'  => 'anomaly.module.installer::field.database_name.placeholder',
        'instructions' => 'anomaly.module.installer::field.database_name.instructions',
        'value'        => env('DB_DATABASE', snake_case(strtolower(config('streams::distribution.name')))),
        'type'         => 'anomaly.field_type.text',
        'required'     => true,
    ],
];
FormBuilder::$skips

The skips property is for defining fields to skip. Simply include an array of field slugs.

Example
protected $skips = [
    'example_field',
];
FormBuilder::$sections

Form sections help you organize your fields into different field group layouts.

Example
protected $sections = [
    'license'       => [
        'fields' => [
            'license',
        ],
    ],
    'database'      => [
        'fields' => [
            'database_driver',
            'database_host',
            'database_name',
            'database_username',
            'database_password',
        ],
    ],
    'administrator' => [
        'fields' => [
            'admin_username',
            'admin_email',
            'admin_password',
        ],
    ],
    'application'   => [
        'fields' => [
            'application_name',
            'application_reference',
            'application_domain',
            'application_locale',
            'application_timezone',
        ],
    ],
];
FormBuilder::$actions

Form actions determine what your form does when submitted. Most actions assume the form saves and then does something else like redirect to a new form or the same form to continue editing the entry but they can do anything else you need like submitting to APIs or redirecting somewhere.

Note: Actions extend UI buttons so some actions may use registered buttons to further automate themselves.
Example
protected $actions = [
    'save',
    'save_create',
];
FormBuilder::$buttons

Form buttons extend base UI buttons and allow you to add simple button links to your form. Form buttons do not submit the form.

Example
protected $buttons = [
    'cancel',
    'view',
];
FormBuilder::$options

Form options help configure the behavior in general of the form. You can use options for toggling specific UI on or off, adding a simple title and description, or pushing data into the form view.

Example
protected $options = [
    'redirect' => 'admin/products/view/{entry.id}'
];
FormBuilder::$assets

The assets property defines assets to load to load for the form.

Example
protected $assets = [
    'scripts.js' => [
        'theme::js/forms/initialize.js',
        'theme::js/forms/validation.js',
    ],
    'styles.css' => [
        'theme::scss/forms/validation.scss',
    ]
];
Ajax Forms

You can easily make forms use ajax behavior by setting the $ajax property.

protected $ajax = true;

You can also flag forms as ajax on the fly.

$builder->setAjax(true);

Ajax forms are designed to be included in a modal by default but you can configure it to display like a normal form or however you like.

In Development: The Ajax API is still being developed. While ajax forms are usable, more robust JSON response information is still missing.
Read-only Forms

To render the form as read-only just set the $readOnly flag on the builder.

protected $readOnly = true;

You can also set this flag on the fly:

$builder->setReadOnly(true);
Form Handlers

Form handlers are responsible for handling the business logic of the form. Generally this is to simply save the form.

The default form handler for example looks like this:

<?php namespace Anomaly\Streams\Platform\Ui\Form;

class FormHandler
{

    /**
     * Handle the form.
     *
     * @param FormBuilder $builder
     */
    public function handle(FormBuilder $builder)
    {
        if (!$builder->canSave()) {
            return;
        }

        $builder->saveForm();
    }
}
Writing Custom Handlers

You can use your own form handler by defining it in your form builder. Simply define the self handling class or a callable class string.

protected $handler = \Example\Test\MyCustomHandler::class; // Assumes @handle

protected $handler = 'Example\Test\[email protected]';

Now in your form handler you can add your own logic.

class MyCustomHandler
{

    public function handle(MyCustomFormBuilder $builder)
    {
        if ($builder->hasFormErrors()) {
            return; // We have errors.. abandon ship!
        }

        // Do amazing stuff here.
    }
}
Pro Tip: Handlers are called using the Service Container and support class and method injection.
Form Models

Form models are used to determine the form repository to use and provide the model for the system to use when creating and updating an entry.

Form models are guessed based on the form builders class. If using php artisan make:stream the model property does not need to be set.

If you would like to or have to define the model yourself you can do so on the form builder.

protected $model = \Anomaly\UsersModule\User\UserModel::class;
Form Repositories

Form repositories are used to create an entry when creating and to update an entry when editing. The repository is guessed based on the type of model used.

If you would like to or need to define the repository yourself you can do so on the form builder.

protected $repository = \Example\Test\FancyFormRepository::class;

The form repository must implement \Anomaly\Streams\Platform\Ui\Form\Contract\FormRepositoryInterface and implement the following methods:

/**
 * Find an entry or return a new one.
 *
 * @param $id
 * @return mixed
 */
public function findOrNew($id);

/**
 * Save the form.
 *
 * @param  FormBuilder $builder
 * @return bool|mixed
 */
public function save(FormBuilder $builder);
Including Assets

Besides the obvious overriding views to include your own assets you can also specify assets to include with the $assets array.

Specify the assets to include per the collection they should be added to.

protected $assets = [
    'scripts.js' => [
        'theme::js/form/initialize.js',
        'theme::js/form/example.js',
    ],
    'styles.css' => [
        'theme::scss/form/example.scss',
    ]
];

Fields

Form fields are the main building blocks of forms. They control the inputs first but also the way data is handled when saving and everything in between.

The Field Definition

Below is a list of all available field definition properties.

Properties
Key Required Type Default Description

$slug

true

string

The field definition key.

The field slug is used for naming the field input and identifying it amongst other fields.

$label

false

string

The field assignment label or field name if available.

The input label.

$instructions

false

string

The field assignment instructions or field instructions.

The input instructions.

$warning

false

string

The field assignment warning or field warning.

The input warning.

$placeholder

false

string

The field assignment placeholder or field placeholder.

The input placeholder.

$type

false

string

The field type.

The namespace or slug of a field type to use.

$field

false

string

The streams field slug.

The streams field slug to use for populating defaults.

$required

false

boolean

The required status of the field assignment.

A shortcut boolean flag to add required to the rules array.

$unique

false

boolean

The unique status of the field assignment.

A shortcut boolean flag to add unique to the rules array.

$rules

false

array

null

An array of additional Laravel validation rules.

$validators

false

array

null

An array of custom validators keyed by rule.

$prefix

false

string

The prefix of the form.

The prefix helps when more than one form are displayed on a page.

$disabled

false

boolean

false

Determines whether the field will be disabled or not.

$enabled

false

boolean

true

Determines whether the field will be processed or not.

$readonly

false

boolean

false

Determines whether the field will be read only or not.

$hidden

false

boolean

false

Determines whether the field will be visibly hidden or not.

$config

false

array

null

A config array for the field type.

$attributes

false

array

null

An array of key => value HTML attributes. Any base level definition keys starting with data- will be pushed into attributes automatically.

$class

false

string

Varies by field type.

A class to append to the attributes.

$input_view

false

string

Varies by field type.

A prefixed view to use for the input.

$wrapper_view

false

string

Varies by field type.

A prefixed view to use for the field wrapper.

Sections

Sections help you organize your fields into different field groups.

The Section Definition

Below is a list of all possible section definition properties available.

Properties
Key Required Type Default Description

slug

true

string

The definition key.

The section slug can be used to reference the section later.

title

false

string

{vendor}.module.{module}::{slug}.title

The section title.

description

false

string

{vendor}.module.{module}::{slug}.description

The section description.

fields

false

array

null

The section fields.

tabs

false

array

null

The section tab definitions. See below for more information.

attributes

false

array

null

An array of key => value HTML attributes. Any base level definition keys starting with data- will be pushed into attributes automatically.

view

false

string

null

The view to delegate the section to.

The Tab Definition

Here is a list of all available tab properties.

Properties
Key Required Type Default Description

slug

true

string

The definition key.

The tab slug is used in it's HTML markup as part of an ID.

title

true

string

none

The tab title.

fields

false

array

null

The tab fields.

Standard Sections

Standard sections simply stack fields on top of each other in a single group.

protected $sections = [
    'database'      => [
        'title'  => 'Database Information',
        'fields' => [
            'database_driver',
            'database_host',
            'database_name',
            'database_username',
            'database_password'
        ]
    ],
    'administrator' => [
        'title'  => 'Admin Information',
        'fields' => [
            'admin_username',
            'admin_email',
            'admin_password'
        ]
    ],
];
Tabbed Sections

Tabbed sections allow separating fields in the section into tabs.

protected $sections = [
    'general' => [
         'tabs' => [
             'form'          => [
                 'title'  => 'module::tab.form',
                 'fields' => [
                     'form_name',
                     'form_slug',
                     'form_description',
                     'success_message',
                     'success_redirect'
                 ]
             ],
             'notification'  => [
                 'title'  => 'module::tab.notification',
                 'fields' => [
                     'send_notification',
                     'notification',
                     'notification_send_to',
                     'notification_cc',
                     'notification_bcc'
                 ]
             ],
         ]
     ]
];
Section Views

You can also define a view to handle the entire section.

protected $sections = [
    'general'      => [
        'view'  => 'module::form/general',
    ],
    'advanced'      => [
        'view'  => 'module::form/advanced',
    ],
];

Actions

Form actions determine what your form does when submitted. Most actions assume the form saves and then does something else like redirect to a new form or the same form to continue editing the entry.

The Action Definition

Below is a list of all action specific definition properties. Actions extend buttons so you can refer to button documentation for a complete set of options for buttons.

Properties
Key Required Type Default Description

slug

true

string

The definition key.

The action becomes the submit button's name.

redirect

false

string

Back to the form.

The action redirect URL.

handler

false

string

null

A callable class string. This is useful when you want to include additional logic when a form is submitted using an action.

Defining Actions

A standard action usually modifies the redirect but can also define a handler to perform extra logic too.

protected $actions = [
    'button'   => 'save',
    'redirect' => 'admin/products/view/{entry.id}',
];
Using Registered Actions

There are a number of actions registered in the \Anomaly\Streams\Platform\Ui\Form\Component\Action\ActionRegistry class. To use any of these actions simply include their registered string slug.

protected $actions = [
    'save',
];

The simple slug get's populated from the registered button like this:

protected $actions = [
    'save' => [
        'button' => 'save',
        'text'   => 'streams::button.save'
    ],
];
Overriding Registered Actions

Just like other definitions either registered or automated, you can include more information in your definition to override things.

protected $actions = [
    'save' => [
        'text' => 'Save Me!'
    ],
];
The Action Registry

Below are the basic registered actions. Note the button options that will in turn automate more action properties based on registered buttons. Actions may also simply be registered buttons themselves and allow default handling behavior. Experiment with it!

Registered actions can be found in Anomaly\Streams\Platform\Ui\Form\Component\Action\ActionRegistry.

'update'         => [
    'button' => 'update',
    'text'   => 'streams::button.update'
],
'save_exit'      => [
    'button' => 'save',
    'text'   => 'streams::button.save_exit'
],
'save_edit'      => [
    'button' => 'save',
    'text'   => 'streams::button.save_edit'
],
'save_create'    => [
    'button' => 'save',
    'text'   => 'streams::button.save_create'
],
'save_continue'  => [
    'button' => 'save',
    'text'   => 'streams::button.save_continue'
],
'save_edit_next' => [
    'button' => 'save',
    'text'   => 'streams::button.save_edit_next'
],

Options

Form options help configure the behavior in general of the form. Anything from toggling specific UI on or off to adding a simple title and description can be done with the form options.

protected $options = [
    'title'     => 'My awesome form!',
    'form_view' => 'module::my/custom/form'
];

You can also set/add options from the API.

$builder->addOption('url', 'http://domain.com/example/api');
Available Options

Below is a list of all available options for forms.

Properties
Key Required Type Default Description

form_view

false

string

streams::form/form

The form view is the primary form layout view.

wrapper_view

false

string

streams::blank

The wrapper view is the admin layout wrapper. This is the view you would override if you wanted to include a sidebar with your form for example.

permission

false

string

{vendor}.module.{module}::{stream}.write

The permission string required to access the form.

url

false

string

The route displaying the form (submits to itself).

The URL for the form submission. This is generally automated but varies depending on how the form is being used.

Icons

Defining icons help abstract how your theme's icons are presented throughout both admin and public themes. By referring to an icon by a registered universal slug and letting the registry provide the class you can easily swap out icon implementations.

Basic Usage

Icon definitions are only a string. If the icon is not found in the registry then the string will be used as-is as the icon class.

'icon' => 'addon' // <i class="fa fa-puzzle-piece"></i>

'icon' => 'my-icon' //  <i class="my-icon"></i>

The Icon Registry

These are all the icons registered in Anomaly\Streams\Platform\Ui\Icon\IconRegistry class.

'addon'                => 'fa fa-puzzle-piece',
'adjust'               => 'glyphicons glyphicons-adjust-alt',
'airplane'             => 'glyphicons glyphicons-airplane',
'amex'                 => 'fa fa-cc-amex',
'arrows-h'             => 'fa fa-arrows-h',
'arrows-v'             => 'fa fa-arrows-v',
'ban'                  => 'fa fa-ban',
'bars'                 => 'fa fa-bars',
'brush'                => 'glyphicons glyphicons-brush',
'calendar'             => 'glyphicons glyphicons-calendar',
'car'                  => 'glyphicons glyphicons-car',
'check'                => 'fa fa-check',
'check-circle'         => 'fa fa-check-circle',
'check-circle-alt'     => 'fa fa-check-circle-o',
'check-square-alt'     => 'fa fa-check-square-o',
'circle'               => 'fa fa-circle',
'circle-alt'           => 'fa fa-circle-o',
'circle-question-mark' => 'glyphicons glyphicons-circle-question-mark',
'cloud-plus'           => 'glyphicons glyphicons-cloud-plus',
'cloud-upload'         => 'fa fa-cloud-upload',
'cloud-upload-alt'     => 'glyphicons glyphicons-cloud-upload',
'code'                 => 'fa fa-code',
'code-fork'            => 'fa fa-code-fork',
'cog'                  => 'fa fa-cog',
'cogs'                 => 'fa fa-cogs',
'comments'             => 'glyphicons glyphicons-comments',
'compress'             => 'fa fa-compress',
'conversation'         => 'glyphicons glyphicons-conversation',
'credit-card'          => 'glyphicons glyphicons-credit-card',
'cubes'                => 'fa fa-cubes',
'dashboard'            => 'fa fa-dashboard',
'database'             => 'fa fa-database',
'diners-club'          => 'fa  fa-cc-diners-club',
'discover'             => 'fa fa-cc-discover',
'download'             => 'fa fa-download',
'duplicate'            => 'glyphicons glyphicons-duplicate',
'envelope'             => 'fa fa-envelope',
'envelope-alt'         => 'fa fa-envelope-o',
'exchange'             => 'fa fa-exchange',
'exit'                 => 'fa fa-sign-out',
'expand'               => 'fa fa-expand',
'external-link'        => 'fa fa-external-link',
'eyedropper'           => 'glyphicons glyphicons-eyedropper',
'facebook'             => 'fa fa-facebook',
'facebook-square'      => 'fa fa-facebook-square',
'facetime-video'       => 'glyphicons glyphicons-facetime-video',
'file'                 => 'fa fa-file-o',
'file-image'           => 'fa fa-file-image-o',
'film'                 => 'fa fa-film',
'filter'               => 'fa fa-filter',
'fire'                 => 'glyphicons glyphicons-fire',
'flag'                 => 'fa fa-flag',
'folder-closed'        => 'glyphicons glyphicons-folder-closed',
'folder-open'          => 'glyphicons glyphicons-folder-open',
'fullscreen'           => 'glyphicons glyphicons-fullscreen',
'git-branch'           => 'glyphicons glyphicons-git-branch',
'global'               => 'glyphicons glyphicons-global',
'globe'                => 'glyphicons glyphicons-globe',
'home'                 => 'fa fa-home',
'jcb'                  => 'fa fa-cc-jcb',
'keys'                 => 'glyphicons glyphicons-keys',
'language'             => 'fa fa-language',
'laptop'               => 'fa fa-laptop',
'link'                 => 'glyphicons glyphicons-link',
'list-alt'             => 'fa fa-list-alt',
'list-ol'              => 'fa fa-list-ol',
'list-ul'              => 'fa fa-list-ul',
'lock'                 => 'fa fa-lock',
'locked'               => 'fa fa-lock',
'magic'                => 'glyphicons glyphicons-magic',
'map-marker'           => 'fa fa-map-marker',
'mastercard'           => 'fa fa-cc-mastercard',
'minus'                => 'fa fa-minus',
'newspaper'            => 'fa fa-newspaper-o',
'options'              => 'fa fa-options',
'order'                => 'glyphicons glyphicons-sort',
'paperclip'            => 'glyphicons glyphicons-paperclip',
'paypal'               => 'fa fa-cc-paypal',
'pencil'               => 'fa fa-pencil',
'percent'              => 'fa fa-percent',
'phone'                => 'fa fa-phone',
'picture'              => 'glyphicons glyphicons-picture',
'play'                 => 'fa fa-play',
'plug'                 => 'fa fa-plug',
'plus'                 => 'fa fa-plus',
'plus-circle'          => 'fa fa-plus-circle',
'plus-square'          => 'fa fa-plus-square',
'power-off'            => 'fa fa-power-off',
'question'             => 'fa fa-question',
'question-circle'      => 'fa fa-question-circle',
'quote-left'           => 'fa fa-quote-left',
'quote-right'          => 'fa fa-quote-right',
'redo'                 => 'glyphicons glyphicons-redo',
'refresh'              => 'fa fa-refresh',
'repeat'               => 'fa fa-repeat',
'retweet'              => 'glyphicons glyphicons-retweet',
'rss'                  => 'fa fa-rss',
'rss-square'           => 'fa fa-rss-square',
'save'                 => 'fa fa-save',
'scissors'             => 'glyphicons glyphicons-scissors-alt',
'search'               => 'fa fa-search',
'server'               => 'glyphicons glyphicons-server',
'share'                => 'fa fa-share-alt',
'shopping-cart'        => 'glyphicons glyphicons-shopping-cart',
'sign-in'              => 'fa fa-sign-in',
'sign-out'             => 'fa fa-sign-out',
'sitemap'              => 'fa fa-sitemap',
'sliders'              => 'fa fa-sliders',
'sort-ascending'       => 'glyphicons glyphicons-sort-by-attributes',
'sort-descending'      => 'glyphicons glyphicons-sort-by-attributes-alt',
'sortable'             => 'glyphicons glyphicons-sorting',
'square'               => 'fa fa-square',
'square-alt'           => 'fa fa-square-o',
'star'                 => 'fa fa-star',
'stripe'               => 'fa-cc-stripe',
'tag'                  => 'fa fa-tag',
'tags'                 => 'fa fa-tags',
'th'                   => 'fa fa-th',
'th-large'             => 'fa fa-th-large',
'thumbnails'           => 'glyphicons glyphicons-thumbnails',
'times'                => 'fa fa-times',
'times-circle'         => 'fa fa-times-circle',
'times-square'         => 'fa fa-times-square',
'translate'            => 'glyphicons glyphicons-translate',
'trash'                => 'fa fa-trash',
'truck'                => 'fa fa-truck',
'twitter'              => 'fa fa-twitter',
'unlock'               => 'fa fa-unlock',
'upload'               => 'fa fa-upload',
'usd'                  => 'fa fa-usd',
'user'                 => 'fa fa-user',
'users'                => 'fa fa-users',
'video-camera'         => 'fa fa-video-camera',
'warning'              => 'fa fa-warning',
'wrench'               => 'fa fa-wrench',
'youtube'              => 'fa fa-youtube',

Tables

Tables let you easily display a table of stream entries. They can also be used without streams using regular Eloquent models or without any database backend at all by manually loading entries.

Builders

Table builders are the entry point for creating a table. All tables will use a builder to convert your basic component definitions into their respective classes.

Basic Usage

To get started building a table. First create a TableBuilder that extends the \Anomaly\Streams\Platform\Ui\Table\TableBuilder class.

When using the make:stream command table builders are created for you. A generated table builder looks like this:

<?php namespace Example\TestModule\Test\Table;

use Anomaly\Streams\Platform\Ui\Table\TableBuilder;

class TestTableBuilder extends TableBuilder
{

    /**
     * The table views.
     *
     * @var array|string
     */
    protected $views = [];

    /**
     * The table filters.
     *
     * @var array|string
     */
    protected $filters = [];

    /**
     * The table columns.
     *
     * @var array|string
     */
    protected $columns = [];

    /**
     * The table buttons.
     *
     * @var array|string
     */
    protected $buttons = [
        'edit'
    ];

    /**
     * The table actions.
     *
     * @var array|string
     */
    protected $actions = [
        'delete'
    ];

    /**
     * The table options.
     *
     * @var array
     */
    protected $options = [];

    /**
     * The table assets.
     *
     * @var array
     */
    protected $assets = [];
}
TableBuilder::$filters

Table filters let you easily define inputs that filter the results of the table.

Example
protected $filters = [
    'title',
    'category',
    'description',
];
TableBuilder::$columns

Table columns are the basic building block of the table.

Example
protected $columns = [
    'title',
    'category',
    'description',
];
TableBuilder::$buttons

Table buttons display in the last column of each row.

Example
protected $buttons = [
    'edit',
    'view',
];
TableBuilder::$actions

Table actions let you define submittable buttons that perform an action on the selected rows.

Example
protected $actions = [
    'delete',
];
Ajax Tables

You can easily make tables use ajax behavior by setting the $ajax property.

protected $ajax = true;

You can also mark tables ajax on the fly.

$builder->setAjax(true);

Ajax tables are designed to be included in a modal by default but you can configure it to display like a normal table or however you like.

Table Models

Table models are used to determine the table repository to use and provide the model for the system to use when creating and updating an entry.

Table models are guessed based on the table builders position first. If using php artisan make:stream the model does not need to be set.

If an entry object is set the model will be pulled off of that next.

Lastly if you would like to or need to define a model yourself you can do so on the table builder.

protected $model = UserModel::class;
Table Repositories

Table repositories are used to create an entry when creating and to update an entry when editing. The repository is guessed based on the type of model used.

If you would like to or need to define a repository yourself you can do so on the table builder.

protected $repository = FancyTableRepository::class;
Including Assets

Besides the obvious overriding views to include your own assets you can also specify assets to include with the $assets array.

Specify the assets to include per the collection they should be added to.

protected $assets = [
    'scripts.js' => [
        'theme::js/tables/initialize.js',
        'theme::js/tables/validation.js',
    ],
    'styles.css' => [
        'theme::scss/tables/validation.scss',
    ]
];

Filters

Filter definitions display filters to help find entry sets.

Defining Filters

You can also define filters manually.

protected $filters = [
    'title' => [
        'type' => 'text',
        'query' => \Example\Test\FilterQuery::class, // Assumes @handle
    ],
];
Using Field Filters

To specify filters from the entry stream being used simply define the filters by the field slug:

protected $filters = [
    'title',
    'category',
    'description',
];
Custom Filter Queries

While the filter querying is generally handled 100% automatically. You can provide your own filtering logic as well:

protected $filters = [
    'title' => [
        'query' => \Example\Test\CustomQuery::class
    ],
];

The query class must accept the query Builder, Filter instance, and is called with the service container.

<?php namespace Example\Test;

use Anomaly\Streams\Platform\Ui\Table\Component\Filter\Contract\FilterInterface;

class CustomFilter
{

    /**
     * Handle the filter.
     *
     * @param Builder         $query
     * @param FilterInterface $filter
     */
    public function handle(Builder $query, FilterInterface $filter)
    {
        $query->where($filter->getSlug(), 'LIKE', "%{$filter->getValue()}%");
    }
}
Pro Tip: Even automated stream filters can be completely overridden. Try providing your own custom query!
The Filter Definition

Below is a list of all possible filter definition properties available.

Properties
Key Required Type Default Description

slug

true

string

The definition value/key.

The filter slug is used for naming the filter input and identifying it amongst other filters.

slug

true

string

The definition value/key.

The filter slug is used for naming the filter input and identifying it amongst other filters.

heading

false

string

The filter assignment name.

The filter label.

filter

true

string

\Anomaly\Streams\Platform\Ui\Table\Component\Filter\Type\InputFilter

The filter class.

query

true

string

\Anomaly\Streams\Platform\Ui\Table\Component\Filter\Query\GenericFilterQuery

The custom filter class to use.

Columns

Column definitions are the primary building block of a table. If your table uses a stream model then most of the work can be automated for you. However you can also define columns 100% manually too.

The Column Definition

Below is a list of all possible column definition properties available.

Properties
Key Required Type Default Description

value

true

string

none

The valuated string for the coumn text value. You can pass an array of values and merge them into the wrapper too.

slug

false

string

The definition key

The column slug is used for naming the column input and identifying it amongst other columns.

heading

false

string

The column assignment name.

The column label.

column

false

string

\Anomaly\Streams\Platform\Ui\Table\Component\Column\Column

The custom column class to use.

wrapper

false

string

{value}

The column value wrapper.

view

false

string

null

The view to delegate the column to.

class

false

string

null

The column class. Includes the heading row.

Defining Manual Columns

You can also define columns manually. Take a look at how the FileTableBuilder does it.

protected $columns = [
    'entry.preview' => [
        'heading' => 'anomaly.module.files::field.preview.name'
    ],
    'name'          => [
        'sort_column' => 'name',
        'wrapper'     => '
                <strong>{value.file}</strong>
                <br>
                <small class="text-muted">{value.disk}://{value.folder}/{value.file}</small>
                <br>
                <span>{value.size} {value.keywords}</span>',
        'value'       => [
            'file'     => 'entry.name',
            'folder'   => 'entry.folder.slug',
            'keywords' => 'entry.keywords.labels',
            'disk'     => 'entry.folder.disk.slug',
            'size'     => 'entry.size_label'
        ]
    ],
    'size'          => [
        'sort_column' => 'size',
        'value'       => 'entry.readable_size'
    ],
    'mime_type',
    'folder'
];
Pro Tip: Automated stream columns can be overridden too!
Using Stream Columns

To specify columns from the entry stream being used simply include the column slugs of the assigned columns.

protected $columns = [
    'title',
    'category',
    'description',
];

Just like other UI definitions you can override automation and defaults by including more intableation.

protected $columns = [
    'title' => [
        'heading' => 'Example Title'
    ],
    'category',
    'description',
];
Note: Tables using streams without defined columns will default to the title column (ID by default) only.

Actions

Actions determine available mass actions for the table when 1 or more rows are selected.

Note: Actions extend UI buttons so some actions may use registered buttons to further automate themselves.
The Action Definition

Below is a list of all action specific definition properties available. Since actions extend buttons please refer to UI button documentation for a complete set of options for buttons.

Properties
Key Required Type Default Description

slug

true

string

The definition key.

The action slug helps separate it from others.

permission

false

string

null

The required permission to view/execute the action.

handler

true

string

none

A callable class string or closure. The handler is passed an array of selected IDs from the table as well as the table builder itself.

The Action Registry

Below are the registered basic actions. Note the button options that will in turn automate more action properties. Actions may also simply be buttons and allow default handling behavior. So be sure to refer to the button registry for more options.

Registered actions can be found in Anomaly\Streams\Plattable\Ui\Table\Component\Action\ActionRegistry.

'delete'       => [
    'handler' => Delete::class
],
'prompt'       => [
    'handler' => Delete::class
],
'force_delete' => [
    'button'  => 'prompt',
    'handler' => ForceDelete::class,
    'text'    => 'streams::button.force_delete',
],
'export'       => [
    'button'  => 'info',
    'icon'    => 'download',
    'handler' => Export::class,
    'text'    => 'streams::button.export'
],
'edit'         => [
    'handler' => Edit::class
],
'reorder'      => [
    'handler' => Reorder::class,
    'text'    => 'streams::button.reorder',
    'icon'    => 'fa fa-sort-amount-asc',
    'class'   => 'reorder',
    'type'    => 'success'
]
Using Registered Actions

There are a number of actions registered in the Anomaly\Streams\Plattable\Ui\Table\Component\Action\ActionRegistry class. To use any of these actions simply include their string slug.

protected $actions = [
    'delete',
];

The full definition registered to the above actions is as follows.

protected $actions = [
    'delete' => [
        'handler' => \Anomaly\Streams\Platform\Ui\Table\Component\Action\Handler\Delete::class
    ],
];

After the delete button properties are merged in it looks like this:

protected $actions = [
    'delete' => [
        'icon'       => 'trash',
        'type'       => 'danger',
        'text'       => 'streams::button.delete',
        'attributes' => [
            'data-toggle'  => 'confirm',
            'data-message' => 'streams::message.confirm_delete',
        ],
        'handler' => \Anomaly\Streams\Platform\Ui\Table\Component\Action\Handler\Delete::class
    ],
];
Overriding Registered Actions

Just like other definitions either registered or automated, you can include more information in your definition to override things:

protected $actions = [
    'delete' => [
        'text' => 'Delete rows!'
    ],
];
The Action Handler

Below is an example of the action handler.

<?php namespace Example\Test;

use Anomaly\Streams\Platform\Ui\Table\Component\Action\ActionHandler;

class ExampleHandler extends ActionHandler
{

    public function handle(ExampleTableBuilder $builder, array $selected)
    {
        $model = $builder->getTableModel();

        foreach ($selected as $id) {

            $entry = $model->find($id);

            // Do something here
        }

        if ($selected) {
            $this->messages->success('Something amazing was done!');
        }
    }
}

Options

Table options help configure the behavior in general of the table. Anything from toggling specific UI on or off to adding a simple title and description can be done with the table options.

protected $options = [
    'title' => 'My awesome table!',
    'table_view' => 'module::my/custom/table'
];

You can also set/add options from the API.

$builder->addOption('title', 'Example Title');
Available Options

Below is a list of all available options for tables.

Options
Key Required Type Default Description

table_view

false

string

streams::table/table

The table view is the primary table layout view.

wrapper_view

false

string

streams::blank

The wrapper view is the admin layout wrapper. This is the view you would override if you wanted to include a sidebar with your table for example.

permission

false

string

{vendor}.module.{module}::{stream}.read

The permission string required to access the table.

Database

This section will go over interacting with the database through query builders, migrations, seeders, and more.

Migrations

In general migrations in PyroCMS work just like migrations in Laravel. Except now you can leverage using the streams, fields, and assignments repositories to scaffold your database automatically.

Generating Migrations

You can generate migrations just like you normally would using make:migration with the addition of a few options.

Pro Tip: Migrations in the database/migrations directory are ran last during system installation.
Field Migrations

You can create fields via repositories but you may prefer using the field migration to simplify things. You can create a field migration by including the --addon option and --fields option flag.

php artisan make:migration create_more_fields --addon=example.module.test --fields
Example
<?php

use Anomaly\Streams\Platform\Database\Migration\Migration;

class AnomalyModuleRedirectsCreateRedirectsFields extends Migration
{

    protected $fields = [
        'from'   => 'anomaly.field_type.text',
        'to'     => 'anomaly.field_type.text',
        'status' => [
            'type'   => 'anomaly.field_type.select',
            'config' => [
                'default_value' => '301',
                'options'       => [
                    '301' => 'anomaly.module.redirects::field.status.option.301',
                    '302' => 'anomaly.module.redirects::field.status.option.302',
                ],
            ],
        ],
        'secure' => 'anomaly.field_type.boolean',
    ];

}
Defining the namespace

When creating field you must provide a namespace to create the fields in.

By default the slug of the addon the migration resides in will be used as the namespace.

You can also define the $namespace property if the addon slug does not suffice:

protected $namespace = 'example';

Lastly you can define the namespace inline with the stream and and assignment definitions:

protected $stream = [
    'slug'         => 'widgets',
    'namespaces'   => 'example',
    'title_column' => 'name',
    'translatable' => true,
    'trashable'    => true,
];

protected $assignments = [
    'name'        => [
        'namespace'    => 'example',
        'required'     => true,
        'translatable' => true,
    ],
];
Pro Tip: Being able to set the namespace like this means you can create streams and assign fields for any namespace from anywhere without modifying core code.
Stream Migrations

You can create streams via repositories but you may prefer using the stream migration to simplify things. You can create a stream migration by including the --addon and --stream=stream_slug options.

php artisan make:migration create_example_stream --addon=example.module.test --stream=widgets
Example
<?php

use Anomaly\Streams\Platform\Database\Migration\Migration;

class AnomalyModuleUsersCreateRolesStream extends Migration
{

    protected $stream = [
        'slug'         => 'roles',
        'title_column' => 'name',
        'translatable' => true,
        'trashable'    => true,
    ];

    protected $assignments = [
        'name'        => [
            'required'     => true,
            'translatable' => true,
        ],
        'slug'        => [
            'required' => true,
            'unique'   => true,
        ],
        'description' => [
            'translatable' => true,
        ],
        'permissions',
    ];

}
Defining the namespace

Similar as to when creating fields, when creating streams and the stream assignments you must provide a namespace to create them in.

By default the slug of the addon the migration resides in will be used as the namespace.

You can also define the $namespace property if the addon slug does not suffice:

protected $namespace = 'example';

Lastly you can define the namespace inline with the field definitions:

'status' => [
    'type'      => 'anomaly.field_type.select',
    'namespace' => 'example_namespace',
    'config'    => [
        'default_value' => '301',
        'options'       => [
            '301' => 'anomaly.module.redirects::field.status.option.301',
            '302' => 'anomaly.module.redirects::field.status.option.302',
        ],
    ],
],
Pro Tip: Being able to set the namespace like this means you can create fields for any namespace from anywhere without modifying core code.
Default Migrations

You can also create "normal" migrations by omitting the --fields and --stream options. This will create a migration very similar to Laravel migrations with the exception that the fields, streams, and assignments repositories are all available to use.

Example
<?php

use Anomaly\Streams\Platform\Database\Migration\Migration;

class AnomalyModulePagesMakePagesSearchable extends Migration
{

    public function up()
    {
        $stream = $this->streams()->findBySlugAndNamespace('pages', 'pages');
        
        $stream
            ->setAttribute('searchable', true)
            ->save();
            
        $field = $this->fields()
            ->create(
                [
                    'slug'      => 'content',
                    'namespace' => 'example',
                    'type'      => 'anomaly.field_type.wysiwyg',
                ]
            );
            
        $this->assignments()
            ->create(
                [
                    'field'    => $field,
                    'stream'   => $stream,
                    'required' => true,
                ]
            );
    }

    public function down()
    {
        $stream = $this->streams()->findBySlugAndNamespace('pages', 'pages');
        
        $stream
            ->setAttribute('searchable', false)
            ->save();
            
        if ($field = $this->fields()->findBySlugAndNamespace('content', 'example')) {
            $field->delete(); // Will clean up abandoned assignments.
        }
    }
}

Query Builders

Query builders in PyroCMS work just the same as query builders in Laravel with the addition of a few cool features.

Caching Queries

The query builder in PyroCMS provides a direct API for storing query results in the model's cache collection. The cache collection is automatically cleared when the model's table is altered in any way (such as saving, deleting, etc).

To enable query cacheing you must first set DB_CACHE=true in your .env file and set the cache lifetime.

You can set the cache lifetime in seconds by using the ttl method:

$results = $model->where('status', true)->cache(300)->get();

You can also define the $ttl property on the model you are using for the query:

protected $ttl = 300;
Note: Caching queries uses the resulting query string including bindings to determine uniqueness.
Fetching Fresh Results

If you are leveraging query caching you may want to temporarily omit cache. You can do this by using the fresh method.

$results = $model->where('status', true)->fresh()->get();

Addons

This section will go over the basics of developing addons.

Plugins [Incomplete]

Themes

Themes are addons that control the way the control panel and public facing content look. Two themes can be active at any given time. An admin theme for the control panel and a standard theme for public facing content.

Introduction

This section will introduce you to themes and how they work.

Example Theme

Feel free to fork or reference the starter theme that comes with PyroCMS:

https://github.com/anomalylabs/starter-theme.

For a more robust theme boilerplate you can try the Bootstrap 4 starter theme:

https://github.com/anomalylabs/bootstrap4-theme.

Creating a Theme

Creating a theme is the same as creating any other addon. Simply use the make:addon command:

php artisan make:addon example.theme.test

The new theme will be located at addons/{application}/example/test-theme.

The --shared option may also be used to create the theme in the shared addons directory.

php artisan make:addon example.theme.test --shared

This theme will be located at addons/shared/example/test-theme.

Creating an Admin Theme

In order to make a theme available for use for the control panel you must flag it as an admin theme.

<?php namespace Anomaly\ExampleTheme;

use Anomaly\Streams\Platform\Addon\Theme\Theme;

class ExampleTheme extends Theme
{
    protected $admin = true;
}

Theme Structure

The structure of a theme is consistent with other addons. We'll go over each section and how they are used below.

Resources

The vast majority of theme material is in the resources directory. Just like in other addons the resources directory is registered in the Asset and Image services among others. You can place images, css, scss, less, js, or whatever assets your project requires in the resources directory and access them with Asset and Image services.

Views

The resources/views directory is where all of your theme views and layouts will exist. The addon registers this path hint for the view engine as well:

view('example.theme.test::example/welcome');

The above will match views in the following order:

resources/views/example/welcome.twig
resources/views/example/welcome.blade.php
resources/views/example/welcome.md
resources/views/example/welcome.html

Modules [Incomplete]

Field Types [Incomplete]

Extensions [Incomplete]