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"
Notice: The Streams Platform is currently compatible with Laravel 5.3 only.

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

The Streams Plugin is the primary plugin shipped with the Streams Platform. You can read it's documentation here: http://pyrocms.com/documentation/streams-plugin/v1.1

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() }}

Routing

This section will describe how to define routes in PyroCMS. It helps to know how to route in Laravel already.

Laravel Routing

While most routing in Pyro will occur within your addon service provider you can still route as you normally would with Laravel using the /routes directory.

Route::get('example', function () {
    return view('theme::hello');
});

To learn more about native routing please refer to Laravel documentation.

Addon Service Providers

Most routing is specified within the relevant addon's service provider using the $routes and $api properties.

The most basic route definition looks like this:

protected $routes = [
    'example/uri' => 'Example\ExampleModule\Http\Controller\[email protected]',
];
The Route Definition

Below is a list of all routes definition properties available.

Definitions are always keyed by the URI path/pattern like this:

protected $routes = [
    'posts/{slug}' => $definition,
];
Properties
Key Required Type Default Description

uses

true

string

none

The Controller\[email protected] string handling the response.

as

false

string

none

The name of the route.

verb

false

string

any

The request verb to route. Available options are get, post, put, patch, delete, and options

middleware

false

array

[]

An array of additional middleware to run for the route.

contraints

false

array

[]

An array of parameter constraints to force on any segment parameters.

streams::addon

false

string

The addon providing the route.

The dot namespace of the addon responsible for the route. This is used in setting the active module during a request.

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
  • version: appends an automated version ID to the published path

Example

$asset->path('theme.css', ['version']);

Twig

{{ asset_style('theme.css', ['version']) }}
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.

Messages

The Anomaly\Streams\Platform\Message\MessageBag class helps flash messages to users like validation errors and success messages.

Basic Usage

Include the Anomaly\Streams\Platform\Message\MessageBag class in your code to get started.

MessageBag::has()

The has method returns whether or not the message bag has any messages of the specified $type.

Returns: bool
Arguments
Key Required Type Default Description

$type

true

string

none

The type of message to check for. Available options are success, info, warning, and danger.

Example
if ($messages->has('success')) {
    die('Oh goodie!');
}
MessageBag::get()

The get method returns all messages of the specified $type.

Returns: array
Arguments
Key Required Type Default Description

$type

true

string

none

The type of message to check for. Available options are success, info, warning, and danger.

Example
foreach ($messages->get('success') as $message) {
    echo $message . '<br>';
}
MessageBag::pull()

The pull method pulls all messages of a specified $type out of the message bag. Removing them from the session data.

Returns: array
Arguments
Key Required Type Default Description

$type

true

string

none

The type of message to check for. Available options are success, info, warning, and danger.

Example
foreach ($messages->pull('success') as $message) {
    echo $message . '<br>'
}
MessageBag::error()

The error method pushes an error message into the message bag.

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

$message

true

string

none

The error message to display.

Example
$messages->error('Ah snap! It broke.');
MessageBag::info()

The info method pushes an informational message into the message bag.

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

$message

true

string

none

The informational message to display.

Example
$messages->info('You know what? Ya me neither.');
MessageBag::warning()

The warning method pushes a warning message into the message bag.

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

$message

true

string

none

The warning message to display.

Example
$messages->warning('You had better watch it sparky.');
MessageBag::success()

The success method pushes a success message into the message bag.

Arguments
Key Required Type Default Description

$message

true

string

none

The success message to display.

Example
$messages->success('You win!');

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.

stacked

false

boolean

false

If true then tabs will stack vertically.

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.

heading

false

string

The filter assignment name.

The filter label.

placeholder

false

string

The filter label.

The filter input placeholder.

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();

Models

Models in Pyro extend Laravel's Eloquent models. This section will go over some addition features available for entry models and our base eloquent models as well.

Entry Routers

Entry routers assist in binding named routes to stream entries. This makes it much easier to override routing by simply defining another route with the same name.

The basic idea is as that instead of something like this in your code:

{{ url('articles/' ~ category.slug ~ '/view/' ~ article.slug) }}

You can rely on entry routing to simply do this:

{{ article.route('view') }}

