Best Practice / Resources for Blade

That’s probably the issue… PHP doesn’t know where to find the class because you’re not following the autoloading conventions.

Sage is autoloaded with PSR-4: https://github.com/roots/sage/blob/681ad997a2d56ddc2a7fad135ab03e21ff56f565/composer.json#L29-L33

I would put that class in app/Traits, with the namespace App\Traits, and load it in other files use App\Traits\TraitA, which should get it autoloaded correctly. Just remember, PSR-4 basically means having your namespaces follow your folder structure.

1 Like

Doesn’t Controller suggest the use of the partials directory, though?

Yes, apparently SoberWP does… I’ll have to take a closer look…

Controller doesn’t require the use of PSR4 loading, anything within the controllers/ directory is included, and it will determine traits from classes, and then load the traits first.

I’ll do some testing re using traits outside of Controller and see what I can come up with, but I’m fairly confident it has to do with the order.

1 Like

Can I ask what the thought process is behind loading everything in the controllers folder and not following PSR-4? I apologize if this has been discussed previously, I’ve been busy the last couple months moving, haven’t been keeping up with Roots stuff, just starting to catch up now.

Possible related: I’ve run into an issue where Controller’s classes aren’t loaded when a Sage-based theme is installed via Composer.

Controller uses some info from the includes for the loader, functions like get_declared_classes(). I am going to look into using PSR4 though, because if I could get it working it could reduce some code from class Loader.php, and as you mentioned, would help standardise it more to be in line with Sage.

3 Likes

Ok, when I manually load the Trait file like this:

array_map(function ($file) use ($sage_error) {
	$file = "../app/{$file}.php";
	if (!locate_template($file, true, true)) {
		$sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
	}
}, ['helpers', 'setup', 'filters', 'admin', 'controllers/partials/TraitA']);

I can use the Trait like this:

App\TraitA::getPosts();

without setting use App\TraitA

1 Like

Cool, so it’s just a matter of the order it’s being loaded in, as I thought would be the case.

If we could get Controller to use PSR4 that should resolve. I will look into it this weekend.

4 Likes

Looks like PSR4 loading is possible with changes to the Loader.

Is everyone for using PSR4? It seems like it would be the correct approach, but would be good to hear feedback prior to implementing a ‘breaking’ change. (this will live in dev-master until a new tag/release is done)

You would need to make changes to your Controller files though.

  • The namespace would change to App\Controllers and to App\Controllers\Partials (to match the folder names, as per PSR4 autoloading standards)
  • For using traits within Controller files, you would need to specify the added namespace, or let them live in the same directory.

app/controllers/About.php

<?php

namespace App\Controllers;

use Sober\Controller\Controller;

class About extends Controller
{
    use Partials\TraitName;
}

app/controllers/partials/Testing.php

<?php

namespace App\Controllers\Partials;

trait Testing
{
    public function example()
    {
        return 'Example';
    }
}

@Twansparant this should as a result solve the order issue as well, but not tested yet.

5 Likes

I submitted a PR to bump illuminate/support to 5.5 which includes the new @switch statements and my personal favorite, custom if statements.

The custom if statements work similar to the directives that I posted examples of above, but instead can simplify if statements dramatically.

A small example would be checking if an ACF field exists. Before, our directive would look somewhat like this:

$sage = sage('blade')->compiler();

/**
 * Create @hasField() Blade directive
 */
$sage->directive('hasField', function ($expression) {
    $expression = strtr($expression, ['(' => '', ')' => '']);
    return "<?php if (Acf::field($expression)->get()) : ?>";
});

/**
 * Create @endField Blade directive
 */
$sage->directive('endField', function () {
    return "<?php endif; ?>";
});

Not too bad, but a little dramatic to make two separate directives, and if you’re anything like me, to achieve a clean Blade syntax I was ending up with dozens of Blade directives that were simply an endif.

With the new Custom If Statements, that shouldn’t be such an issue anymore. To achieve the above, we simply do:

$sage = sage('blade')->compiler();

/**
 * Create @hasField() Blade directive
 */
$sage->if('hasField', function ($field) {
    return Acf::field($field)->get();
});

and this alone will automatically create an @endHasField() directive allowing you to quickly check if your field exists.

To clarify, ->if() will always expect a boolean.

[note: above examples are using acf-fluent.]

Edit: ->if() might have potential issues with Sage at the moment. See here

3 Likes

@Log1x have you found a workaround for ->if() yet?

After reading and using a lot of what was discussed here, I thought there could be an easier way. I created an extension package named Sage Xpress which is available on GitHub.

There are Blade Directives for loop, query, menu, sidebar, etc. An optional config/blade-directives.php is available for you to easily extend with your own directives. [umm, wouldn’t let me put at-signs in front of the directives – thinks I am mentioning users – LOL]

A MenuProvider which is completely configurable via config\menu.php and using the @menu('menuId') in your blade file. The provider handles the registration and rendering of the menu. No controller file needed, no setup functions needed, easy-peasy.

