Trouble with pluck and pages

Hello fellows Kirby lovers.

I came up to something when building Pages objects, and I did find an answer, but I don’t think it’s a very nice one and I’m wondering what would be the more proper way to do it that i’m not seeing :confused:

I’m trying to get a Pages object based on a page Model method within another Pages object, but the pluck() method is blocking me out.

What am I doing ?

Reduced, I’m trying to do this

$pages = pages(
	$site
	->index()
	->filterBy('myPagesField', '*=', $page->id())
	->pluck('CustomMethod', null, true)
	);

However, this isn’t returning anything. :frowning:

CustomMethod is a method in a page Model, and return some of the page’s ancestors filtered by template. Therefor, a Pages object like this :

Kirby\Cms\Pages Object
(
	[0] => page/this
	[1] => page/that
)

The pluck('CustomMethod', null, true) return this :

Array(
	[0] => Kirby\Cms\Pages Object
		(
			[0] => page/this
			[1] => page/that
		)
	[1] => Kirby\Cms\Pages Object
		(
			[0] => page/this
			[1] => page/that
		)
) …

However, pluck('CustomMethod', ',', true) return this weird thing :

Array
(
	[0] => page/this
	page/that
	[1] => page/this
	page/that
)

And i didn’t find the adequate $split param to separate the pages.


What is expected ?

I’d like the pluck method to return a simple array with my page’s ids, not a multidimensional one, like the one below, in order to use the pages() helper to build my Pages object. :

Array
(
	[0] => page/this
	[1] => page/that
)

To achieve that, i came up with this solution. I’m building the desired array with a foreach and some array_merge().

$pages = $site->index()...->pluck('CustomMethod');

$a = [];
foreach ($pages as $key => $value) {
	$a[] = $value->pluck('id');
}

$a = call_user_func_array('array_merge', $a);
$newPages = pages($a);

And this is working well. But does anyone have a better and nicer way to achieve this ?

Thanks for reading. I hope and made myself clear, and I’m sorry for the mis-post a while before, something went wrong when adding my first post.

Hey @Wizhou, trying to understand what you are doing and not quite getting it. Could you please post your page model?

Hello @texnixe, and thank you for the answer.
Sorry for not being clear. This project is quite heavy, so I tried to simplify it, I’ll be more precise now on.

Long story short, the website have articles (here named as contributions) separated in three templates, and they all have authors (not users), represented either by a page within the site, or by a virtual one builded upon a tag field. I’m referencing the authors of an article by a method within the article’s page model, returning a collection of pages. This method is named auteurices().

The articles are also organised within parents pages, named as antennes and programmes. Antennes are at the roots of the content, and programmes are children of antennes. Articles can be either children of an antenne or of a programme.

However, for an article, antennes and programmes can either be its parents, or some other pages linked to it by a pages field. Both are also referenced by two methods within the article’s page model, returning two collections of pages. These methods are named antennes() and programmes().

Here, I’m on the author page model, and I would like to build two distinct collections of pages representing all the antennes and all the programmes of this author’s articles.

To do this, I first get all the author’s articles. This is made by filtering all the website articles (which are builded inside a kirby->collection()), by looking for the author’s page id inside the results of their auteurices() method.

Then I get the antennes and programmes of these articles by recovering the results of the antennes() and programmes() methods. And my troubles are at this point (more on that at the end).


Author page model :

On this model, i’m building the author’s article pages collection, and I’m trying to get the antennes and programmes of these articles inside another two collections.

class AuteuricePage extends Page {

	public function contributions() {
		return kirby()->collection('contributions')
			->filterBy( 'auteurices','*=', $this->id() );
	}

	// This below I find quite quirky

	public function antennes() {
		$elms = $this->contributions()->pluck('antennes', null, true);
		$a = [];
		
		foreach ($elms as $value) {
			$a[] = $value->pluck('id');
		}

		$a = call_user_func_array('array_merge', $a);
		return pages($a);
	}

	public function programmes() {
		// same logic as antennes().
	}
}

Here is the detail of the collection :

//kirby/site/collections/contributions.php

return function ($site) {
	return $site->page('antennes')
		->index()
		->listed()
		->filterBy('template', 'in', templates)
		// templates is a const array
		->sortBy('date', 'desc');
};

Article Page Model

On that page model, I build the article’s authors page collection, as well as the article’s antennes and programmes pages collections.

For the authors, basically, I build a page collection both with the authors page referenced by a multiselect field and with virual pages made upon a tag field. As the articles can have both, only one, or none, the collection is safely merged or returned at the end.

