timber/starter-theme

Error: Call to protected Timber\Post::__construct() from global scope

luukee opened this issue · 8 comments

We are updating our theme from Timber 1.0 to 2.0 but we cannot seem to get past the Call to protected Timber\Post::__construct() error.

We are trying our best to follow the Upgrade to 2.0 docs (extending Timber, class maps, etc.)

Main point in our code where the issue lies (the links ex. /archive.php: are to the repo code):

/archive.php:

$context = Timber::context();

$options = [];
$globals = new Engage\Managers\Globals();
use Engage\Models\Archive;
$teamGroups = [];

$options = [
	'filters'	=> $globals->getVerticalMenu(get_query_var('verticals'))
];
$context['archive'] = new Archive($options);
Timber::render( 'archive.twig', $context );

src/Models/Archive.php:

namespace Engage\Models;

class Archive extends \Timber\Post
{
	public $posts,
		   $pagination,
		   $slug,
		   $intro = [],
		   $vertical = false,
		   $category = false;

    public function init($query = false) 
    {
    	$this->setVertical();
    	$this->setCategory();
    	$this->setPostType();

        parent::__construct($query);
        $this->setQueriedObject();
        $this->posts = $this->queryIterator->get_posts();
        $this->pagination = $this->pagination();
        $this->taxonomy = $this->queriedObject->taxonomy;

        $this->slug = $this->queriedObject->slug;
				
        $this->setIntro();
    }

functions.php classmap

use Engage\Models\Archive; // Import your custom Archive class

add_filter('timber/post/classmap', function ($classmap) {
	$custom_classmap = [
		'research' => Archive::class, // 2. Define a custom class map for the 'research' post type
	];
	return array_merge($classmap, $custom_classmap); // 3. Merge the custom class map with the default class map
});

Managing Timber with composer & using Timber 2.x-dev.

If I did not provide enough info/code/error logs please let me know and I will update the question. Any help is very much appreciated.

Good point @szepeviktor. But the curious thing is, when I switch to the __construct method I get the same exact error.

Let me send more code & error output when I'm back at my computer. . .

Thank you for commenting 👍

I think you’re mixing different things. Your Archive class extends Timber\Post, so that would mean that you would use this to work with single posts. But then you call this->posts = $this->queryIterator->get_posts(); and $this->pagination = $this->pagination(); inside the init() function of your post. What do you want to do with that?

Should your archive.php display multiple archive posts? Or should Archive be a class that handles multiple posts?

If your Archive class should handle multiple posts, then you probably shouldn’t extend Timber\Post.

If Archive should act as the class for posts in archive.php, then you shouldn’t instantiate the post directly.

$context['archive'] = new Archive($options);

You’ve added a class map, which means you can use Timber::get_post( $post_id ) to get an archive posts and Timber::get_posts() to get the collection of archive posts if you’re in archive.php.

public function init($query = false) {
	// ...

	parent::__construct($query);
}

If you’re overwriting the init() method, you should call parent::init(), but not parent::construct(). If you extend Timber\Post and run parent::construct(), then it will do nothing, because we use empty constructors.

Thank you @gchtr for checking out my issue. I've been trying everything I can come up with to no avail.

What do you want to do with this->posts = $this->queryIterator->get_posts(); and $this->pagination = $this->pagination(); inside the init() function?

These are actually used to build the sidebar taxonomy filters/categories.

Should your archive.php display multiple archive posts?

Yes, it should show a list of archive research posts with a sidebar taxonomy filter menu. Here's an screenshot:

Screenshot 2023-11-07 at 3 17 33 PM

Here's the Timber 1.0 original src/Models/Archive.php constructor:

