Zend framework 2: Define a method available in all controllers - zend-framework

How do I have a method, defined once, available in all controllers?
In my UsersController app I have a method called getAuthService (which fetches the authentication service), but I want to be able to access the authentication instance from other controller too (so I can access it's storage). Below is my method in UsersController:
class UsersController {
protected $authService
.
.
.
protected function getAuthService() {
if (! $this->authService) {
$dbAdapter = $this->getServiceLocator()->get('\Zend\Db\Adapter\Adapter');
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter, 'users', 'email', 'password', 'MD5(?)');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$this->authService = $authService;
}
return $this->authService;
}
}
.. however, I cannot access this in my ApplicationController unless I copy the method in there? Can I define this method somewhere else? Or, another way?
In Rails I'd put this method into the application controller as other controllers extend from that. Is it the Zend way to create a controller containing shared methods that extends AbstractActionController and other controllers extend from that, or extend other modules' controllers from the Application\Controller\IndexController:
- Zend\Mvc\Controller\AbstractActionController (abstract)
- Application\Controller\IndexController (containing my getAuthService method)
- Users\Controller\UsersController (extends the above so getAuthService is available)
Thanks

You could write a Controller Plugin. Since you require the authservice aswell as the dbadapter it probably is a good idea to write a factory which retrieves those. Within your application/src/Controller we add a folder called Plugin. Once that is done we create our factory which fetches the required services etc.
namespace Application\Controller\Plugin;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
*
* Your factory
*
* #package Application
*/
class AuthFactory implements FactoryInterface
{
/**
* Create Service Factory
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$sm = $serviceLocator->getServiceLocator();
$adapter = $sm->get('\Zend\Db\Adapter\Adapter');
$plugin = new Auth();
$plugin->setAdapter($adapter);
return $plugin;
}
}
The Plugin could be something like following:
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class Auth extends AbstractPlugin
{
protected $adapter;
protected $authService;
public function setAdapter($adapter)
{
$this->adapter = $adapter;
}
public function getAdapter()
{
return $this->adapter;
}
public function getService()
{
if (! $this->authService) {
$dbAdapter = $this->getAdapter();
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter, 'users', 'email', 'password', 'MD5(?)');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$this->authService = $authService;
}
return $this->authService;
}
}
Now we have to add our factory to the module.config file like so:
'controller_plugins' => array(
'factories' => array(
'auth' => 'Application\Controller\Plugin\AuthFactory',
),
),
Once that is done you can just call your controller plugin within the controller like so:
$this->auth()->getService();
//or the alternative syntax
$this->plugin('auth')->getService();

Related

JPlugin properties not available in child of child class

I'm writing a Joomla user plugin (Joomla 3.8 and PHP 7.0) and I'm having some trouble with inheritance. A child class can access the properties and methods of a parent using:
$this->[propertyormethod]
but ....
<?php
defined('_JEXEC') or die;
class PlgUserMyPluginParent extends JPlugin
{
public function onUserAfterLogin($options = array())
{
$MyPluginChildObj = new MyPluginChild;
}
}
class MyPluginChild extends PlgUserMyPluginParent
{
public function __construct()
{
var_dump($this->params);
die;
}
}
In the above, the method onUserAfterLogin() fires as expected when a user completes login.
Because $params is set by JPlugin and PlgUserMyPluginParent extends JPlugin, I can access $params (and other JPlugin properties) inside methods of the PlgUserMyPluginParent class thus:
$this->params
So far so good. But when I extend PlgUserMyPluginParent with MyPluginChild, $this->params is null, as are any properties I set directly in PlgUserMyPluginParent and try to access from MyPluginChild.
For example, if I declare in PlgUserMyPluginParent:
public $myParentClassProperty = "somevalue";
then try to access it from MyPluginChild using $this->myParentClassProperty, it returns null.
Can someone please shed some light on what is going on here?
You prohibited the initialisation of the plugin with your constructor.
If you use a constructor in a childclass, you should always call the parent's constructor explicitly.
class MyPluginChild extends PlgUserMyPluginParent
{
/**
* Constructor
*
* #param object $subject The object to observe
* #param array $config An optional associative array of configuration settings.
* Recognized key values include 'name', 'group', 'params', 'language'
* (this list is not meant to be comprehensive).
*/
public function __construct($subject, $config = [])
{
parent::construct($subject, $config);
var_dump($this->params);
die;
}
}
Then the constructor of JPlugin has a chance to populate $params.

