Right Way to Add New Walker to Sage 9

That would be great! I just created a repo on Github using the Walker I’d found but yours is probably better since it implements Soil’s walker. Please feel free to fork/PR/whatever.

I’m interested in seeing your implementation if you don’t mind.

I don’t know how my NavWalkers stack up against what’s already out there. I wrote these for my specific needs in two different projects, and they worked for me. Neither were intended for wider distribution, but here you go anyway. One is for Bootstrap and the other is for Foundation.

4 Likes

Maybe it will help someone.
I have situation like this in sage9-beta3.

I created new class file in new folder called Classes inside sage/app/lib/Sage/. I’ve created MainWalker.php file that looks like this:

MainWalker.php

namespace Roots\Sage\Classes;

use Roots\Soil\Nav\NavWalker as NavWalker;

/*
 * @author brunekninja
 */
class MainWalker extends NavWalker 
{
}

After that I just use that class as walker in my new nav menu inside header.blade.php

header.blade.php

<header class="page-header" data-method="stickyHeader">
  <div class="container">
    <nav class="site-navigation">

      @if (has_nav_menu('primary_navigation'))
        {!! wp_nav_menu([
        'theme_location' => 'primary_navigation',
        'menu_class' => 'site-navigation__main-nav',
        'container_class' => 'site-navigation__main-nav',
        'walker' => new Roots\Sage\Classes\MainWalker()
        ]) !!}
      @endif

That’s it, now you can create your custom nav walker in sage 9.
Hope it helps someone.

1 Like

Hi bruno. I tried this approach as it seemed very clean but I’m getting Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found.
Used your code verbatim but am on Sage 9 beta 4 so i created the MainWalker class under /app and included it in functions.php as a required Sage include. Seen a lot of talk of namespace issues :confused:

1 Like

That method requires having Soil installed and activated

yep. i have installed and activated. Can see the clean soil nav structure.

Update, i resolved this with the help of smutek’s gist… FTW!

1 Like

Hey,

yes beta 4 has different structure, and also it is true that this approach requires Soil to be activated. But eventually you can just extend main navwalker class from WP and it has to work.

I checked the new structure, and there is small difference. I just putted navigation folder inside app.

48

Only thing that has to be changed is namespace, and call of nav walker.

So you have situation like this.

namespace App\Navigation;

this is namespace inside MainWalker file.

@if (has_nav_menu('primary_navigation'))
    {!! wp_nav_menu([
     'theme_location' => 'primary_navigation',
     'menu_class' => 'site-navigation__main-nav',
     'walker' => new \App\Navigation\MainWalker()
    ]) !!}     
@endif

And it works!

3 Likes

I followed the instructions of @brunekninja, but have the same problem as @thewhipster: Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found.

I’m using Trellis/Bedrock/Sage 9 beta 4, with Soil installed and activated.

The same question was asked here, but never resolved.

Any other thoughts on why the class can’t be found, even though Soil is installed and activated?

1 Like

Hi, I had that problem on linux server in production, before that everything worked fine in docker etc.
Eventually I’ve added this in composer.json file.

"autoload": {
    "psr-4": {
     "App\\": "web/app/themes/sage/app/",
     "App\\Widgets\\": "web/app/themes/sage/app/widgets/"
    }
 }

After I registered classes this way, error have disappeared

You can try this, this is the only solution that I can think of.

1 Like

I tried brunekninja’s suggestion and several other autoload options in composer.json files for Bedrock, Sage, and Soil without success. I finally got this to work by loading the Foundation walker file on its own after Soil modules are loaded (and not including the Foundation walker in the Sage required files array). At the bottom of soil.php the modules are loaded with after_setup_theme, priority 100. So my Sage functions.php file now has this:

/**
 * Sage required files
 *
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
 */
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']);

/**
 * Foundation Nav Walker
 *
 * Load the Foundation nav walker after Soil plugin modules are loaded so that Soil's NavWalker class is available.
 */
add_action( 'after_setup_theme', 'add_foundation_nav_walker', 101 );
function add_foundation_nav_walker() {

    if ( class_exists('\Roots\Soil\Nav\NavWalker') ) {

        require get_template_directory() . '/../app/nav-foundation-walker.php';
    }
}

I’m new to psr-4 and namespaces in php, but I gather that the reason for autoloading with psr-4 is to allow classes to be available independently of how they might otherwise be loaded via require, etc. Soil’s compser.json file currently doesn’t autoload anything. Should it include the following?

"autoload": {
    "psr-4": { "Roots\\Soil\\Nav": "modules" }
 },

When I try that and run composer update in Soil, the generated autoload file at soil/vendor/composer/autoload_psr4.php file correctly shows:

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Roots\\Soil\\Nav\\' => array($baseDir . '/modules'),
    'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
);

But if I then load nav_foundation_walker.php via the Sage required array I still get “Fatal error: Class ‘Roots\Soil\Nav\NavWalker’ not found” when loading the site in my browser. I assume the correct way to do this is with autoloading, but I’m not sure how to make that work.

1 Like

I’m having a somewhat similar problem. I have my Walkers working on development with no problems, but when I upload the theme to the prod server, I get the same “Walker not found error”. Weird.

