Unset child object static variable from abstract parent class php - php

I am using Laravel and it's Validators.
I have the following code in my controller:
class ResellerController extends BaseController{
public function add() {
//some code before
$userValidator = new App\Services\Validators\UserCreateValidator();
//HERE I WANT TO REMOVE THE company KEY FROM THE RULES IN THE USERS CREATE VALIDATOR
$userValidator->removeRule('company');
//code execution continues
}
}
The UserCreateValidator extends a parent Validator class:
namespace App\Services\Validators;
class UserCreateValidator extends Validator {
public static $rules = array(
'firstName' => 'required',
'lastName' => 'required',
'email' => 'required|email|unique:users',
'company' => 'required'
);
}
And here is the base Validator class:
namespace App\Services\Validators;
abstract class Validator {
/**
* Validation rules
* #var array
*/
public static $rules;
//THIS CODE DOES NOT WORK IN THE CONTROLLER UP
public static function removeRule($ruleKey){
if(is_array($ruleKey))
{
foreach($ruleKey as $key)
{
if(!array_key_exists($key, static::$rules)) continue;
unset(static::$rules[$key]);
}
return true;
}
if(!array_key_exists($ruleKey, static::$rules)) //return false;
unset(static::$rules['company']);
return true;
}
}
The unsettting of the static::$rules[$key] in ResellerController does not work.
I can see in a XDEBUG session (after this line gets executed) that the static::$rules['company'] is still present in the UserCreateValidator as property.
I thought that Late Static Binding should solve this problem?
What is wrong?

The problem is solved. It was in the commented part in the:
if(!array_key_exists($ruleKey, static::$rules)) //return false;
The unsetting is working fine after I uncomment the return false.
Silly mistake :)

Related

Writing tests for controllers with forms

