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.
Related
So I have a model which I am filling from JSON. If I just return the filled model from the controller all is working as expected. However, if I try to access/return/use a specific property of the model it returns the default value. I feel like I must be missing something basic here. Any help is appreciated.
class parent extends Model
{
$id = -1;
$child;
protected $fillable = ['id','child'];
public function __construct()
{
$child = new child();
}
}
class child extends Model
{
$id = -1;
protected $fillable = ['id'];
}
Then in the controller method
public function doStuff(Request $request)
{
$data = json_decode($request->input('parent'),true);
$newParent = new parent();
$newParent->fill($data);
return $newParent; //has data as expected from posted json data which includes a parent with a child underneath it and data set on both
return $newParent->id; //has -1 rather than passed in value
}
Figured it out. By explicitly adding variables with the same name as the desired attributes those variables weren't getting filled but the attribute bag was. This is what comes of transitioning from a C#/strongly typed world I guess.
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
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();
In my RestController which extends AbstractRestfulController, I can get the route params in the implemented functions such as...
public function create($data)
{
$entity = $this->params()->fromRoute('entity');
}
... but when I do the same in the constructor like this
public function __construct()
{
$entity = $this->params()->fromRoute('entity');
}
I get Call to a member function getParam() on a non-object.
Why is that? How can I get the route parameters in the constructor?
What I am trying to do
Since I'm trying to create a generic controller, there is a part of the restful route that is shared for all actions (resp. verbs). The entity for which the request is made. I'd like to store this in a class parameter for convenience.
Normally you'd write a method to proxy to whatever value you need, and just call that method, it's only a little more expensive to call $this->getEntity() than it is to call $this->entity, which, as far as I can tell is the stated aim
class RestController
{
protected $entity;
public function getEntity()
{
if (!$this->entity) {
$this->entity = $this->params()->fromRoute('entity');
}
return $this->entity;
}
}
If you really do want to pre-populate the entity property, the simplest method is to use an initializer, and move the code from your __constructor to init(). Have your controller implement \Zend\Stdlib\InitializableInterface
use Zend\Stdlib\InitializableInterface;
class RestController extends AbstractRestfulController implements InitializableInterface
{
protected $entity;
public function init() {
$this->entity = $this->params()->fromRoute('entity');
}
}
Add an initializer to the controller loader in your module boostrap
use Zend\Stdlib\InitializableInterface;
class Module
{
public function onBootstrap(MvcEvent $e)
$sm = $e->getApplication()->getServiceManager();
$controllers = $sm->get('ControllerLoader');
$controllers->addInitializer(function($controller, $cl) {
if ($controller instanceof InitializableInterface) {
$controller->init();
}
}, false); // false tells the loader to run this initializer after all others
}
}
That would not make any sense as the route is matched to a particular action.
You can't route to a constructor, therefore how could you get route parameters there?
If you give an idea of what you are trying to do then I could suggest a better/nicer way to do it
I mostly use zend_db_table with a paginator, the problem is that it will return zend_db_rows instead the domain objects from my datamapper.
Let's say :
class Content_Model_ArticleMapper {
/*
* #param Zend_Db_Select $select
* #return Zend_Paginator
*/
public function getPaginator($select = null){}
}
I can hack it by overriding _loadAndReturnRow method in a custom rowset
However this is pretty ugly as I don't have a Zend_Db_Row anymore when I query the table.
And loose the methods too like save which I don't want to replicate on the domain object.
:
class Content_Model_DbTable_Rowset_Articles extends Zend_Db_Table_Rowset {
protected function _loadAndReturnRow($position)
{
if (!isset($this->_data[$position])) {
require_once 'Zend/Db/Table/Rowset/Exception.php';
throw new Zend_Db_Table_Rowset_Exception("Data for provided position does not exist");
}
// do we already have a row object for this position?
if (empty($this->_rows[$position])) {
$this->_rows[$position] = new Content_Model_Article($this->_data[$position]);
}
// return the row object
return $this->_rows[$position];
}
}
So my question how do you do this nicely ? :) Do you write custom Paginator adapters?
You can set a rowClass in your DbTable like
DbTable
class Content_Model_DbTable_Article extends Zend_Db_Table_Abstract {
protected $_name = 'article';
public function init() {
$this->setRowClass('Content_Model_Article');
}
}
Domain Model
class Content_Model_Article extends Zend_Db_Table_Row {
//for example
public function getAuthorFullName() {
return $this->author_firstname . ' ' . $this->author_lastname;
}
}
Now rows in your rowset are instances of Content_Model_Article and you can use the Zend_Paginator_Adapter_Iterator.
Using Paginator
$articleTable = new Content_Model_DbTable_Article();
$articleRowset = $articleTable->fetchAll();
$paginator = new Zend_Paginator(Zend_Paginator_Adapter_Iterator($articleRowset));
//now you can loop through the paginator
foreach($paginator as $article) {
echo $article->getAuthorFullName();
}