How does Symfony controller as service work with __invoke method?

For example, in the following code:
/**
* #Route("/patients", service="bundle1.controller.patient.index")
*/
final class IndexController
{
private $router;
private $formFactory;
private $templating;
private $patientFinder;
public function __construct(RouterInterface $router, FormFactoryInterface $formFactory, EngineInterface $templating, PatientFinder $patientFinder)
{
$this->router = $router;
$this->formFactory = $formFactory;
$this->templating = $templating;
$this->patientFinder = $patientFinder;
}
/**
* #Route("", name="patients_index")
*/
public function __invoke(Request $request) : Response
{
$form = $this->formFactory->create(PatientFilterType::class, null, [
'action' => $this->router->generate('patients_index'),
'method' => Request::METHOD_GET,
]);
$form->handleRequest($request);
$patients = $this->patientFinder->matching($form->getData() ?: []);
return $this->templating->renderResponse('patient/index.html.twig', [
'form' => $form->createView(),
'patients' => $patients,
]);
}
}
Why is there a route annotation for __invoke that is empty?.
What is the lifecycle of this controller? I mean, when does Symfony creates the object and when executes the class to make use of __invoke?
Empty #Route annotation means that there is nothing after main route of class which is /patients. __invoke is a magic PHP method that is executed when you call your class as a function (without providing any method).
So __invoke method is executed when you hit the route /patients or when you call your service from any code.

Symfony3: Service not able to get arguments