1 Like

You need to run composer install in the theme directory when you deploy a Sage 9 theme:

That was in reply to Right Way to Add New Walker to Sage 9, right? Running composer install in the theme doesn’t solve my problem.

Hard to help without anything to go off.

Please provide the exact code that you are using/changes that you have made to Sage.

Hi Ben! Thanks - will try.

I’m running Windows 10 with Trellis/Bedrock/Sage 9-beta.4, and Soil installed via composer and activated.

In sage/app/ I’ve added a file nav-foundation-walker.php with QWp6t’s Foundation.php walker. So the file looks like this:

<?php 
namespace App;

use Roots\Soil\Nav\NavWalker as SoilNavWalker;

/**
 * Foundation 6 Navigation Walker
 *
 * @author QWp6t
 * @license OSL-3.0
 * @see https://gist.github.com/QWp6t/8f94b7096bb0d3a72fedba68f73033a5
 */

class FoundationWalker extends SoilNavWalker
{
    public function __construct()
    {
        parent::__construct();
        remove_filter('nav_menu_css_class', [$this, 'cssClasses'], 10);
        add_filter('nav_menu_css_class', [$this, 'itemClasses'], 10, 4);
    }
    /**
     * @param string $output
     * @param int $depth
     * @param array $args
     * @SuppressWarnings(PHPMD.CamelCaseMethodName) This method overrides its parent
     * @SuppressWarnings(PHPMD.UnusedFormalParameter) This method overrides its parent
     */
    // @codingStandardsIgnoreLine
    public function start_lvl(&$output, $depth = 0, $args = [])
    {
        $output .= '<ul class="submenu menu" data-submenu>';
    }
    /**
     * @param $classes
     * @param $item
     * @param $args
     * @param $depth
     * @return array
     * @SuppressWarnings(PHPMD.UnusedFormalParameter) This method overrides its parent
     */
    public function itemClasses($classes, $item, /** @noinspection PhpUnusedParameterInspection */ $args, $depth)
    {
        return array_filter(array_map(function ($class) use ($depth) {
            switch ($class) {
                case 'menu-item-has-children':
                    return 'has-submenu';
                default:
                    return $class;
            }
        }, parent::cssClasses($classes, $item)));
    }
}

Then in sage/resources/functions.php I added nav-foundation-walker to the Sage required files:

/**
 * Sage required files
 *
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
 */
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', 'nav-foundation-walker']);

When I load the site in a browser I get the error:

Fatal error: Class 'Roots\Soil\Nav\NavWalker' not found in /srv/www/test.com/current/web/app/themes/sage/app/nav-foundation-walker.php

How do I use Composer and autoloading so that Roots\Soil\Nav\Navwalker is available in nav-foundation.php?

I’ve tried modifying Soil’s composer.json file by adding:

"autoload": {
    "psr-4": {
      "Roots\\Soil\\Nav\\": "modules/"
    }
  },

And I’ve tried modifying Sage’s composer.json file by adding:

"autoload": {
    "psr-4": {
      "App\\": "app/",
      "Roots\\Soil\\Nav\\": "../../plugins/Soil/modules/"
    }
  },

Each time I ran composer update to create a new vendor/autoload_psr4.php file. I also tried something similar in Bedrock’s composer.json file. None of these worked. What am missing here?

1 Like

Any update on this? I’ve just updated from Sage 9 beta 3 (worked fine) to beta 4 and this kind of problems are popping up all over the place.

@krishaamer No fixes yet on my end. I assume I just need a better understanding of autoloading, but I haven’t had a chance to dig in to it. I did start reading a tutorial on autoloading by Alan Storm that could be helpful.

1 Like

I now have QWp6t’s Foundation.php walker working using psr-4 autloading, instead of by requiring the foundation walker file after the Soil plugin has loaded using after_setup_theme().

The problem is that Soil’s NavWalker class is in a file named nav-walker.php. According to the psr-4 spec #3.3 listed here:

“When loading a file that corresponds to a fully qualified class name, the terminating class name corresponds to a file name ending in .php. The file name MUST match the case of the terminating class name.”

So the nav-walker.php file name and NavWalker class name need to match.

I did the following:

  1. Changed the file name from nav-walker.php to NavWalker.php.

  2. Soil’s modules are loaded based on file name, so in Sage’s setup.php file I changed line 28 to add_theme_support('soil-NavWalker').

  3. Added the following to Bedrock’s composer.json file (multi-line code formatting isn’t working in ordered lists):

    “autoload”: {
    “psr-4”: {
    “Roots\Soil\Nav\”: “web/app/plugins/soil/modules/”
    }
    }

  4. Ran composer update and confirmed the mapping had been added to Bedrock’s /vendor/composer/autoload_psr4.php.

  5. Finally, in Sage’s functions.php file I removed the after_setup_theme() hook I had previously used to load the foundation walker file and instead added the file to Sage’s required files array:
    ['helpers', 'setup', 'filters', 'admin', 'theme-options', 'nav-foundation-walker']

And voila, it works!

It would be great if the Soil changes could be merged so that it can work with autoloading.

Merry Christmas! (if that’s your thing)

4 Likes