Multiple models -> 1 Controller

Can someone please point me to a sample of a controller that builds a bridge from several models into one view?

I’ve spent the weekend scouring the docs and searching the web for an example …

I have 5 different models that I want to feed into my ViewModel …

I have no problems with creating single model controller → view … the Album example in the docs does a fine job of that … so I have two separate views, each with one model.

What I want is one view that merges those two views into one … ( well, in the end, there will be 5 → 1 ) … I just can’t find ( or figure out ) how to build a Controller that … does this.

Happy with just pointers to a sample, I figure its just something really simple that isn’t clicking into place …

If it helps, I am using Factories to define my database / table connections … and as i said, its all working fine with single model/table views …

Thanks …

Welcome to the forum :slight_smile:
This is one way to accomplish it.

What that doc explains is that you are just creating additional ViewModels and assigning them a property in the actions ViewModel so that you can render them where and when you need to within the actions view file. Using a layout and an action view works the same way. The framework just does it for you by assigning the actions child to the layouts $this->content property. Just remember to inject your required models into the controller via its factory so you have them available for the action.

Another thing I might mention here is that your Controller really does not do this. The controller is just the point of convergence where this happens. Your controller factory injects your Models into the controller instance so they are available. Within the controller action you create the additional ViewModels then assign them to the actions ViewModel as children.

Thanks for that … not the answer to the question, but gives me an answer to a question I hadn’t thought to ask ( yet ) … I’m not used to working within a Framework, I generally just wrote HTML with code built into it … I see benefit to this, which is why I’m trying to learn it, but its a whole different way of thinking :slight_smile:

In the case of my question though, specifically …

I have three models ( three database views ) whose data I want to pass into a single view …

Right now, my controller for a ‘single model’ looks like ( based on the Albums example ):

class DemographicsByCountryController extends AbstractActionController
{
    private $table;

    // Add this constructor:
    public function __construct( DemographicsByCountryView $table )
    {
        $this->table = $table;
    }

    public function indexAction()
    {

        return new ViewModel([
            'demographics_by_country' => $this->table->fetchAll()
        ]);
    }
}

so the model is pull into the _construct, and then passed into the ViewModel via indexAction() … straight forward …

But what I want in this case is three tables passed into ViewModel:

‘demo_by_country’
‘demo_by_age’
‘demo_by_sex’

I have the Factory’s defined for those tables … how do I pass those in so that I can use those?

Actually, that URL you sent is a perfect example … each one of those Models would apply to each separate child View … I could break my view up into 3 different ones, and pull them together just like that … but … how do I “open” the different Models to then assign to each of the child views?

I’d be doing something like:

        $view = new ViewModel();

        // this is not needed since it matches "module/controller/action"
        $view->setTemplate('content/article/view');

        $articleView = new ViewModel(['demo_by_country' => $this->table1->fetchAll()]);
        $articleView->setTemplate('content/demo_by_country');

        $primarySidebarView = new ViewModel(['demo_by_age' => $this->table2->fetchAll()]);
        $primarySidebarView->setTemplate('content/demo_by_age');

        $secondarySidebarView = new ViewModel(['demo_by_sex' => $this->table3->fetchAll()]);
        $secondarySidebarView->setTemplate('content/demo_by_sex');

        $view->addChild($articleView, 'article')
             ->addChild($primarySidebarView, 'sidebar_primary')
             ->addChild($secondarySidebarView, 'sidebar_secondary');

        return $view;

but how do I define/set table1/table2/table3? that is the part that is eluding me …

Thanks …

Hey @Scrappy ,

as you already have a dependency injection in your controller, you can add even more dependencies. I guess you have a controller factory. Just edit it and add the other table gateways.

<?php

declare(strict_types=1);

namespace Marcel\Controller\Factory;

use Marcel\Controller\MyController;
use Marcel\Db\TableGateway;
use Psr\Container\ContainerInterface;

