Writing the ViewModel for the Team Data
Writing the ViewModel for the Team Data
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Now the team page gets its actual data. As discussed in chapter 7, that's handled by a ViewModel implementing ArgumentInterface - no custom block class.
The data structure
Every team member gets a name, a role, and a category. The category later becomes the key for the Alpine filter logic (chapter 21) - so we use short, technical identifiers for it rather than the display text directly.
<?php
declare(strict_types=1);
namespace Mironsoft\TeamPage\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Supplies team members and categories for the team page.
*
* The data is deliberately hard-coded for this tutorial - a real project
* would inject a repository here instead.
*/
class TeamMembers implements ArgumentInterface
{
/**
* Returns all team members.
*
* @return array<int, array{name: string, role: string, category: string, photo: string}>
*/
public function getTeamMembers(): array
{
return [
['name' => 'Anna Keller', 'role' => 'Backend Developer', 'category' => 'entwicklung', 'photo' => 'anna.jpg'],
['name' => 'Ben Vogel', 'role' => 'UI/UX Designer', 'category' => 'design', 'photo' => 'ben.jpg'],
['name' => 'Carla Roth', 'role' => 'Customer Support', 'category' => 'support', 'photo' => 'carla.jpg'],
['name' => 'David Neumann', 'role' => 'Frontend Developer', 'category' => 'entwicklung', 'photo' => 'david.jpg'],
];
}
/**
* Returns all available categories, including "all", for the filter.
*
* @return array<int, array{key: string, label: string}>
*/
public function getCategories(): array
{
return [
['key' => 'alle', 'label' => 'All'],
['key' => 'entwicklung', 'label' => 'Development'],
['key' => 'design', 'label' => 'Design'],
['key' => 'support', 'label' => 'Support'],
];
}
}Why a dedicated getCategories() method?
You could, in theory, derive the categories from getTeamMembers() (array_unique over the category values). A dedicated method is deliberately better here: it allows a fixed order, a descriptive display text (label) separate from the technical key (key), and an "all" entry that doesn't appear in the actual team data at all.
Binding the ViewModel in the Layout XML
Now we extend the empty layout handle from chapter 18 with the ViewModel argument:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template"
name="team.page"
template="Mironsoft_TeamPage::team/index.phtml">
<arguments>
<argument name="team_view_model" xsi:type="object">Mironsoft\TeamPage\ViewModel\TeamMembers</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>The argument name team_view_model is freely chosen, but must be retrieved under exactly that name in the template (chapter 20).
Tipp: PHPStan (level 5, per this project's CLAUDE.md conventions) checks, among other things, whether array return types match the @return annotation. For arrays with a fixed shape like this one, a detailed shape annotation (array{name: string, role: string, ...}) is worth it instead of just array - it also gives IDE autocompletion more information in the template.