And via API this would look like:

$article->route('view');

Basic Usage

To get started with entry routing you must first define your routes with a specific naming pattern. Note this is not referring to the path but the route name. We'll use an AddonServiceProvider routing example:

protected $routes = [
    "posts/{slug}"                                              => [
        'as'   => 'anomaly.module.posts::posts.view',
        'uses' => 'Anomaly\PostsModule\Http\Controller\[email protected]',
    ],
];

Note the above route is named anomaly.module.posts::posts.view. This can be broken down into the addons namespace, then the entry's stream slug, and lastly the route action.

{namespace}::{stream}.{action}

By calling route($action) on an entry model Pyro will lookup what addon the model belongs to and find use it's stream slug and action to return the route path.

EntryRouter::make()

The make method returns the entry model's matching route. The name variable can be simply an action or the stream slug and action of another named route within the same addon.

The entry's route method directly wraps make hence $entry->route($action).

Returns: string or null
Arguments
Key Required Type Default Description

$route

true

string

none

The route name. Can be simply action if route is for the calling model or stream.action if route is in calling model's same addon.

$parameters

false

array

none

Query string parameters appended to the route.

Example
$article->route('view');

$article->route('categories.index');

// Equivalent long syntax.
$article->getRouter()->make('view');

$article->getRouter()->make('categories.index');
Twig
{{ article.route('view') }}

{{ article.route('categories.index') }}

Addons

Addons are the primary building blocks of PyroCMS. Logically, Pyro is comprised of and organized as Laravel, the Streams Platform, and any addons.

The Basics

This section will go over the basic behavior of addons in general.

Addon Locations

This section will go over where addons can be loaded from and the difference between core and application addons.

Core Addons

All addons listed in the composer.json file will be installed by composer in the /core directory similar to the /vendor directory.

Heads Up! Only addons required by the root composer.json file will resolve dependencies required by the addon's composer.json file.
Application Addons

All non-core addons are considered application addons and are located in the /addons directory.

Note: Application addons should be committed to the project's repository.

Application addons are split into private addons and shared addons.

Private Addons

Private addons are located within /addons/{APP_REF} directory and are organized by vendor just like the /core and /vendor directories.

Private addons are only available to the application designated by the {APP_REF} directory in which they reside.

Shared Addons

Shared addons are located within /addons/shared directory and are organized by vendor just like the /core and /vendor directories.

Shared addons are available to all applications within the PyroCMS installation.

Packaged Addons

Addons can include their own addons. While it is not common the Grid and Repeater field types are good examples of an addon that come packaged with it's own dependent addons.

Addons can be registered anywhere but when using this technique they are usually found within the /addons directory within the addon itself.

Addon Object

All addon types extend the base Anomaly\Streams\Platform\Addon\Addon class and inherit some basic functionality.

Addon::isCore()

The isCore method returns whether the addon is core or not.

Returns: bool
Example
if ($addon->isCore()) {
	echo 'Yep!';
}
Twig
{% is addon.isCore() %}
	Yep!
{% endif %}
Addon::isShared()

The isShared method returns if the addon is shared or not.

Returns: bool
Example
if ($addon->isShared()) {
	echo 'Yep!';
}
Twig
{% addon.isShared() %}
	Yep!
{% endif %}
Addon::getName()

The getName method returns the translatable name of the addon.

Returns: string
Example
echo trans($addon->getName());
Twig
{{ trans(addon.getName()) }}
Addon::getTitle()

The getTitle method returns the title which generally similar to the name but does not include the addon type.

Returns: string
Example
echo trans($addon->getTitle());
Twig
{{ trans(addon.getTitle()) }}
Addon::getDescription()

The getDescription method returns the addon description.

Returns: string
Example
echo trans($addon->getDescription());
Twig
{{ trans(addon.getDescription()) }}
Addon::getNamespace()

The getNamespace method returns the addon's dot namespace with an optional key.

This is helpful for creating config keys, language keys, hinted view paths, and anything else prefixed by the addon's dot namespace.

Returns: string
Arguments
Key Required Type Default Description

$$key

false

string

none

The key to append to the namespace.