class MyControllerFactory
{
    public function __invoke(ContainerInterface $container): MyController
    {
        $tableGateway1 = $container->get(TableGateway\MyTableGateway1::class);
        $tableGateway2 = $container->get(TableGateway\MyTableGateway2::class);
        $tableGateway3 = $container->get(TableGateway\MyTableGateway3::class);

        $controller = new MyController(
            $tableGateway1,
            $tableGateway2,
            $tableGateway3,
        );

        return $controller;
    }
}

As you can see all needed table gateway instances are initialized in the controller factory and injected into the controller 's constructor. I simply assume that you have defined a factory for each table gateway. That leads us to the controller …

<?php

declare(strict_types=1);

namespace Marcel\Controller;

use Laminas\Db\TableGateway\TableGatewayInterface;
use Laminas\Mvc\Controller\AbstractActionController;

class MyController extends AbstractActionController
{
    public function __construct(
        protected TableGatewayInterface $tableGateway1,
        protected TableGatewayInterface $tableGateway2,
        protected TableGatewayInterface $tableGateway3,
    ) {}

    public function indexAction()
    {
        // do all the stuff you need with your three table gateways

        return new ViewModel([
            'param-1' => $this->tableGateway1->fetchAll(),
            'param-2' => $this->tableGateway2->fetchAll(),
            'param-3' => $this->tableGateway3->fetchAll(),
        ]);
    }
}

You can also consider whether these three table gateways should be used in combination in other controllers. Maybe even controller plugins would be a good idea. For now, however, the solution using dependency injection is perfectly adequate.

It was very much an answer to the question you asked. You may have wanted an answer to a different question but that is not the question you asked :slight_smile:

Couple of points here that are really important in regards to effectively using Laminas MVC or Mezzio.

A factory in this context has one single purpose. To create the requested service instance and return it to the container. Since your factories have access to the container then all other service factories can request an instance from the container to allow them to be injected into other services as dependencies. Which is exactly what your controller factory is doing with your tablegateway classes. This is known as Dependency Injection and every component uses the same approach.

You have the return of 3 select *'ish queries for which you want to render in a single view.

Using the partialLoop view helper is another way to accomplish what you are trying to do. The helper is already provided and it does not require initializing an additional ViewModel instance per Resultset.

Please reference this doc:

@ezkimo has already covered how to get your TableGateway instances into your controller (I think this might actually be what you were trying to ask in the original question).

I will mention since ezkimo brought them up (controller plugins that is) it’s actually easier to just implement a Trait.

You have to return the $controller :wink:

1 Like

This is where I may be going wrong … I started off following the Album tutorial, and this may be where my confusion is coming in …

I have three files for each table:

Model file ( defines the table )
Table file ( defines access to the table … ie. fetchAll )
Factory file, which does:

    public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): VersionsView
    {
        $dbAdapter = $container->get(AdapterInterface::class);
        $resultSetPrototype = new ResultSet();
        $resultSetPrototype->setArrayObjectPrototype(new Versions());
        $tableGateway = new TableGateway('v_versions_summary', $dbAdapter, null, $resultSetPrototype);
        return new VersionsView($tableGateway);
    }

So there is my TableGateway …

From your example above, I get the feel that what I should be doing is moving:

        $dbAdapter = $container->get(AdapterInterface::class);
        $resultSetPrototype = new ResultSet();
        $resultSetPrototype->setArrayObjectPrototype(new Versions());
        $tableGateway = new TableGateway('v_versions_summary', $dbAdapter, null, $resultSetPrototype);

From the Factory to somewhere else, and change my invoker in the Factory to call that … which makes sense as to why I’m having a problem trying to get more then one …