Ever since I started using Zend Framework 3, I had problems with testing my controllers. I'm trying to test my controllers with PhpUnit 5.7 and my controllers depend on Zend Form, which is hydrated with Doctrine's DoctrineObject.
I'm trying to put this as simple as possible, so here's a minimal example of a setup that's giving me headaches:
Controller:
class IndexController extends AbstractActionController {
private $form;
public function __construct(AlbumForm $form) {
$this->form = $form;
}
public function indexAction() {
return ['form' => $this-form];
}
}
ControllerFactory:
class IndexControllerFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, ...) {
$formManager = $container->get('FormElementManager');
return new IndexController($formManager->get(AlbumForm::class));
}
}
The corresponding view template in albums/index/index.phtml:
<?php
$this->form->prepare();
$this->form->setAttribute('action', $this->url(null, [], true));
$albumFieldset = $this->form->get('album');
?>
<?= $this->form()->openTag($this-form) ?>
<div class="form-group">
<?= $this->formRow($albumFieldset->get('name')) ?>
</div>
<?= $this->form()->closeTag() ?>
The form:
class AlbumForm extends Form {
public function init() {
$this->add([
'name' => 'albumFieldset',
'type' => AlbumFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
}
}
The fieldset:
class AlbumFieldset extends Fieldset {
public function init() {
$this->add([
'name' => 'name',
'type' => Text::class,
'options' => [
'label' => 'Name of album',
],
]);
}
}
The FieldsetFactory:
class AlbumFieldsetFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, ...) {
$objectManager = $container->get(ObjectManager::class);
$fieldset = new AlbumFieldset();
$fieldset->setHydrator(new DoctrineObject($objectManager));
$fieldset->setObject(new Album());
return $fieldset;
}
}
Now, so far everything is working great.
However, when writing tests for this I run into troubles. Let me first show you what I have so far:
class IndexControllerTest extends AbstractHttpControllerTestCase {
protected function setUp() {
parent::setUp();
$this->configureServiceManager($this->getApplicationServiceLocator());
}
private function configureServiceManager(ServiceManager $services) {
$services->setAllowOverride(true);
$services->setService(ObjectManager::class, $this->mockObjectManager()->reveal());
$services->setService('FormElementManager', $this->mockFormManager()->reveal());
$services->setAllowOverride(false);
}
private $objectManager;
private function mockObjectManager() {
$this->objectManager = $this->prophesize(ObjectManager::class);
return $this->objectManager;
}
private $formManager;
private function mockFormManager() {
$this->formManager = $this->prophesize(FormElementManager::class);
$this->formManager->get(AlbumForm::class)->willReturn($this->mockForm()->reveal());
return $this->formManager;
}
private $form;
private function mockForm() {
$this->form = $this->prophesize(AlbumForm::class);
$this->form->prepare()->willReturn(null);
$this->form->setAttribute('action', Argument::type('string'))->willReturn(null);
$this->form->getAttributes()->willReturn([]);
$this->form->get('album')->willReturn($this->mockAlbumFieldset()->reveal());
return $this->form;
}
private $albumFieldset;
private function mockAlbumFieldset() {
$this->albumFieldset = $this->prophesize(AlbumFieldset::class);
$this->albumFieldset->get('name')->willReturn($this->mockName()->reveal());
return $this->albumFieldset;
}
private $name;
private function mockName() {
$this->name = $this->prophesize(Text::class);
$this->name->getLabel()->willReturn('label');
$this->name->getLabelAttributes()->willReturn(['for' => 'name']);
$this->name->getLabelOption('disable_html_escape')->willReturn(false);
$this->name->getLabelOption('always_wrap')->willReturn(false);
$this->name->getLabelOption('label_position')->willReturn('prepend');
$this->name->getName('album[name]');
$this->name->getAttribute('type')->willReturn('text');
$this->name->hasAttribute('id')->willReturn(true);
$this->name->getAttribute('id')->willReturn('name');
$this->name->getAttributes([])->willReturn([]);
$this->name->getValue()->willReturn(null);
$this->name->getMessages()->willReturn([]);
return $this->name;
}
}
This will eventually run without errors. However, I would like to draw your attention to the last few methods, especially mockName(). Most of those definitions are totally default and almost none of them are specified in AlbumFieldset in the beginning (only name is). It is very annoying to write them down for every form input I may have and writing this down actually introduces more errors than it solves. For example, I'm still not sure what the correct label option for always_wrap would be. I actually don't even care about that option, but I have to write something about it in my test, because otherwise the test fails with 'Prophecy\Exception\Call\UnexpectedCallException' with message 'Method call: - getLabelOption("always_wrap") on Double\Zend\Form\Element\Text\P245 was not expected, expected calls were: ....
Therefore, I'm asking you: is there any better way to go about this? A way that does not involve writing 20+ rows for every field I have in my fieldset. If it involves rewriting my controllers/fieldsets/view templates (etc.), that would totally be fine!
Any help is greatly appreciated! Also, this is my very first time asking something in a forum in over eight years of programming, so please bear with me if anything is unclear.
Yours
Steffen
PS: What I have already tried is to give the IndexController null instead of an actual form and simply abort the view template when it detects that the form is null. However, while that worked without that much setup, I was basically just avoiding the view template's logic. Because of that, I was not able to detect errors in the view template. That's not what I want.
edit IndexControllerTest: Change private as protected elsewhere and extends it for your new fields. Each new controller must overwrite methods calling parent::methodname($args) and add the needed code...

Can't initialize my plugin function in ZF2 constructor