Example
$addon->getNamespace(); // anomaly.module.pages
$addon->getNamespace('config.limit') // anomaly.module.pages::config.limit
Twig
{{ addon.getNamespace() }} // anomaly.module.pages
{{ addon.getNamespace('config.limit') }} // anomaly.module.pages::config.limit

{{ config(addon.getNamespace('config.limit'), 100) }}

Addon::getPath()

The getPath method returns the addon's installed path.

Returns: string
Example
require_once $addon->getPath() . '/some/old-timer/file.php'
Addon::getAppPath()

The getAppPath returns the relative application path of the addon.

Returns: string
Example
require_once base_path($addon->getPath() . '/some/old-timer/file.php');
Addon::getType()

The getType returns the addon type in singular form.

Returns: string
Example
if ($addon->getType() == 'field_type') {
	echo "I'm a field type!";
}
Twig
{% if addon.getType() == "field_type" %}
	I'm a field type!
{% endif %}
Addon::getSlug()

The getSlug method returns the slug of the addon.

Returns: string
Example
echo $addon->getSlug(); // pages
Twig
{{ addon.getSlug() }} // pages
Addon::getVendor()

The getVendor method returns the vendor string of the addon.

Returns: string
Example
echo $addon->getVendor(); // anomaly
Twig
{{ addon.getVendor() }} // anomaly

Field Types

Field types are responsible for rendering form inputs and managing data transportation in and out of the database and it's models.

Displaying Inputs

The main aspect of field types from an end-user perspective is the form inputs they provide. This section will go over how to render inputs and filters for field types.

FieldType::render()

The render method returns the input wrapped in a field group wrapper for use in a form. This output method includes the label, required flag, instructions, warning, and the input.

Returns: string
Twig
{{ field_type.render()|raw }}
FieldType::getInput()

The getInput returns the rendered input view used for forms. The view rendered is determined by the field type's $inputView property.

This method returns only the input view. No surrounding field group wrapper.

Returns: string
Twig
{{ field_type.getInput()|raw }}
FieldType::getFilter()

The getFilter method returns the rendered input view for table filtering.

Returns: string
Twig
{{ field_type.getFilter()|raw }}

Presenters

Field type presenters decorate the field type and it's contained value. Because objects are automatically decorated on the way to views field type presenters will always be returned for entry values by default.

{{ entry.attribute }} // The field type presenter::__toString() {{ entry.attribute.value }} // The raw value from the field type presenter

It is because of this you must using .value within if statements.

FieldType::getPresenter()

The getPresenter method returns a new presenter instance. By default method uses class transformation to convert YourFieldType class to YourFieldTypePresenter.

Returns: Anomaly\Streams\Platform\Addon\FieldType\FieldTypePresenter
Example
$fieldType->getPresenter()->foo();

Modifiers

Modifiers are classes that modify values for database storage and restore them before hydrating the model. By default no modification is done.

FieldType::getModifier()

The getModifier method returns a new modifier instance. By default this method uses class transformation to convert YourFieldType to YourFieldTypeModifier.

Returns: Anomaly\Streams\Platform\Addon\FieldType\FieldTypeModifier
FieldTypeModifier::modify()

The modify method modifies the $value for storage in the database.

Returns: mixed
Arguments
Key Required Type Description

$value

true

mixed

The value as provided by the field type setting the attribute

Example
public function modify($value)
{
	return serialize((array)$value);
}
FieldTypeModifier::restore()

The restore method restores the value from the database.

Returns: mixed
Arguments
Key Required Type Description

$value

true

mixed

The value from the database.

Example
public function restore($value)
{
	if (!$value) {
		return [];
	}

	if (is_array($value)) {
		return $value;
	}

	return (array)unserialize($value);
}

Accessors

Accessors are responsible for setting the value data on the entry model. By default the value is set on the model as an attribute with the same name as the field.

$entry->{field} = $value;

FieldType::getAccessor()

The getAccessor method returns a new accessor instance. By default the method uses class transformation to convert YourFieldType to YourFieldTypeAccessor.

Returns: Anomaly\Streams\Platform\Addon\FieldType\FieldTypeAccessor
FieldTypeAccessor::set()

The set method set's the $value on the entry.

Returns: void
Arguments
Key Required Type Description

$value