	public function __construct($query = false, $class = 'Engage\Models\Article')
	{
		// Set the 'verticals' taxonomy term
		$this->setVertical();
		$this->setCategory();
		// Set the post type
		$this->setPostType();
		
		parent::__construct($query, $class);
		$this->setQueriedObject();
		$this->posts = $this->queryIterator->get_posts();
		$this->pagination = $this->pagination();
		// Assign the taxonomy term. ex. 'verticals' tax
		// "$this->queriedObject" is a WP_Term
		$this->taxonomy = $this->queriedObject->taxonomy;
		// Set the slug ex. 'journalism'
		$this->slug = $this->queriedObject->slug;
		// Set the intro ex {{ archive.intro }} - tax term, tax title, tax excerpt
		$this->setIntro();
	}

Here's the original (Timber 1.0) archive.php file:

$context = Timber::get_context();

$options = [];
$globals = new Engage\Managers\Globals();
$articleClass = 'Engage\Models\Article';
$teamGroups = [];
$options = [
	'filters'	=> $globals->getVerticalMenu(get_query_var('verticals'))
];
// build intro
$query = false;
$archive = new Engage\Models\TileArchive($options, $query, $articleClass);
$context['archive'] = $archive;
Timber::render( ['archive.twig'], $context, ENGAGE_PAGE_CACHE_TIME);

getVerticalMenu from src/Managers/Globals.php:

public function getVerticalMenu($vertical) {
  		$menu = get_transient('vertical-filter-menu--'.$vertical);
  		if(!empty($menu)) {
  			return $menu;
  		}

  		$vertical = get_term_by('slug', $vertical, 'verticals');

      	// The filter menu will be built in this order
  		$postTypes = [ 'research',  'blogs', 'announcement', 'tribe_events', 'post',  'team' ];

  		$posts = new Timber\PostQuery([
  			'post_type'      => $postTypes,
  			'tax_query'		=> [
  				[
  					'taxonomy' => 'verticals',
  					'field'	=> 'slug',
  					'terms'	=> $vertical->slug
  				]
  			],
  			'posts_per_page' => -1
  		]);

  		$options = [
  			'title'				=> $vertical->name,
  			'slug'				=> $vertical->slug.'-menu',
  			'posts' 			=> $posts,
  			'taxonomies'		=> ['research-categories', 'blogs-category', 'announcement-category', 'tribe_events_cat', 'category', 'team_category'],
			'postTypes'			=> $postTypes
		  ];

  		// we don't have the vertical menu, so build it
		$filters = new \Engage\Models\FilterMenu($options);
  		$menu = $filters->build();

  		set_transient('vertical-filter-menu--'.$vertical->slug, $menu );

  		return $menu;
  	}

Lastly the src/Models/TileArchive.php file:

namespace Engage\Models;

class TileArchive extends Archive
{
	public $filters = []; // when you want things organized by vertical
	
	public function __construct($options, $query = false, $class = 'Engage\Models\Article')
	{
		
		$defaults = [
			'filters'    => []
		];
		
		$options = array_merge($defaults, $options);
		
		$this->filters = $options['filters'];
		
		parent::__construct($query, $class);
		
		// loop through the posts and if it's an event, set it as the event model instead
		foreach($this->posts as $key => $val) {
			if($val->post_type === 'tribe_events') {
				$this->posts[$key] = new Event($val->ID);
			}
		}
		
		// This is usually already set from a global. If it's empty, then there's no sidebar
		if(!empty($this->filters)) {
			// get the current filter menu item
			$this->setCurrentFilter();
		}
	}
	