I will be adding providers for Sidebar, Shortcodes, and whatever you guys can come up with and think would be handy.

Setup is easy. Composer require the package and add one line of code to your setup.php. You don’t have to register your menus there, so you can safely delete that action. Config files are automatically added to sage()['config'] so no messing with functions.php either.

This package is supposed to copy the config stubs into your config directory if they don’t exist, but I have not fully tested this yet. It is my first composer package using scripts – crossing my fingers. If they do not get copied over, you can find them in the vendor/webstractions/sage-xpress/config directory.

EDIT: I have a problem with merging custom blade directives with the BladeDirectives provider right now. Working on it.

2 Likes

I just added Schema and Attribute support to Sage Xpress. As an example, it allows you to do something like this for a single post

    <article @schema('entry')>

      <header class="entry-header">
        @include('partials.entry.title')
        <div class="entry-meta entry-byline">
          <span @schema( 'entry-author' )>
            @php( the_author_posts_link() )
          </span>
          <span @schema( 'entry-published' )>
            <?= get_the_date(); ?>
          </time>
          </span>
          <a href="@php(comments_link())" class="comments-link post-comments">
            @php( comments_number( esc_html__( 'No comments','sage' ), esc_html__( 'One comment','sage' ), esc_html__( '% comments','sage' ) ) )
          </a>
        </div>
      </header>

      <div @schema('entry-content')>
        @php(the_content())
      </div>

    </article>

This provider will spit out schema.org markup and basic classes. It is based on Justin Tadlock’s hybrid_attr() function from Hybrid Core. He has removed the schema.org portion from his later releases.

The classes that are injected are slightly opinionated, but are easily over-ridden with filters.

After looking at all of the ACF directives, I am working on a similar provider with just one directive @acf('slug') that will accomplish the same thing. If anyone would like to help, drop me a note.

1 Like

I’m using Sage 9 for the first time, and am confused about the About.php and Home.php controllers in sage/app/controllers/.

After reading the Sober documentation, I realised that Home.php would be used when the exact WordPress template was home.php. Neither front-page or page-home would suffice; I would need a controller called Front-page.php or Page-home.php for those templates.

Still, I am confused by the presence of About.php - which template will this be activated on, or is it simply supposed to be used statically?

Hope someone can shed some light!

Also - should I name my Front-page.php controller as FrontPage, or something else?

Regarding your last two replies:

3 Likes

I didn’t know about the new controller file structure until I did a composer update and everything broke. But after I moved all the controllers to app/controllers it still does not work. I get this error:

Uncaught ReflectionException: Class App\\Controllers\\template-home does not exist in /app/wp-content/themes/asdf/vendor/soberwp/controller/controller.php

Full error below

[Mon Oct 30 16:05:49.409392 2017] [:error] [pid 1366] [client 172.17.0.1:43296] PHP Fatal error:  Uncaught ReflectionException: Class App\\Controllers\\template-home does not exist in /app/wp-content/themes/asdf/vendor/soberwp/controller/controller.php:19\nStack trace:\n#0 /app/wp-content/themes/asdf/vendor/soberwp/controller/controller.php(19): ReflectionClass->__construct('App\\\\Controllers...')\n#1 /app/wp/wp-includes/class-wp-hook.php(298): Sober\\Controller\\loader('')\n#2 /app/wp/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters(NULL, Array)\n#3 /app/wp/wp-includes/plugin.php(453): WP_Hook->do_action(Array)\n#4 /app/wp/wp-settings.php(448): do_action('init')\n#5 /app/wp-config.php(125): require_once('/app/wp/wp-sett...')\n#6 /app/wp/wp-load.php(42): require_once('/app/wp-config....')\n#7 /app/wp/wp-blog-header.php(13): require_once('/app/wp/wp-load...')\n#8 /app/index.php(4): require('/app/wp/wp-blog...')\n#9 {main}\n  thrown in /app/wp-content/themes/asdf/vendor/soberwp/controller/controller.php on line 19, referer: http://localhost:3000/about-us/management-team/```

@YarGnawh If you did a direct copy of the controllers, make sure to check your namespace and class names.

Your file should be app/controllers/template-home.php with something along this in the file:

<?php
namespace App;

use Sober\Controller\Controller;

class TemplateHome extends Controller
{
    //
}
1 Like

hey @Webstractions, thanks for the help. That’s exactly what I have

<?php

namespace App;

use Sober\Controller\Controller;

class TemplateHome extends Controller
{
	public function heroCarousel()
    {
        return get_field('hero_carousel');
    }
}

One thing I did find out is that, in my composer.json soberwp/controller was using dev-master. After changing it to ~9.0.0-beta.4 , everything started working again. So I guess it’s something in the changes after beta4 that is breaking it.


  "require": {
    "php": ">=7",
    "composer/installers": "~1.0",
    "illuminate/support": "~5.4",
    "roots/sage-lib": "~9.0.0-beta.4",
    "soberwp/controller": "~9.0.0-beta.4"
  },
1 Like