true

mixed

The value from the field type modifier.

Example
public function set($value)
{
	$entry = $this->fieldType->getEntry();
	$attributes = $entry->getAttributes();
	if (is_numeric($value)) {
		$attributes[$this->fieldType->getColumnName()] = $value;
	}
	if (is_object($value) && $data = $this->toData($value)) {
		$attributes[$this->fieldType->getField() . '_data'] = json_encode($data);
	}
	if (is_array($value) && $data = $this->toData($value)) {
		$attributes[$this->fieldType->getField() . '_data'] = json_encode($data);
	}
	if (is_null($value)) {
		$attributes[$this->fieldType->getColumnName()]      = $value;
		$attributes[$this->fieldType->getField() . '_data'] = $value;
	}
	$entry->setRawAttributes($attributes);
}
FieldTypeAccessor::get()

The get method get's the value off the entry.

Returns: mixed
Example
public function get()
{
	$entry = $this->fieldType->getEntry();

	$attributes = $entry->getAttributes();

	return [
		'image' => array_get($attributes, $this->fieldType->getColumnName()),
		'data' => array_get($attributes, $this->fieldType->getColumnName() . '_data')
	];
}

Schema

Field type schema classes help control the schema changes required by the field type.

FieldType::getSchema()

The getSchema method returns a new schema instance. By default this method uses class transformation to convert YourFieldType class to YourFieldTypeSchema.

Returns: Anomaly\Streams\Platform\Addon\FieldType\FieldTypeSchema
FieldTypeSchema::addColumn()

The addColumn method is responsible for adding the column required by the field type to the database table. By default this is automated and adds a column named after the field_slug and uses the column type as defined by the field type.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

$assignment

\Anomaly\Streams\Platform\Assignment\Contract\AssignmentInterface

The assignment object representing the assigned field.

FieldTypeSchema::updateColumn()

The updateColumn updates the columns features as required by the field or assignment changes.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

$assignment

\Anomaly\Streams\Platform\Assignment\Contract\AssignmentInterface

The updated assignment object representing the assigned field.

FieldTypeSchema::renameColumn()

The renameColumn is responsible for renaming the field type column(s) when a field object is updated.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

$from

\Anomaly\Streams\Platform\Addon\FieldType\FieldType

The field type from the updated fields.

INTERNAL $to

\Anomaly\Streams\Platform\Addon\FieldType\FieldType

The current field type is always available as $this->fieldType

FieldTypeSchema::changeColumn()

The changeColumn method changes the column type as needed when changing the field type for a field.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

$assignment

\Anomaly\Streams\Platform\Assignment\Contract\AssignmentInterface

The updated assignment object representing the changed field.

FieldTypeSchema::dropColumn()

The dropColumn method drops the field type's column from the database table when the assignment is deleted.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

FieldTypeSchema::backupColumn()

The backupColumn method temporarily backs up the column data in cache.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

FieldTypeSchema::restoreColumn()

The restoreColumn restores the backup data from the backupColumn method.

Returns: void
Arguments
Key Type Description

$table

\Illuminate\Database\Schema\Blueprint

The table blueprint utility.

Parsers

Field type parsers allow you to control what is parsed on the compiled entry model when the stream, assignments, or fields are modified. This method will also fire during manual compiling with the streams:compile artisan command.

FieldType::getParser()

The getParser method returns a new parser instance. By default this method uses class transformation to convert YourFieldType class to YourFieldTypeParser.

Returns: Anomaly\Streams\Platform\Addon\FieldType\FieldTypeParser
FieldTypeParser::relation()

The relation method is only ran when the field type provides a getRelation method.

Returns: string
Arguments
Key Type Description

$assignment

\Anomaly\Streams\Platform\Assignment\Contract\AssignmentInterface

The assignment object representing the assigned field.

Query Builders

The field type query builders provide methods for manipulating query builders. These methods are using for filtering field type values and for extending the functionality of the query builder through the field type.

FieldTypeQuery::filter()

The filter method filters a query builder with the value provided by a table filter interface.

Returns: void
Arguments
Key Type Description

$query

\Illuminate\Database\Eloquent\Builder

The query builder for the table entries.

$filter

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

The table filter interface.