In fact, short of the separate TableGateway files ( which would be cleaner, not suggesting not doing it ), but what I could do is:

    public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): VersionsView
    {
        $dbAdapter = $container->get(AdapterInterface::class);
        $resultSetPrototype = new ResultSet();
        $resultSetPrototype->setArrayObjectPrototype(new Versions());
        $tableGateway1 = new TableGateway('v_versions_summary', $dbAdapter, null, $resultSetPrototype);
        $tableGateway2 = new TableGateway('v_demo_by_age', $dbAdapter, null, $resultSetPrototype);


        return new VersionsView($tableGateway1, $tableGateway2);
    }

And extend my Controller to accept two TableGateways in the constructor, instead of the 1 that I’m working with now …

Is this correct?

If I’m reading your code sample above right, your TableGateways are located in:

src/Db/TableGateway.php

Looking something like Table Gateways - laminas-db - Laminas Docs ( Quick Setup ), but you have multiple Classes ( for the example above ) within the same php file, so something like:

namespace Laminas\Db\TableGateway;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\ResultSet\ResultSetInterface;
use Laminas\Db\Sql;
use Laminas\Db\Sql\TableIdentifier;

class MyTableGateway1 extends AbstractTableGateway 
{
    public function __construct( );
}

class MyTableGateway2 extends AbstractTableGateway 
{
    public function __construct( );
}

and then in my Factory(s), I just call up which TableGateway I want to access …

I’m curious, what does your folder structure look like? Again, if I’m reading your use statements above correctly, you have something like:

src
src/Controller
src/Controller/Factory
src/Db
src/Model
...?

Again, I’ve been basing mine on that Album tutorial, so I have my Factory files mixed into my Model files, which probably works for a simple, single Model module … I know each tends to have their own way of doing things, but rather start off with a structure that probably makes more sense on larger. modules then the tutorial, if you do not mind?

Thanks … unless I’ve truly confused my understanding of what you’ve shown above, this has been a major help …

Don 't make things more complicated as they are. Do you know the KISS principle? That 's the first I 've learned back in the days. Just keep it simple and stupid. There 's another principle in software development that 's called separation of concerns. Just write single factory for a single purpose. You want a table gateway? Write a factory for a table gateway. You want a controller with several dependencies? Write a factory for that single controller. As long as it 's all wired up in you config file, it will work. The laminas service container will call all the single factories for you, when you need it. Pretty fast. Pretty simple.

Think of a module with the following folder structure.

config/module.config.php
src/Controller/Factory
    MyControllerFactory.php
src/Controller
    MyController.php
src/Db/TableGateway/Factory
    TableGateway1Factory.php
    TableGateway2Factory.php
    TableGateway3Factory.php
src/Db/TableGateway
    TableGateway1.php
    TableGateway2.php
    TableGateway3.php
view/module-name/controller-name/action-name.phtml

That 's all you need. When you need different table gateways in one single controller, then your config should look like this.

<?php

declare(strict_types=1);

namespace Marcel;

return [
    'controllers' => [
        'factories' => [
            Controller\MyController::class => Controller\Factory\MyControllerFactory::class,
        ],
    ],
    'router' => [
        'routes' => [
            'my-route' => [
                'type' => \Laminas\Router\Http\Literal::class,
                'options' => [
                    'route' => '/my-route',
                    'defaults' => [
                        'controller' => Controller\MyController::class,
                        'action' => 'my-action',
                    ],
                ],
            ],
        ],
    ],
    'service_manager' => [
        'factories' => [
            Db\TableGateway\MyFirstTableGateway::class => Db\TableGateway\Factory\MyFirstTableGatewayFactory::class,
            Db\TableGateway\MySecondTableGateway::class => Db\TableGateway\Factory\MySecondTableGatewayFactory::class,
        ],
    ],
];

It 's as simple as that. Every single table gateway has a factory, that is registered in your service manager config. The controller factory injects all table gateways you need into your controller. The constructor of your controller needs therefor as many parameters as you want to inject into your controller. Alternatively you could use setter methods, to set a property you need in your controller.

Do not initialize every table gateway in a factory again and again. Let the service manager do the job for you. It caches already initialized instances and you can use it as often as you want. So just use the PSR container like shown above in the controller factory.