Roots Discourse

Why is my View Composer not returning data to its assigned View?

Im missing some key understanding of what is going on under the hood and im hoping someone can enlighten me.

  • I have a directory: app/Components.
  • I have a test component folder (app/Components/Test)
  • The test components has its own View Composer (app/Components/Test/Test/.php) that is namespaces as App\Components\Test
  • It extends composer: class Test extends Composer
  • It defines the view it should be tied to:
protected static $views = [
     'partials.test'
];
  • This view is loading, and displaying.
  • none of the data in the with() method is being returned.

So, im clearly confused and looking for clarification on:

  • Can you use composers outside of the main Composers folder this way?
  • If so, why is it not being trigged when the view is being called?

Any help appreciated!

1 Like

I’m suddenly having this same issue with the composer for the Menu…

:thinking:

View Composers were not intended to be mixed with View Components– and it’s not necessarily clear to me a situation where they should be.

Sage 10’s Composers are provided by Acorn as a means to replace Sage 9’s Controllers with a more appropriate implementation. It gives you a means to target views and their partials in an effective, non-hacky way (e.g. Sage 9 used body classes :face_with_raised_eyebrow:).

View Components were recently introduced in Laravel 7 and came after our “Composer” concept. While it has a very familiar feel to a Composer, it does not allow you to target and send data to multiple views/partials – it will only pass it to the view rendered via the render() method in the Component.

Are you building a card? A hero/header that accepts different background images, colors, subtitles, descriptions? Use a component.

Maybe you want to build a “Post” component that allows you to specify a couple styles such as card, tile, and when nothing is specified, it will default to entry

<?php

namespace App\View\Components;

use Roots\Acorn\View\Component;

class Post extends Component
{
    /**
     * The card type.
     *
     * @var string
     */
    public $type = 'entry';

    /**
     * The current post.
     *
     * @var mixed
     */
    public $post;

    /**
     * Create the component instance.
     *
     * @param  string $type
     * @param  mixed  $post
     * @return void
     */
    public function __construct($post = null, $type = null)
    {
        $this->post = $post ?? get_the_ID();
        $this->type = $type ?? $this->type;

        $this->withAttributes(['class' => get_post_class(false, $this->post)]);
    }

    /**
     * The post title.
     *
     * @return string
     */
    public function title()
    {
        return get_the_title($this->post);
    }

    /**
     * The post excerpt.
     *
     * @return string
     */
    public function excerpt()
    {
        return apply_filters('the_excerpt', get_the_excerpt($this->post));
    }

    /**
     * The post content.
     *
     * @return string
     */
    public function content()
    {
        return str_replace(
            ']]>',
            ']]&gt;',
            apply_filters('the_content', get_the_content())
        );
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return $this->view('components.post.' . str_replace('.', '-', $this->type));
    }
}

In the Post component we provide the $title, $content, and $excerpt of the current (or specified) post to the views rendered by the component.

In its simplest form, <x-post /> would render components.posts.entry passing it the details for the current post in the loop.

With our component constructor allowing type and post – we are also able to specify a post ID and/or type specifically:

<x-post type="card" post="13" />

If you need to pass a variable and/or function, you can prefix the key with :

<x-post :type="get_field('card_type')" />

Components become extremely powerful for getting specific jobs done.

4 Likes

I understand that composers are not intended to be mixed, but I am trying to make reusable ‘components’ that can be dropped into any instance of my trellis sites to be reused.
(note, when I mention components, I really mean a set of files that are tied to an ACF flexible layout field. Not to be confused with any other concept of components - laravel or otherwise)

I will have to dig into your response around components/composers.
In the meantime, I found a solution(?) to my problem:

Autoload a Components folders in the composer.json:

    "psr-4": {
      "App\\": "app/",
      "Components\\": "components/"
    }
  },

Glob the all of the Component Classes:


    public static function globComponents()
    {
        $components = [];

        $dirs = glob(get_theme_file_path('/components/*'), GLOB_ONLYDIR);

        foreach($dirs as $d){
            $name   = basename($d);
            $class  = "Components\\". $name. '\\' . $name;
            // Folder structure: components/HelloWorld/Composer.php
            $components[] = $class;
        }

        return $components;
    }

Adjust the View config paths to look in the components folder first:

    'paths' => [
        get_theme_file_path('/components'),
        get_parent_theme_file_path('/components'),
        get_theme_file_path('/resources/views'),
        get_parent_theme_file_path('/resources/views'),
    ],

Update the View composers config to add the Component Composers:

    'composers' => App::globComponents()

And now my components folder can house modular components I can drop in and out of various trellis projects.

Im not claiming this is the best, or even right way - but its doing the job I want it to - target views and their partials, but keep them contained in one folder.

Im sure someone will shoot some holes in it - be my guest!

Haha, my mistake for confusing the situation with Sage 10’s view components (although I hope my reply is useful to someone down the road).

How you are approaching it is fine if it works!

1 Like

Its already starting my wheels turning - Thank you, I really appreciate your detailed response!

I notice that in the newest version of sage, this commit removes the parsing of the composer config, thus rendering my approach dead:

foreach ($this->app->config['view.composers'] as $composer) {
     $this->view()->composer($composer::views(), $composer);
 }

src/Acorn/View/ViewServiceProvider.php

Is there a chance this may be added back in? It was proving to be very very useful for a project I was working on using the ideas above.

I’m not somewhere I can test but let me know if https://github.com/roots/acorn/pull/64 works and I can merge it.

1 Like

This works!

I can’t thank you enough. I really appreciate you adding this back in, and so fast.

Look forward to sharing this project soon