ZF2 TableGateway join - zend-framework2

I am in the process of trying to learn OO/Zend Framework over standard PHP.. I want to scream and write a mysql query instead of using the TableGateway method.
I have been following tutorials and have successfully printed out a table and some fields, although with the way I have gone about doing this, I am totally lost in how I should make this a join with another table and print out some fields there.
For example.
Table Fields
customer Idx, Company
contact Idx, First_Name
This is my customersController where I assume the work is carried out
namespace Customers\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\DB\TableGateway\TableGateway;
class CustomersController extends AbstractActionController
{
protected $customersTable;
public function indexAction()
{
return new ViewModel(array('customer' => $this->getCustomersTable()->select()));
//return new ViewModel(array('customers' => $this->fetchJoin()->select()));
}
public function addAction()
{
}
public function editAction()
{
}
public function deleteAction()
{
}
public function getCustomersTable()
{
if (!$this->customersTable) {
$this->customersTable = new TableGateway (
'customer', //table name
$this->getServiceLocator()->get('Zend\DB\Adapter\Adapter')
);
}
return $this->customersTable;
}
}
Am I on the right track here?

If you need to make joins read about Zend\Db\Sql and Zend\Db\Select
which you can read about here
http://framework.zend.com/manual/2.0/en/modules/zend.db.sql.html
An example would be:
In your model(that extends the TableGateway or the AbstractTableGateway)
in Some function you can have something like(this is from a project) :
$sql = new \Zend\Db\Sql\Sql($this->getAdapter());
$select = $sql->select()
->from('event_related_events')
->columns(array())
->join('event_invitees', 'event_invitees.event_id =
event_related_events.related_event_id')
->where(array('event_related_events.event_id' => $eventId));
$selectString = $sql->getSqlStringForSqlObject($select);
$results = $this->getAdapter()->query($selectString, \Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
Then you can loop over the results and do what you need to.
Taking a look at more powerful ORM like Doctrine or Propel may also help, but may be an overkill for a small/hobby project.
EDIT: Answer for what was asked in comments
For Using expression(if, case etc) directly you can use something like :
$sql->select()
->from('table')
->columns(array(
'sorter' => new Expression('(IF ( table.`something` >= 'otherthing', 1, 0))'),
'some_count' => new Expression('(count(*))'),
)
)
Explaining the last line in SQL terms, it would be:
count(*) AS some_count

So this is my controller, basically from the Album example but now it will display customers from the customer table.
<?php
namespace Customers\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Customers\Model\Customers;
use Customers\Form\CustomersForm;
class CustomersController extends AbstractActionController
{
protected $customersTable;
public function indexAction()
{
return new ViewModel(array(
'customer' => $this->getCustomersTable()->fetchAll(),
));
}
public function addAction()
{
}
public function editAction()
{
}
public function deleteAction()
{
}
public function getCustomersTable()
{
if (!$this->customersTable) {
$sm = $this->getServiceLocator();
$this->customersTable = $sm->get('Customers\Model\CustomersTable');
}
return $this->customersTable;
}
}
?>
The indexAction calls the getCustomersTable method which goes to the model (CustomersTable) and executes the "query" there.
<?php
namespace Customers\Model;
use Zend\Db\TableGateway\TableGateway;
class CustomersTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getCustomers($id)
{
}
public function saveCustomers(customers $customers)
{
}
public function deleteCustomers($id)
{
}
}
?>
So from your example, I should be trying to implement this into the fetchAll in the model?
Thanks for the help.
$sql = new \Zend\Db\Sql\Sql($this->getAdapter());
$select = $sql->select()
->from('customer')
->columns(array())
->join('contact', 'contact.Idx = customer.Idx')
->where(array('contact.Idx' => $eventId));
$selectString = $sql->getSqlStringForSqlObject($select);
$results = $this->getAdapter()->query($selectString, \Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);

Related

get several values ​separated by , and save line by line in the database

I have an input, and I want to extract several numbers separated by , and store each number on each new line in the database.
model - CouponDocument
<?php
namespace App\Models;
use App\Http\Controllers\CouponDocumentController;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CouponDocument extends Model
{
use HasFactory, SoftDeletes;
protected $table = "coupons_document";
protected $fillable = [
"id",
"cpf",
"coupon_id"
];
protected $hidden = [
'deleted_at',
'created_at',
'updated_at'
];
public function coupon()
{
return $this->belongsTo(Coupon::class, 'coupon_id', 'id')->withTrashed();
}
}
Controller
class CouponDocumentController extends Controller
{
static public function store($request)
{
$data = [];
foreach ($request->input('cpf') as $cpf) {
$data[] = [
'cpf' => trim($cpf),
'coupon_id' => 61
];
}
foreach ($data as $item) {
$couponDocument = new CouponDocument();
$couponDocument->fill($item);
$couponDocument->save();
}
}
}
Resources
class CouponsDocument extends Resource
{
use SearchesRelations;
public static $displayInNavigation = false;
public static $model = \App\Models\CouponDocument::class;
public static $title = 'id';
public static $search = [
'id',
'cpf',
'coupon_id'
];
public static function label()
{
return __('modules.couponDocument.button');
}
public static function singularLabel()
{
return __('modules.couponDocument.button');
}
public function fields(Request $request)
{
return [
Text::make(__('fields.couponDocument.name'), "cpf")
->sortable(),
];
}
where the resource is called
HasMany::make
__("fields.couponDocument.name"),
"documentRelationship",
CouponsDocument::class
I tried to do it with the standard functions of nova, but it ended up not working, I would like to know if there is any other solution

ZF FactoryInterface - using options parameter for configuring loading dependencies

I am wondering about the best practices for loading complex objects.
To begin with, i'm going to outline some boilerplate before getting to the problem.
Assume the following: A simple domain model Client is loaded using a tablegateway, with factories used at every stage to inject dependencies:
namespace My\Model\Client;
class Client implements InputFilterProviderInterface
{
/**#var integer*/
protected $id;
/**#var InputFilter*/
protected $inputFilter;
/**#var Preferences */
protected $preferences;
/**#var Orders*/
protected $orders;
/**#var Contacts*/
protected $contacts;
}
A factory for this Client object:
namespace My\Model\Client;
class ClientFactory implements FactoryInterface
{
public function($container, $requestedName, $options)
{
$client = new Client();
$client->setInputFilter($container->get('InputFilterManager')->get('ClientInputFilter'));
return $client;
}
}
Next the mapper factory, which uses a TableGateway:
namespace My\Model\Client\Mapper;
class ClientMapperFactory implements FactoryInterface
{
public function __invoke($container, $requestedName, $options)
{
return new ClientMapper($container->get(ClientTableGateway::class));
}
}
The TableGatewayFactory:
namespace My\Model\Client\TableGateway
class ClientTableGatewayFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$hydrator = new ArraySerialisable();
$rowObjectPrototype = $container->get(Client::class);
$resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
$tableGateway = new TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
return $tableGateway;
Note the use of a HydratingResultset to return fully formed Client objects from the ResultSet.
This all works nicely.
Now the Client object has several related objects as properties, so whilst using the HydratingResultSet, i'm going to add an AggregateHydrator to load them:
class ClientTableGatewayFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
**$hydrator = $container->get('HydratorManager')->get(ClientHydrator::class);**
$rowObjectPrototype = $container->get(Client::class);
$resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
$tableGateway = new TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
return $tableGateway;
}
Finally, the Clients hydrator factory:
class ClientHydratorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
//base ArraySerializable for Client object hydration
$arrayHydrator = new ArraySerializable();
$arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());
$aggregateHydrator = new AggregateHydrator();
$aggregateHydrator->add($arrayHydrator);
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsAddressHydrator::class));
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsOrdersHydrator::class));
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsPreferencesHydrator::class));
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsContactsHydrator::class));
return $aggregateHydrator;
}
}
... with the gist of the above hydrators being like:
class ClientsAddressHydrator implements HydratorInterface
{
/** #var AddressMapper */
protected $addressMapper;
public function __construct(AddressMapper $addressMapper){
$this->addressMapper = $addressMapper;
}
public function extract($object){return $object;}
public function hydrate(array $data, $object)
{
if(!$object instanceof Client){
return;
}
if(array_key_exists('id', $data)){
$address = $this->addressMapper->findClientAddress($data['id']);
if($address instanceof Address){
$object->setAddress($address);
}
}
return $object;
}
}
Finally we're at the issue. The above works perfectly and will load quite cleanly a Client object with all the related objects fully formed. But i have some resources where the entire object graph is not needed - for instance, when viewing a table of all clients - there is no need for any more information to be loaded.
So i've been thinking of ways of using the factories to choose which dependencies to include.
Solution 1
A factory for each use case. If only the Client data is needed (with no dependencies), then create a series of factories ie ClientFactory, SimpleClientFactory, ComplexClientFactory, ClientWithAppointmentsFactory etc. Seems redundant and not very reusable.
Solution 2
Use the options param defined in the FactoryInterface to pass "loading" options to the hydrator factory, eg:
class ViewClientDetailsControllerFactory implements FactoryInterface
{
//all Client info needed - full object graph
public function __invoke($container, $requestedName, $options)
{
$controller = new ViewClientDetailsController();
$loadDependencies = [
'loadPreferences' => true,
'loadOrders' => true,
'loadContacts' => true
];
$clientMapper = $container->get(ClientMapper::class, '', $loadDependencies);
return $controller;
}
}
class ViewAllClientsControllerFactory implements FactoryInterface
{
//Only need Client data - no related objects
public function __invoke($container, $requestedName, $options)
{
$controller = new ViewAllClientsController();
$loadDependencies = [
'loadPreferences' => false,
'loadOrders' => false,
'loadContacts' => false
];
$clientMapper = $container->get(ClientMapper::class, '', $loadDependencies);
return $controller;
}
}
The mapper factory passes the options to the tablegateway factory, that passes them on to the hydrator factory:
class ClientTableGatewayFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$hydrator = $container->get('HydratorManager')->get(ClientHydrator::class, '', $options);
$rowObjectPrototype = $container->get(Client::class);
$resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
$tableGateway = new TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
return $tableGateway;
}
Finally, we can define here how much info to load into the Client:
class ClientHydratorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
//base ArraySerializable for Client object hydration
$arrayHydrator = new ArraySerializable();
$arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());
$aggregateHydrator = new AggregateHydrator();
$aggregateHydrator->add($arrayHydrator);
if($options['loadAddress'] === true){
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsAddressHydrator::class));
}
if($options['loadOrders'] === true){
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsOrdersHydrator::class));
}
if($options['loadPreferences'] === true){
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsPreferencesHydrator::class));
}
if($options['loadContacts'] === true){
$aggregateHydrator->add($container->get('HydratorManager')->get(ClientsContactsHydrator::class));
}
return $aggregateHydrator;
}
}
This seems to be a clean solution, as the dependencies can be defined per request. However i don't think that this is using the options param as intended - the documentation states that this parameter is supposed to be for passing constructor params to the object, not defining what logic the factory should use to load dependencies.
Any advice, or alternative solutions to achieve the above, would be great. Thanks for reading.
Creating a big palette of all possible combinations would not be just a nightmare, but a declared suicide.
Using options
I wouldn't suggest you this option either. I mean, it's not that bad, but it has a major issue: everytime you instantiate your hydrator, you should remember to pass those options, or you'll get an "empty hydrator". Same logic applies to everything that uses those hydrators.
Since you actually want to remove hydrators you don't need, I'd suggest to avoid this solution, because this way you are always forced to declare which hydrators you need (and, honestly, I'll always forget to do it.. ^^ ).
If you add a new hydrator, you'll have to go through your project and add new options. Not really worth the effort...
That's why I propose you the next solution
Removing unnecessary hydrators
In 99% of the cases, hydrators are used by mappers. Thus, I think it would be cleanier to have a mapper which, by default, returns always the same kind of data (->a single hydrator), but that it can be modified to remove a certain set of hydrators.
Inside the AggregateHydrator, all hydrators are converted into listeners and attached to EventManager. I had some issue while trying to get all events, so I turned on creating an aggregate hydrator with the option to detach an hydrator:
class DetachableAggregateHydrator extends AggregateHydrator
{
/**
* List of all hydrators (as listeners)
*
* #var array
*/
private $listeners = [];
/**
* {#inherit}
*/
public function add(HydratorInterface $hydrator, int $priority = self::DEFAULT_PRIORITY): void
{
$listener = new HydratorListener($hydrator);
$listener->attach($this->getEventManager(), $priority);
$this->listeners[get_class($hydrator)] = $listener;
}
/**
* Remove a single hydrator and detach its listener
*
* #param string $hydratorClass
*/
public function detach($hydratorClass)
{
$listener = $this->listeners[$hydratorClass];
$listener->detach($this->getEventManager());
unset($listener);
unset($this->listeners[$hydratorClass]);
}
}
Then, in the TableGatewayFactory:
class ClientTableGatewayFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$hydrator = $container->get('HydratorManager')->get(ClientHydrator::class);
$rowObjectPrototype = $container->get(Client::class);
$resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
$adapter = $container->get(Adapter::class);
$tableGateway = new TableGateway('clients', $adapter, null, $resultSet);
return $tableGateway;
}
}
And the ClientHydratorFactory:
class ClientHydratorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$aggregateHydrator = new DetachableAggregateHydrator();
$arrayHydrator = new ArraySerializable();
$arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());
$aggregateHydrator->add($arrayHydrator);
$hydratorManager = $container->get('HydratorManager');
$aggregateHydrator->add($hydratorManager->get(ClientsAddressHydrator::class));
$aggregateHydrator->add($hydratorManager->get(ClientsOrdersHydrator::class));
$aggregateHydrator->add($hydratorManager->get(ClientsPreferencesHydrator::class));
$aggregateHydrator->add($hydratorManager->get(ClientsContactsHydrator::class));
return $aggregateHydrator;
}
}
You just need to make tablegateway accessible by outstide the mapper:
class ClientMapper
{
private $tableGateway;
// ..
// Other methods
// ..
public function getTableGateway(): TableGateway
{
return $this->tableGateway;
}
}
And now you're able to choose which hydrators you don't want to attach.
Let's say you have two controllers:
ClientInfoController, where you need clients and their address, preferences and contacts
ClientOrdersController, where you need clients with their orders
Their factories will be:
class ClientInfoController implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$clientMapper = $container->get(ClientMapper::class);
// Orders are unnecessary
$resultSetPrototype = $clientMapper->getTableGateway()->getResultSetPrototype();
$resultSetPrototype->getHydrator()->detach(ClientsOrdersHydrator::class);
return $aggregateHydrator;
}
}
class ClientOrdersController implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$clientMapper = $container->get(ClientMapper::class);
// Orders are unnecessary
$resultSetPrototype = $clientMapper->getTableGateway()->getResultSetPrototype();
$resultSetPrototype->getHydrator()->detach(ClientsAddressHydrator::class);
$resultSetPrototype->getHydrator()->detach(ClientsPreferencesHydrator::class);
$resultSetPrototype->getHydrator()->detach(ClientsContactsHydrator::class);
return $aggregateHydrator;
}
}