For antennes and programmes, as they are ancestors of the article, I’m just picking the right one and merge() the linked ones if they exists.

class ArticlePage extends Page {

	public function auteurices() {

		// auteurice is a multiselect field querying author pages.
		$auteurice = $this->auteurice()->split();
		$auteurice = pages($auteurice);

		// auteuriceFantomes is a tag field.
		$auteuricesFantomes = $this->auteuriceFantomes();

		// Theses functions are detailed below
		$auteuricesFantomes = makePages($auteuricesFantomes);
		
		return mergeIfExist($auteurice, $auteuricesFantomes);
	}

	public function antennes() {
		$antenne = null;

		// If the parent is a programme, then the antenne will be the programme parent.
		if($page->parent()->template() == 'programme') {
			$antenne = $page->parent()->parent();
		}

		// Else, the antenne is the parent.
		elseif($page->parent()->template() == 'antenne') {
			$antenne = $page->parent();
		}

		// Here i'm recovering the antennes linked to the page by a pages field.
		$antennesAdd = $page->antenneAdd()->toPages();

		// Function are detailed below.
		$antennes = mergeIfExist($antennesAdd, $antenne);

		return $antennes;
	}

	public function programmes() {
		// Same kind of logic than for antennes…
	}
}

The function makePages() is used to build of collection of virtual pages with a tag field.

function makePages($field) {

	if($field->exist() && $field->isNotEmpty()) {

		$arr = [];

		foreach($field->split(',') as $key => $value) {

			$parent = site()->page('auteurices');

			$arr[] = new Page([
				'slug' => 'ghosts/' . Str::slug($value),
				'content' => [
					'title' => $value,
				],
				'parent' => $parent,
			]);
		}

		$pages = new Pages($arr);
		return $pages;
	}
}

The function mergeIfExist() safely return the end collection.

function mergeIfExist($obj1, $obj2) {
	if($obj1 && $obj2) return $obj1->merge($obj2);
	elseif($obj1 && ! $obj2) return $obj1;
	elseif(! $obj1 && $obj2 ) return $obj2;
}

What the troubles are

So, inside the authors page model, I would like for the antennes / programmes pages collection to be builded that way :

pages($this->contributions()->pluck('antennes', ',', true));

Which seems to be a proper and nicer way to do it.

However, when I’m applying pluck() to the collection, it returns a multidimensional array that i cannot use inside the pages() helpers. And when I’m trying to split() it, the restul array is a bit weird. Eg :

Array
(
	[0] => page/antenne1
	page/antenne4
	[1] => page/antenne2
	page/antenne6
	…
)

My suspicions are upon the merge() of the antennes and programmes linked by a pages field, inside the article model. But I’m not sure how and why.

Here it is. I hope I’m clearer this time. And thank you again for your time. I know I’m only asking for code improvements here, as my code basically works, but I do really think there is some logic I’m missing, or something I do not understand, and would like to learn it :slight_smile:

It is actually normal behavior that the pluck method returns a multidimensional array. The only way to avoid that is to use the split parameter but that only makes sense for a list of comma or otherwise separated items.

You could circumvent this returning an array of page ids from your model methods instead of the pages collection and then use the split parameter.

Simplified example in the Starterkit, where both the notes and photography pages have the same childArray() model method:

Model:

class NotesPage extends Page
{
    public function childArray()
    {
      return $this->children()->pluck('id', ',');
    }
}

Template:

<?php dump($site->children()->filterBy('hasChildren', true)->pluck('childArray', ',')) ?>

Result:

Array
(
    [0] => photography/trees
    [1] => photography/sky
    [2] => photography/ocean
    [3] => photography/desert
    [4] => photography/mountains
    [5] => photography/waterfall
    [6] => photography/plants
    [7] => photography/landscape
    [8] => notes/across-the-ocean
    [9] => notes/a-night-in-the-forest
    [10] => notes/in-the-jungle-of-sumatra
    [11] => notes/through-the-desert
    [12] => notes/himalaya-and-back
    [13] => notes/chasing-waterfalls
    [14] => notes/exploring-the-universe
)

On a side note, I don’t think you need the mergeIfExists() function if you set your default variable value to new Pages() instead of null.

Hey @texnixe,

Thank you for your time and your detailed and precise answer.
I do like this solution, as it’s cleaner and more maintainable, and I think more coherent inside a Kirby website.

I was indeed troubled by the split parameter allowing pluck to return a non multidimensional array, and didn’t understood it as clear as this.

I’ll apply this method to simplify my pages model clean them a bit. Also, a great thanks for the side note :smile:

As always, help from the forum is very precious !

1 Like