I am quite new to ZF2 and I am preparing a demo application with simple login and CRUD system. Now for login I have prepared a plugin which consists of some functions that will authenticate users, return the logged in user data, return the logged in status etc. But the problem that I am facing is I can't initialize any variable into the constructor of my controller which will store any return value from the plugin. It's always showing service not found exception.
Please find my plugin code below:
AuthenticationPlugin.php
<?php
namespace Album\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Session\Container as SessionContainer;
use Zend\View\Model\ViewModel;
use Album\Entity\User;
class AuthenticationPlugin extends AbstractPlugin{
protected $entityManager;
protected $usersession;
public function __construct(){
$this->usersession = new SessionContainer('UserSession');
}
public function dologin($email,$password)
{
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('email' => $email, 'password' => $password));
if(count($getData)){
$this->usersession->offsetSet('userid', $getData->getId());
return true;
}
else{
return false;
}
}
public function isloggedin(){
$userid = $this->usersession->offsetGet('userid');
if(!empty($userid)){
return true;
}
else{
return false;
}
}
public function logindata(){
$userid = $this->usersession->offsetGet('userid');
$getData = $this->em()->getRepository('Album\Entity\User')->findOneBy(array('id' => $userid));
return $getData;
}
public function logout(){
$this->usersession->offsetUnset('userid');
}
public function em(){
return $this->entityManager = $this->getController()->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
}
?>
In my module.config.php
'controller_plugins' => array(
'invokables' => array(
'AuthPlugin' => 'Album\Controller\Plugin\AuthenticationPlugin',
)
),
Now I am doing this in my controller:
protected $entityManager;
protected $isloggedin;
protected $authentication;
public function __construct(){
$this->authentication = $this->AuthPlugin();
$this->isloggedin = $this->authentication->isloggedin();
}
The error I am getting is like below:
An error occurred An error occurred during execution; please try again
later. Additional information:
Zend\ServiceManager\Exception\ServiceNotFoundException
File:
D:\xampp\htdocs\subhasis\zf2-tutorial\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php:555
Message:
Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for AuthPlugin
But if I write the above constructor code in any of my controller actions everything is fine. in ZF1 I could initialize any variable in the init() method and could use the variable in any of my actions. How can I do this in ZF2? Here, I want to detect if the user is logged in the constructor itself. Now I have to call the plugin in every action which I don't want.
What should I do here?
The error you are receiving is because you are trying to use the ServiceManager (via the Zend\Mvc\Controller\PluginManager) in the __construct method of the controller.
When a controller is registered as an invokable class, the Service Manager (ControllerManager) is responsible for the creating the controller instance. Once created, it will then call the controllers various default 'initializers' which also inlcudes the plugin manager. By having your code in __construct it is trying to use the plugin manager before it has been set.
You can resolve this by using a controller factory, rather than an invokable in module.config.php.
'controllers' => [
'factories' => [
'MyModule\Controller\Foo' => 'MyModule\Controller\FooControllerFactory',
],
],
Then the factory
namespace MyModule\Controller\FooControllerFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class FooControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
$serviceManager = $controllerManager->getServiceLocator();
$controllerPluginManager = $serviceManager->get('ControllerPluginManager');
$authPlugin = $controllerPluginManager->get('AuthPlugin');
return new FooController($authPlugin);
}
}
Lastly, update the controller __construct to add the new argument and remove the call to $this->authPlugin()
class FooController extends AbstractActionController
{
public function __construct(AuthPlugin $authentication)
{
$this->authentication = $authentication;
$this->isloggedin = $authentication->isloggedin();
}
}

Proper implementation for multiple validation

Here's my code:
I'm trying to implement it on ServiceProvider but I don't have any luck.
//Contact.php
class Contact extends \Eloquent {
protected $fillable = array('email', 'name', 'subject', 'msg');
public static $rules = array(
'email' => 'required|email',
'name' => 'required',
'subject' => 'required',
'msg' => 'required'
);
public static function validate($input) {
return Validator::make($input, static::$rules);
}
}
//Registration .php
class Registration extends \Eloquent {
protected $fillable = array('name', 'address', 'birthdate', 'gender', 'civil_status', 'nationality', 'contact_number', 'email', 'invited');
protected $guarded = array('id');
public static $rules = array(
"name" => "required|alpha_spaces",
"address" => "required",
"contact_number" => "required|numeric",
"email" => "required|email|unique:registrations"
);
public static function validate($input) {
return Validator::make($input, static::$rules);
}
}
class HomeController extends BaseController
{
public function postContactForm()
{
return Contact::validate(Input::all());
}
public function postRegistrationForm()
{
return Registration ::validate(Input::all());
}
}
Is the a way that I can implement it like this?
$this->validate-check(Input::all());
I'm trying to refactor my code and also still new using laravel 4 as well.
Thanks,
Aldren,
I think a Service Provider is a bit overkill for this task. You can create something like a Validation Service. Let me explain:
Say you put your custom files under app/src and use composer to autoload the classes there.
Create an abstract Validator class. This way you can extend this class for every model you need to validate:
<?php namespace Foo\Services\Validation;
abstract class Validator {
protected $errors;
public function check($validator)
{
if ($validator->fails()) {
$this->errors = $validator->messages();
return false;
}
return true;
}
public function isValidForCreation($input)
{
$validator = \Validator::make($input, static::$insertRules);
return $this->check($validator);
}
public function isValidForUpdate($input)
{
$validator = \Validator::make($input, static::$updateRules);
return $this->check($validator);
}
public function errors()
{
return $this->errors;
}
}
Now, lets say you want to validate your Contact model input, right ? You can then create a ContactValidator class that extends our Validator abstract class:
<?php namespace Foo\Services\Validation;
class ContactValidator extends Validator
{
static $insertRules = [
'name' => 'required'
];
static $updateRules = [
'name' => 'required'
];
}
All right, so now we have our boilerplate done. Now lets go to ContactsController to implement our new ContactValidator.
First of all, we need to inject our validator inside the controller. IMHO the best way to do it is in the controllers constructor.
So, lets go:
<?php
use Foo\Services\Validation\ContactValidator as Validator;
class ContactsController extends \BaseController {
protected $validator;
function __construct(Validator $validator)
{
$this->validator = $validator;
}
Great! Now we have it injected. Next, we have to make sure our ContactValidator is invoked when I try to store a Contact. Lets say your method is called store().
public function store()
{
if(!$this->validator->isValidForCreation(Input::all()))
{
return Redirect::back()->withErrors($this->validator->errors())->withInput();
}
else
{
//store your data here.
}
}
You can use either $this->validator->isValidForCreation or $this->validator->isValidForUpdate to check your input against the Validator Service.
I hope you can understand everything and if you have any doubts please let me know.
Cheers and good coding :D
Thanks for the input GustavoR, I get what you want to explain right here. But is it possible to implement the $this->validator in one controller?
use Foo\Services\Validation\MainValidator as Validator;
class HomeController extends BaseController
{
protected $validator;
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function postContactForm()
{
return ( $this->validator->isValidForCreation(Input::all()) );
}
public function postRegistrationForm()
{
return ( $this->validator->isValidForCreation(Input::all()) );
}
}
Again, thanks for the input :)