ZF2: inject variables in constructor of service

Is there a way to create a new instance of a service and adding constructor parameters? I am a bit new to depency injection, and I find I can only add services as constructor parameters and not runtime variables through a factory.
The code I have looks similar to this:
Class MyService
{
private $name;
private $active;
public function __construct($name,$active)
{
$this->name = $name;
$this->active = $active;
}
}
$myService = $this->getServiceLocator()->get('MyService')
Yes there is a way by using the MutableCreationOptionsTrait trait in your factory.
class YourServiceFactory implements FactoryInterface, MutableCreationOptionsInterface
{
use MutableCreationOptionsTrait;
public function createService(ServiceLocatorInterface $serviceLocator)
{
if (isset($this->creationOptions['name'])) {
// do something with the name option
}
if (isset($this->creationOptions['active'])) {
// do something with the active option
}
$yourService = new YourService(
$this->creationOptions['active'],
$this->creationOptions['name']
);
return $yourService;
}
}
The above shown code implements the trait for creation options. With this trait you can handle an array of options in your factory. Call your service like in the following code.
$yourService = $this->getServiceLocator()->get(YourService::class, [
'active' => true,
'name' => 'Marcel',
]);
Easy as pie. ;)
Let's say your service exists:
Class MyService
{
private $name;
private $active;
public function __construct($name,$active)
{
$this->name = $name;
$this->active = $active;
}
}
If instead of ->get()'ing it, you could ->build() it :)
class SomeFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return $container->build(MyService::class, ['name' => "Foo", 'active' => true]);
// Line below works as well, using a variable requested name, handy for an AbstractFactory of some kind (e.g. one that creates different adapters in the same way with same params)
// return $container->build($requestedName, ['name' => "Foo", 'active' => true]);
}
}
Check out the ServiceManager build() function
Note: Not sure since when it's present, this works in higher versions of ZF2 and all of ZF3.
Note 2: Both get() and build() call function doCreate(). Function declaration:
private function doCreate($resolvedName, array $options = null)
get() does: $object = $this->doCreate($name);
build() does: return $this->doCreate($name, $options);