	// set the current filter based on the archive
	// this is way too confusing, but seems to work fine... :/
	public function setCurrentFilter() {
		// search for the current slug.
		// If we're displaying all verticals, we'll be looking for the vertical slug as the current match.
		// if it's by postType, then we're looking for the current displayed postType
		
		// Needed to add ability to add team category info to array and filter css.
		// Might consider re-doing some of the filter stuff to acount for things like these.
		if ($this->filters['structure'] === 'vertical') {
			if (isset($this->vertical->slug)) {
				$currentSlug = $this->vertical->slug;
			} elseif (isset($this->category->slug)) {
				$currentSlug = $this->category->slug;
			}
		} else {
			$currentSlug = $this->postType->name;
		}
		
		if($this->filters['terms']) {
			foreach($this->filters['terms'] as $parentTerm) {
				if($currentSlug === $parentTerm['slug']) {
					// found the parent match!
					$this->filters['terms'][$parentTerm['slug']]['currentParent'] = true;
					
					// now see if this is just the current parent or actually the current one
					if($this->category->taxonomy === 'verticals') {
						$this->filters['terms'][$parentTerm['slug']]['current'] = true;
					} else {
						if(!empty($parentTerm['terms'])) {
							// let's find the child
							foreach($parentTerm['terms'] as $childTerm) {
								if($childTerm['slug'] === $this->category->slug) {
									$this->filters['terms'][$parentTerm['slug']]['terms'][$this->category->slug]['current'] = true;
									break;
								}
							}
						}
					}
					break;
				}
			}
		}
	}
}

Also, if I {{ dump(archive) }} from the archive.twig file on the Timber 1.0 theme I get this:

slug: "journalism"
  intro: array:3 [▼
    "vertical" => WP_Term {[#5943 ▶](http://localhost:3003/vertical/journalism/research/#sf-dump-1957750531-ref25943)}
    "title" => "Journalism"
    "excerpt" => ""
  ]
  vertical: WP_Term {[#5943 ▼](http://localhost:3003/vertical/journalism/research/#sf-dump-1957750531-ref25943)
    +term_id: 240
    +name: "Journalism"
    +slug: "journalism"
    +term_group: 0
    +term_taxonomy_id: 240
    +taxonomy: "verticals"
    +description: ""
    +parent: 0
    +count: 184
    +filter: "raw"
  }
  category: WP_Term {[#5942 ▶](http://localhost:3003/vertical/journalism/research/#sf-dump-1957750531-ref25942)}
  filters: array:5 [▼
    "title" => "Journalism"
    "slug" => "journalism-menu"
    "structure" => "postTypes"
    "link" => false
    "terms" => array:3 [ …3]
  ]

Full dump output here

My attempt is to migrate this code to Timber 2.0, but running into troubles with the class maps & using the classes on the archive.php file. I really appreciate your help.

I can always provide more into if you need.

@szepeviktor @gchtr I was able to resolve the issue. I will share the fix when I get back to work tomorrow.

Thanks for your help.

Glad you were able to fix your issue @luukee , I will close the issue here but please do share your fix.

@luukee I encounter the same problem, how did you solve it on your side?

Of course @Jonathan-Scapin & @Levdbas. With the help from @gchtr I was able to get Timber 2.0 working on our theme.

All code below is compared to my original post,

src/Models/Archive.php

I was able to replace my constructor (__construct) with init and excluded the $class parameter. Then for the constructor's parent::__construct($query, $class); I just removed the $class parameter and lastly instead of getting the posts with $this->queryIterator->get_posts() I used Timber::get_posts($query).

These are three changes you'll want to take note of for any other files with similar structure.

Original (Timber 1.0):

public function __construct($query = false, $class = 'Engage\Models\Article')
{
    // other code
    parent::__construct($query, $class);
    $this->setQueriedObject();
    $this->posts = $this->queryIterator->get_posts();
    // other code
}

Updated (Timber 2.0):

public function init($query)
{
    // other code
    parent::__construct($query);
    $this->setQueriedObject();
    $this->posts = Timber::get_posts($query); // Get posts using Timber
    // other code
}

archive.php

For archive.php file:

Original (Timber 1.0):

$context = Timber::get_context();

$options = [];
$globals = new Engage\Managers\Globals();
$articleClass = 'Engage\Models\Article';
$archive = new Engage\Models\TileArchive($options, $query, $articleClass);
$context['archive'] = $archive;
Timber::render( ['archive.twig'], $context, ENGAGE_PAGE_CACHE_TIME);

Updated (Timber 2.0):

$context = Timber::context();

$options = [];
$globals = new Engage\Managers\Globals();
global $wp_query; // Get WP global query
use Engage\Models\TileArchive; // from class maps in functions.php
// remove $articleClass
// same code
$archive = new TileArchive(  $options, $wp_query );
$context['archive'] = $archive;
Timber::render( ['archive.twig'], $context, ENGAGE_PAGE_CACHE_TIME);

src/Managers/Globals.php

For my src/Managers/Globals.php file I only updated the $posts var:

Original (Timber 1.0):

public function getVerticalMenu($vertical) {
  // other code
  $posts = new Timber\PostQuery([
    // other props
  ]);
  // other code
}

Updated (Timber 2.0):

public function getVerticalMenu($vertical) {
  // same code
  $posts = Timber::get_posts([
    // same props
  ]);
  // same code
}

src/Models/TileArchive.php

I left the constructor, removed the $className param and called the parent init function instead of __construct:

Original (Timber 1.0):

public function __construct($options, $query = false, $class = 'Engage\Models\Article')
{
  parent::__construct($query, $class);
}

Updated (Timber 2.0):

public function __construct($query = false, $options')
{
  parent::init($query, $options);
}

I hope that helps. I tried to outline everything I changed to get it working. Let me know if you have any other questions.