Getting @inject Blade directive to work with Sage9

If anyone has tried to use the @inject directive in a Blade file, they have probably noticed that it will fail. The reason for this is that the directive looks for services that are bound to the Laravel Application container. Specifically, it is using the app() helper from Laravel.

To get around this, we need to overwrite that directive in setup.php just after the Blade Provider is bound to the Sage Container.

   /**
     * Overwrite @inject() Blade directive
     * IMPORTANT: This needs to follow the binding of the Blade Provider above.
     */
    sage('blade')->compiler()->directive('inject', function($expression) {
        $segments = explode(',', preg_replace("/[\(\)\\\"\']/", '', $expression));
        $variable = trim($segments[0]);
        $service = trim($segments[1]);
        return "<?php \${$variable} = App\sage('{$service}'); ?>";
    });

Now you can bind a service to the Sage Container (again in setup.php)

   /**
     * Bind a service to container
     */
    sage()->singleton('sage.acfservice', function () {
        return new \App\Acf\Acf();
    });

Create your service class. In this example, I am creating a simple ACF service class that will just dump data from an ACF function. You will probably do more with this data, like transformations, date formatting, and such.

<?php

namespace App\Acf;

class Acf
{

    public function __construct()
    {
        //
    }

    /**
     * Get field objects.
     *
     * @return array $data
     */
    public function getFieldObjects()
    {
        return \get_field_objects();
    }
}

Now the fun stuff. Create a reusable ACF component, in this example it will be a simple ‘card’ in resources\views\acf\card.blade.php. (This is a Bootstrap4 card, btw)

@inject( 'acf', 'acfservice' )

@if($acf)
  @if($fields = $acf->getFieldObjects() )
    <div class="card" style="width: 20rem;">
      <div class="card-body">
        <h4 class="card-title">{{$fields['caption']['value']}}</h4>
        <h6 class="card-subtitle mb-2 text-muted">{{$fields['location']['value']}}</h6>
        <span class="badge badge-info">{{$fields['mood']['value']}}</span>
      </div>
    </div>
  @endif
@else
  <div class="alert alert-warning" role="alert">
    Acf object not found!
  </div>
@endif

There are few things happening in this component. We are injecting the ACF service (acfservice) into the variable $acf which can be used by the component. Then we do the obligatory check to see if anything was passed back (the service will return false instead of an array if the field objects are not a part of the post).

Even if an ACF field group exists, it will pass an empty array if the user did not put a value into any one field. So we do the a simple check and assign statement @if($fields = $acf->getFieldObjects() ). Now we can use the data.

You can reuse this component anywhere in your Blade files with a simple @include. Please note that I have a custom directive for @loop in this example.

@extends('layouts.app')

@section('content')
  @loop
    @include('partials.page-header')
    @include('content.page')

    {{--  Acf card  --}}
    @include('acf.card')

  @endloop
@endsection

While @inject may not be appropriate for everything, there are a number of situations where this technique will prove to be quite useful. As they say over in Laravel-land, “With power, comes great responsibility”

1 Like

Help me out here because I think I’m totally missing the point.

In the above example, could you not just do this in a controller (or trait if you wanted to use across several controllers)?

Controller:

<?php

namespace App;

use Sober\Controller\Controller;

class About extends Controller
{
    public function fields()
    {
        return get_fields();
    }
}

Then the view would be the same:

@if($fields)
    <div class="card">
        <div class="card-body">
        <h4 class="card-title">{{$fields['caption']['value']}}</h4>
        <h6 class="card-subtitle mb-2 text-muted">{{$fields['location']['value']}}</h6>
        <span class="badge badge-info">{{$fields['mood']['value']}}</span>
        </div>
    </div>
@else
    <div class="alert alert-warning" role="alert">
        Fields not found!
    </div>
@endif

First, this was more about injection. And true, you could just as easily use a controller.

I think using injection inside of service components has some perks. They are plug-and-play. Don’t have to worry about what template/controller they are in.

Perhaps using ACF as an example wasn’t that great :grin:

Let’s say you have an Ad service class that handles configuration, networks, etc. Now you can build injectable components in Blade, and plug them anywhere throughout your views.

The components are independent of any controller or template. All you need to do is @include your Blade component.