How to use translate method in view helper?

Let say that we have example view helper code like this:
namespace Product\View\Helper;
use Zend\View\Helper\AbstractHelper;
class ProductType extends AbstractHelper
{
public function __invoke($id)
{
return $this->translate('Super extra product'); # How to use this method here
}
}
Now what it the best way to use translate in this view helper?
Regards,
The easiest way is translate view helper:
public function __invoke($id)
{
return $this->view->translate('Super extra product');
}
Also you can use ServiceManager to do translate or other actions.
In your Module.php change getViewHelperConfig to pass ServiceManager:
public function getViewHelperConfig()
{
return array(
'factories'=>array(
'ProductType'=>function ($helperPluginManager){
return new \Product\View\Helper\ProductType($helperPluginManager->getServiceLocator());
}
)
);
}
Now, you can use ServiceManager to translate in view helper:
class ProductType extends AbstractHelper
{
protected $serviceManager;
public function __construct($serviceManager)
{
$this->serviceManager = $serviceManager;
}
public function __invoke($id)
{
return $this->serviceManager->get('translator')->translate('Super extra product');
}
}

Silex passing app and request to controller classes

I want a simple way to access $app and $request in my controller classes. The document says to do this,
public function action(Application $app, Request $request) {
// Do something.
}
but it doesn't look right to have to inject $app and $request to every method. Is there a way to include $app and $request to every controller by default, maybe using the constructor? I'd like to be able to use it as $this->app.
Thanks.
In the Controllers as Services part of the documentation you can see how to inject dependencies to controller classes via the constructor - in that case a repository.
It's possible :
Create a ControllerResolver.php somewhere in your project and put this inside :
namespace MyProject;
use Silex\ControllerResolver as BaseControllerResolver;
class ControllerResolver extends BaseControllerResolver
{
protected function instantiateController($class)
{
return new $class($this->app);
}
}
Then register it in your app (before $app->run();):
$app['resolver'] = function ($app) {
return new \MyProject\ControllerResolver($app, $app['logger']);
};
Now you can create a base controller for your app, for example :
namespace MyProject;
use Silex\Application;
use Symfony\Component\HttpFoundation\Response;
abstract class BaseController
{
public $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getParam($key)
{
$postParams = $this->app['request_stack']->getCurrentRequest()->request->all();
$getParams = $this->app['request_stack']->getCurrentRequest()->query->all();
if (isset($postParams[$key])) {
return $postParams[$key];
} elseif (isset($getParams[$key])) {
return $getParams[$key];
} else {
return null;
}
}
public function render($view, array $parameters = array())
{
$response = new Response();
return $response->setContent($this->app['twig']->render($view, $parameters));
}
}
And extend it :
class HomeController extends BaseController
{
public function indexAction()
{
// now you can use $this->app
return $this->render('home.html.twig');
}
}

Resources