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;
}
}
}
Related
My first controller is
class MatchesController extends AbstractActionController {
public function checkLogsAction() {
// $logs=new LogsController();
$logs=$this->getServiceLocator()->get('Admin\LogsController');
$logs->writeLogs("log data");
die();
}
Logs Controller
class LogsController extends AbstractActionController {
public function writeLogs($logData) {
$this->getServiceLocator()->get('Zend\Log\opta')->info($logData);
return true;
}
global.php
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
'Zend\Log\opta' => function ($sm) {
$fileName=date("Y-m-d");
$log = new Zend\Log\Logger();
$writer = new Zend\Log\Writer\Stream("./data/opta/$fileName");
$log->addWriter($writer);
return $log;
}
),
),
module.php
public function getServiceConfig() {
return array(
"factories"=>array(
'Admin\LogsController' => function ($sm) {
$logsController = new LogsController();
return $logsController;
},
I am getting this error:
Fatal error: Call to a member function get() on null
Please help me to solve the solution
Your Admin\LogsController extends AbstractActionController. But you do not use it as AbstractActionController!
An AbstractActionController is usuallay invoked by processing the (http) request, whereby the ZF2 application is going to route the request to a controller and executes an action method. During this processing, an instance of ServiceLocator/ServiceManager is passed to the controller. That is what you are missing. Hence, you try to call a method on a null object.
You can not simply instantiate an ActionController from another ActionController. (of course, it is possible, with a lot of afford). If you use it this way, you to make sure the new controller instance holds an instance of the ServiceLocator, request, response etc...
You should consider:
a) is Admin\LogsController really a AbstractActionController in your application? (I assume it is not, respectively your code example)
b) inject the ServiceLocator in to your custom object (LogsController), or a way cleaner: inject the logger instance.
Example:
public function getServiceConfig() {
return array(
'factories' => array(
'Admin\LogsController' => function ($sm) {
$logsController = new LogsController();
$logsController->setServiceLocator($sm); // you have to implement!
return $logsController;
},
);
}
I'm attempting to write a test for a model that has no table but sends an email if the data passes validation in CakePHP 2.
To test I want to assert that some data passes validation and would therefore send an email without actually sending one. For this I am attempting to create a mock method for CakeEmail. However, the test is failing because $useDbConfig hasn't be defined for the mock method:-
Undefined property: Mock_MyModel_7a1fb8d0::$useDbConfig
I assume this is an issue with the model not having a table, but cannot see how to resolve it.
My model looks something like (excluding the validation rules):-
<?php
App::uses('CakeEmail', 'Network/Email');
class MyModel extends AppModel {
public $useTable = false;
public function send($data) {
$this->set($data);
if ($this->validates() === false) {
return false;
} else {
$Email = $this->getEmailer();
$Email->from($data['MyModel']['email_from']);
$Email->to($data['MyModel']['email_to']);
$Email->subject($data['MyModel']['subject']);
$Email->send($data['MyModel']['message']);
}
return true;
}
public function getEmailer() {
return new CakeEmail();
}
}
My test is:-
<?php
class MyModel extends CakeTestCase {
public function setUp() {
parent::setUp();
$this->MyModel = ClassRegistry::init('MyModel');
}
public function testSend() {
$emailer = $this->getMock(
'CakeEmail',
array(
'to',
'emailFormat',
'subject',
'replyTo',
'from',
'template',
'viewVars',
'send'
)
);
$emailer->expects($this->any())->method('send')->will($this->returnValue(true));
$MyModel = $this->getMockForModel('MyModel', array('getEmailer'));
$MyModel->expects($this->once())->method('getEmailer')->will($this->returnValue($emailer));
$data = array(
'MyModel' => array(
'email_to' => 'foo#example.com',
'email_from' => 'bar#example.com',
'subject' => 'Foo bar',
'message' => ''
)
);
$result = $MyModel->send($data);
$this->assertTrue($result);
}
}
Any help would be appreciated. This is the first time I've tried/needed to mock a method in Cake using tests.
Class name should have been MyModelTest rather than MyModel. CakePHP's naming convention needs to be adhered to.
I need to programmatically change the behaviour of a form based on some options. Let's say, for example, I'm displaying a form with some user's info.
I need to display a checkbox, "send mail", if and only if a user has not received an activation mail yet. Previously, with ZF1, i used to do something like
$form = new MyForm(array("displaySendMail" => true))
which, in turn, was received as an option, and which allow'd to do
class MyForm extends Zend_Form {
protected $displaySendMail;
[...]
public function setDisplaySendMail($displaySendMail)
{
$this->displaySendMail = $displaySendMail;
}
public function init() {
[....]
if($this->displaySendMail)
{
$displaySendMail new Zend_Form_Element_Checkbox("sendmail");
$displaySendMail
->setRequired(true)
->setLabel("Send Activation Mail");
}
}
How could this be accomplished using Zend Framework 2? All the stuff I found is about managing dependencies (classes), and nothing about this scenario, except this SO question: ZF2 How to pass a variable to a form
which, in the end, falls back on passing a dependency. Maybe what's on the last comment, by Jean Paul Rumeau could provide a solution, but I wasn't able to get it work.
Thx
A.
#AlexP, thanks for your support. I already use the FormElementManager, so it should be straightforward. If I understand correctly, I should just retrieve these option in my SomeForm constructor, shouldn't I?
[in Module.php]
'Application\SomeForm' => function($sm)
{
$form = new SomeForm();
$form->setServiceManager($sm);
return $form;
},
while in SomeForm.php
class SomeForm extends Form implements ServiceManagerAwareInterface
{
protected $sm;
public function __construct($name, $options) {
[here i have options?]
parent::__construct($name, $options);
}
}
I tryed this, but was not working, I'll give it a second try and double check everything.
With the plugin managers (classes extending Zend\ServiceManager\AbstractPluginManager) you are able to provide 'creation options' array as the second parameter.
$formElementManager = $serviceManager->get('FormElementManager');
$form = $formElementManager->get('SomeForm', array('foo' => 'bar'));
What is important is how you have registered the service with the manager. 'invokable' services will have the options array passed into the requested service's constructor, however 'factories' (which have to be a string of the factory class name) will get the options in it's constructor.
Edit
You have registered your service with an anonymous function which mean this will not work for you. Instead use a factory class.
// Module.php
public function getFormElementConfig()
{
return array(
'factories' => array(
'Application\SomeForm' => 'Application\SomeFormFactory',
),
);
}
An then it's the factory that will get the options injected into it's constructor (which if you think about it makes sense).
namespace Application;
use Application\SomeForm;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class SomeFormFactory implements FactoryInterface
{
protected $options = array();
public function __construct(array $options = array())
{
$this->options = $options;
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new SomeForm('some_form', $this->options);
}
}
Alternatively, you can inject directly into the service you are requesting (SomeForm) by registering it as an 'invokeable' service; obviously this will depend on what dependencies the service requires.
Classic problem:
verify that a user accepted the contract terms but the value of the acceptance is not stored (bound) in the database...
Extend CFormModel rather than CActiveForm (because CActiveForm binds
values to DB)
Post a CFormModel to a controller action
Validate a CFormModel
I'm asking this question to answer it because the existing questions end in see the documentation...
extend CFormModle, define the rules and got to validate. With bound variables you validated as part of save. Now you validate() by itself but Validate requires a list of attributes which is not defined in CFormModel. So, what do you do? You do this:
$contract->validate($contract->attributeNames())
Here's the full example:
class Contract extends CFormModel
{
...
public $agree = false;
...
public function rules()
{
return array(
array('agree', 'required', 'requiredValue' => 1, 'message' => 'You must accept term to use our service'),
);
}
public function attributeLabels()
{
return array(
'agree'=>' I accept the contract terms'
);
}
}
Then in the controller you do this:
public function actionAgree(){
$contract = new Contract;
if(isset($_POST['Contract'])){
//$contract->attributes=$_POST['Contract']; //contract attributes not defined in CFormModel
...
$contract->agree = $_POST['Contract']['agree'];
...
}
if(!$contract->validate($contract->attributeNames())){
//re-render the form here and it will show up with validation errors marked!
}
The results:
For some models, we have soft deletion implemented using a valid boolean in MySQL.
In the class, the scopes method is defined as follows:
public function scopes() {
return array(
'valid'=>array(
'condition'=>"t.valid=1",
)
);
}
This is so that when we load a model we can call the scope to make it include only valid (not deleted) models alongside the other find criteria, or whatever it happens to be.
This isn't very DRY and I am wondering if there is an alternative way of achieving the same thing, that could perhaps be applied to an interface, the abstract Model class that all models derive from, or, if using 5.4, a trait.
Yii has a feature called Behaviors
that is similar to php 5.4 traits but works with earlier versions too.
SoftDeleteBehavior.php:
class SoftDeleteBehavior extends CActiveRecordBehavior {
public $deleteAttribute = 'valid';
public $deletedValue = 0;
public function beforeDelete($event) {
if ($this->deleteAttribute !== null) {
$this->getOwner()->{$this->deleteAttribute} = $this->deletedValue;
$this->getOwner()->update(array($this->deleteAttribute));
// prevent real deletion of record from database
$event->isValid = false;
}
}
/**
* Default scope to be applied to active record's default scope.
* ActiveRecord must call this from our own default scope.
* #return array the scope to be applied to default scope
*/
public function defaultScope() {
return array(
'condition' => $this->getOwner()->getTableAlias(false,false).'.'.$this->deleteAttribute
. ' <> '.var_export($this->deletedValue, true),
);
}
}
Then i have this class to apply deafultscope from behaviors:
ActiveRecord.php (i ofcourse have more methods in this class, downside is that you need to call parent method if you need to extend the method):
class ActiveRecord extends CActiveRecord {
public function defaultScope() {
$scope = new CDbCriteria();
foreach ($this->behaviors() as $name => $value) {
$behavior = $this->asa($name);
if ($behavior->enabled && method_exists($behavior,'defaultScope')) {
$scope->mergeWith($behavior->defaultScope());
}
}
return $scope;
}
}
And then you use it in your Models:
class MyModel extends ActiveRecord {
public function behaviors() {
return array(
'SoftDeleteBehavior' => array(
'class' => 'application.components.behaviors.SoftDeleteBehavior',
),
);
}
}
PROTIP: you can specify your own ActiveRecord class when you generate models with gii