I have made a service to get Doctrine connection in my models (Not sure if it is a nice approach but I dont want to pass connection from controller to model constructor each time).
So lets say I want products in my controller
public function getProductsAction(Request $request) {
$product_model = new ProductModel();
return $product_model->getProducts();
}
I have Product model Which will access a helper to get "database_connection"
use AppBundle\Helper\ContainerHelper;
class ProductModel {
function getProducts() {
$helper = new ContainerHelper();
$db = $helper->getDoctrine();
$query = "SELECT * FROM customer_products;";
$statement = $db->prepare($query);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
Now this helper is defined in src/AppBundle/Helper/ContainerHelper.php
namespace AppBundle\Helper;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class ContainerHelper {
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
public static function getDoctrine() {
$database_connection = $this->container->get('database_connection');
return $database_connection;
}
}
Lets say this service needs "service container" so in app/config/services.yml
services:
app.container_helper:
class: AppBundle\Helper\ContainerHelper
arguments: ['#service_container']
But it gives me error:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Helper\ContainerHelper::__construct() must implement
interface Symfony\Component\DependencyInjection\ContainerInterface,
none given, called in \src\AppBundle\Model\ProductModel.php
on line 148 and defined
While I believe that I have implemented it correctly according to http://symfony.com/doc/current/book/service_container.html and http://anjanasilva.com/blog/injecting-services-in-symfony-2/, its certain that I have missed something or just got the whole bad idea. I need to know if it is a correct concept or what I have missed
While #pavlovich is trying to fix your existing code, I really think you are making this much more convoluted than it has to be. ProductModel itself should be a service with your database connection injected into it.
class ProductModel {
public function __construct($conn) {
$this->conn = $conn;
}
public function getProducts() {
$stmt = $this->conn->executeQuery('SELECT * FROM customer_products');
return $stmt->fetchAll();
}
services:
product_model:
class: AppBundle\...\ProductModel
arguments: ['#database_connection']
// controller.php
$productModel = $this->get('product_model'); // Pull from container
$products = $productModel->getProducts();
Rather than using helpers, I'd recommend using constructor injection and autowiring. It's more safe, future proof and easier to extend and test.
In such case, you'd have to create ProductRepository (more common and standard name for ProductModel) and pass it to controller.
1. Controller
<?php
class SomeController
{
/**
* #var ProductRepository
*/
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getProductsAction()
{
return $this->productRepository->getProducts();
}
}
If you have difficulties to register controller as a service, just use Symplify\ControllerAutowire bundle.
2. ProductRepository
// src/AppBundle/Repository/ProductRepository.php
namespace AppBundle\Repository;
class ProductRepository
{
/**
* #var Doctrine\DBAL\Connection
*/
private $connection;
public function __construct(Doctrine\DBAL\Connection $connection)
{
$this->connection = $connection;
}
public function fetchAll()
{
$query = "SELECT * FROM customer_products;";
$statement = $this->connection->prepare($query);
$statement->execute();
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
}
3. Service registration
# app/cofig/servies.yml
services:
product_repository:
class: AppBundle\Repository\ProductRepository
autowire: true
For more you can see similar question with answer here: Symfony 3 - Outsourcing Controller Code into Service Layer
With new version of Symfony 3.3, a new feature is added (Auto-wired Services Dependencies)
https://symfony.com/doc/current/service_container/autowiring.html
https://symfony.com/doc/current/service_container/3.3-di-changes.html
Using this feature, I solved this issue in following way:
Added a new directory /src/AppBundle/Model
Added my model classes in this directory
namespace AppBundle\Modal;
use Doctrine\ORM\EntityManagerInterface;
class ProductModal
{
private $em;
// We need to inject this variables later.
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
// We need to inject this variables later.
public function getProducts()
{
$statement = $this->em->getConnection()->prepare("SELECT * FROM product WHERE 1");
$statement->execute();
$results = $statement->fetchAll();
return $results;
}
}
Added in my app/config/services.yml
AppBundle\Modal\:
resource: '../../src/AppBundle/Modal/*'
public: true
In my controller I can use it like
$products = $this->get(ProductModal::class)->getProducts();
P.S. Dont forget to add use AppBundle\Entity\Product\Product; in controller

Yii2 rest create - saving to DB

I am trying to save data from the form using yii2 rest api controller, standard create action. New line is created in DB, and id is returned, but data from POST is not saved - only zeroes appear in DB, then I overrided create action by my own one, the same situation. But if I directly save to DB, without rest api controller, data is saved successfully.
What can be a reason of such strange saving to DB? Thanks!
By the way, in index I can see post data in the format: {"_csrf":"wergferw","table_name":{"sum":25000,"currency":1}}
Controller is very simple:
namespace frontend\controllers;
use yii;
use yii\rest\ActiveController;
use yii\web\Response;
class DemandController extends ActiveController
{
public $modelClass = 'frontend\models\Demands';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['application/json']
= Response::FORMAT_JSON;
return $behaviors;
}
}
I get reply in rest:
<response><id>37</id></response>
Model is generated by gii
namespace frontend\models;
use Yii;
class Demands extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'demands';
}
public function rules()
{
return [
[['sum', 'currency'], 'required'],
];
}
public function attributeLabels()
{
return [
'sum' => 'Sum',
'currency' => 'Currency',
];
}
}
If none of the posted data appears in the database, it sounds like a post format issue to me. Check that your HTTP Post headers have "Content-Type: application/x-www-form-urlencoded".
I am also having the problem so after changing this line it's worked for me..
You try to change the model->load() method inside yii/rest/createAction
public function run()
{
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
change this line to
$model->load(Yii::$app->getRequest()->getBodyParams());
}
see this link for more info - http://www.yiiframework.com/doc-2.0/yii-base-model.html#load()-detail
The model->load($data,$formName) - the $data should be $_GET or $_POST value of array and $formName use to load the data into the model. If not set, formName() is used. so u have to change the model->load() in createAction class.
Better you have to override the activecontroller default create action then try
class CabController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Cab';
public function actions(){
$actions = parent::actions();
unset($actions['create']);
unset($actions['update']);
unset($actions['index']);
return $actions;
}
/* Declare methods supported by APIs */
protected function verbs(){
return [
'create' => ['POST'],
'update' => ['PUT', 'PATCH','POST'],
'delete' => ['DELETE'],
'view' => ['GET'],
'index'=>['GET'],
];
}
public function actionCreate(){
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model = new Cab;
$post_data = Yii::$app->request->post();
$model->load($post_data);
$model->save(false);
return $model;
}
Include safe columns in your model
public function rules()
{
return [
[['sum', 'currency'], 'safe'],
];
}

Zend_Validate_Db_RecordExists with Doctrine 2?

I'm using Doctrine 2 in a Zend Framework application and require functionality similar to Zend_Validate_Db_RecordExists and Zend_Validate_Db_NoRecordExists.
For example, when a user enters a new item, I need to validate that a duplicate entry doesn't already exist. This is easy to accomplish with Zend_Db by adding the Db_NoRecordExists validator on my forms.
I tried implementing the custom-validator solution proposed here, but I can't figure out how they are communicating with Doctrine to retrieve entities (I suspect this approach may no longer work post-Doctrine 1.x).
The FAQ section of the Doctrine manual suggests calling contains() from the client code, but this only covers collections, and if possible I'd like to handle all of my form validation consistently from within my form models.
Can anyone suggest a way to use these Zend validators with Doctrine 2 DBAL configured as the database connection/resource?
It's quite straightforward, really.
I have a few Zend_Validate-type validators that talk to Doctrine ORM, so I have an abstract class that they descend from.
Here's the abstract class:
<?php
namespace TimDev\Validate\Doctrine;
abstract class AbstractValidator extends \Zend_Validate_Abstract{
/**
* #var Doctrine\ORM\EntityManager
*/
private $_em;
public function __construct(\Doctrine\ORM\EntityManager $em){
$this->_em = $em;
}
public function em(){
return $this->_em;
}
}
Here's my NoEntityExists validator:
<?php
namespace TimDev\Validate\Doctrine;
class NoEntityExists extends AbstractValidator{
private $_ec = null;
private $_property = null;
private $_exclude = null;
const ERROR_ENTITY_EXISTS = 1;
protected $_messageTemplates = array(
self::ERROR_ENTITY_EXISTS => 'Another record already contains %value%'
);
public function __construct($opts){
$this->_ec = $opts['class'];
$this->_property = $opts['property'];
$this->_exclude = $opts['exclude'];
parent::__construct($opts['entityManager']);
}
public function getQuery(){
$qb = $this->em()->createQueryBuilder();
$qb->select('o')
->from($this->_ec,'o')
->where('o.' . $this->_property .'=:value');
if ($this->_exclude !== null){
if (is_array($this->_exclude)){
foreach($this->_exclude as $k=>$ex){
$qb->andWhere('o.' . $ex['property'] .' != :value'.$k);
$qb->setParameter('value'.$k,$ex['value'] ? $ex['value'] : '');
}
}
}
$query = $qb->getQuery();
return $query;
}
public function isValid($value){
$valid = true;
$this->_setValue($value);
$query = $this->getQuery();
$query->setParameter("value", $value);
$result = $query->execute();
if (count($result)){
$valid = false;
$this->_error(self::ERROR_ENTITY_EXISTS);
}
return $valid;
}
}
Used in the context of a Zend_Form (which has an em() method like the abstract class above):
/**
* Overrides superclass method to add just-in-time validation for NoEntityExists-type validators that
* rely on knowing the id of the entity in question.
* #param type $data
* #return type
*/
public function isValid($data) {
$unameUnique = new NoEntityExists(
array('entityManager' => $this->em(),
'class' => 'PMS\Entity\User',
'property' => 'username',
'exclude' => array(
array('property' => 'id', 'value' => $this->getValue('id'))
)
)
);
$unameUnique->setMessage('Another user already has username "%value%"', NoEntityExists::ERROR_ENTITY_EXISTS);
$this->getElement('username')->addValidator($unameUnique);
return parent::isValid($data);
}
Check out the RecordExists.php and NoRecordExists.php classes in my project:-
https://github.com/andyfenna/AJF-IT/tree/master/library/AJFIT/Validate
I hope these are some use to you.
Thanks
Andrew