Model binding from input form

I'm working with Laravel and it seems in examples that they decide to implement validation inside controller, and I don't like it at all. What I want to ask is if there is some kind of bind method that can bind posted input fields to object that I created so that I can make sure my controllers are not messy.
I will try to explain what I want in code, I think it will be much clearer.
What I have
public function postRegister() {
$validation = Validator::make(Input::all(), array(
'email' => 'required|email',
'password' => 'required|min:6',
'name' => 'required|alpha',
'gender' => 'required|in:male,female'
));
if ($validation->fails()) {
Input::flashExcept('password');
return Redirect::to('register')->withErrors($validation)->withInput();
}
// Register user...
}
What I want to have
class UserRegisterDto {
public $email;
public $password;
public $name;
public $gender;
protected $errors;
public function isValid() {
// Validate it here, set errors if there are some
return $validator->isValid();
}
public function getErrors() {
return $this->errors;
}
}
public function postRegister() {
$user = Input::bind('UserRegisterDto'); // This is made-up function, I wonder if something like this exists
if ($user->isValid()) {
// Register user...
}
}
Ardent can help you to keep the controllers clean: https://github.com/laravelbook/ardent
"Ardent models use Laravel's built-in Validator class. Defining validation rules for a model is simple and is typically done in your model class as a static variable"

Passing properties from a child object to the parent PHP

I've worked with cakePHP in the past and liked the way they built their model system. I want to incorporate their idea of handling validation between extended models.
Here is an example:
class users extends model {
var $validation = array(
"username" => array(
"rule" => "not_empty"
),
"password" => array(
"rule" => "valid_password"
)
);
public function create_user() {
if($this->insert() == true) {
return true;
}
}
}
class model {
public function insert() {
if(isset($this->validation)) {
// Do some validation checks before we insert the value in the database
}
// Continue with the insert in the database
}
}
The problem with the this is that model has no way of getting the validation rules as it's the parent class. Is there a way I can pass the $validation property to the parent class without explicitely passing the validation rules through say the create_user() method as a parameter?
EDIT:
Also, avoiding passing it via the __construct() method to the parent class. Is there another way of doing this which would not cause a lot of extra code within my users class but get the model class to do most of the work (if not all?)
If the instance is a $user, you can simply refer to $this->validation in model::insert().
It would seem that model should also be abstract in this case, preventing instantiation and perhaps confusion.
Create a new abstract method in the model class named: isValid() that each derived class will have to implement, then call that method during the insert() function.
model class:
class model {
abstract protected function isValid();
public function insert() {
if($this->isValid())) { // calls concrete validation function
}
// Continue with the insert in the database
}
}
user class:
class users extends model {
var $validation = array(
"username" => array(
"rule" => "not_empty"
),
"password" => array(
"rule" => "valid_password"
)
);
protected function isValid() {
// perform validation here
foreach ($this->validation) { //return false once failed }
return true;
}
public function create_user() {
if($this->insert() == true) {
return true;
}
}
}

Categories