Yii Custom Validation - Class Validate contains 1 abstract method - php

I have some validation methods that I need to use for multiple models. For example validation of phone numbers can be shared across multiple models.
I understand from http://www.yiiframework.com/wiki/56/ I can create an extension that can be used by multiple models, for example:
array('phone', 'ext.Validate.Validate'),
I have modified this line a few times and can confirm it is hitting the right file.
With the following in Validate.php
class Validate
I get the error Call to undefined method Validate::applyTo(), therefore I have changed it to
class Validate extends CValidator
As suggested by the link above, however I now get the error:
Class Validate contains 1 abstract method
Here is the file as it stands:
<?php
class Validate extends CValidator
{
public function phone($phone)
{
if(!ctype_digit($phone))
{
$this->addError($phone, Yii::t('flash','flash.not_authorised',array('{attribute}'=>$phone)).' '.ucfirst(str_replace('_', ' ', $phone)).' field');
}
else
{
return true;
}
}
}
Can someone point me in the right direction as to how I can have a shared validation between multiple methods using the above.

Ok I managed to solve this.
I found the following after a few hours of searching http://www.yiiframework.com/wiki/56/
The main issue is tat you need the following method in your class, when extending CValidator:
protected function validateAttribute($object,$attribute)
{
}
Here is the model code (yii/protected/extension/Validate/Validate.php)
array('phone', 'ext.Validate.Validate'),
Here is the class code:
<?php
class Validate extends CValidator
{
protected function validateAttribute($object,$attribute)
{
self::{$attribute."Validation"}($object,$attribute);
}
protected function phoneValidation($object,$attribute)
{
if(!empty($object->$attribute))
{
if(!ctype_digit($object->$attribute))
{
$this->addError($object,$attribute,Yii::t('app','validation.telephon_failed',array('{attribute}'=>ucwords($attribute))));
}
}
}
}

Related

Is there a common naming convention for custom trait/attribute model in Laravel?

I am designing an app where user can define traits/attributes for objects.
I was planning to go like this
class AttributeName extends Model
{
//
}
class Attribute extends Model
{
public function attributeName()
{
return $this->belongsTo('AttributeName');
}
public function job()
{
return $this->belongsTo('Job');
}
}
class Job extends Model
{
public function attributes()
{
return $this->hasMany('Attribute');
}
public function getAttributeValue($attributeNameId)
{
return $this->attributes->where('attribute_name_id', $attributeNameId)->first()->value ?? ;;
}
}
Unfourtunately, this gives me hard time as $this->attributes has another meaning for Laravel models. So Attribute is not a very good name for these things.
Naming it trait won't go well in PHP either. So what should I name this? Is there any common name that reflects semantics as well as attribute or trait does but doesn't conflict with other uses in Laravel and PHP?

Traits with PHP and Laravel

I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parent in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.
Similarly, as the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
add custom Accessors and Mutators to an Eloquent model class
add additional items to the protected $appends array matching these methods
One approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Model class with your own enhanced version):
trait AppendingGlue {
public function __construct() {
// parent refers not to the class being mixed into, but its parent
parent::__construct();
// Find and execute all methods beginning 'extraConstruct'
$mirror = new ReflectionClass($this);
foreach ( $mirror->getMethods() as $method ) {
if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
$method->invoke($this);
}
}
}
}
Then any number of Traits implementing differently named extraConstruct methods:
trait AwesomeSauce {
public function extraConstructAwesomeSauce() {
$this->appends[] = 'awesome_sauce';
}
public function doAwesomeSauceStuff() {
}
}
trait ChocolateSprinkles {
public function extraConstructChocolateSprinkles() {
$this->appends[] = 'chocolate_sprinkles';
}
public function doChocolateSprinklesStuff() {
}
}
Finally, we mix in all the traits into a plain model, and check the result:
class BaseModel {
protected $appends = array('base');
public function __construct() {
echo "Base constructor run OK.\n";
}
public function getAppends() {
return $this->appends;
}
}
class DecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}
$dm = new DecoratedModel;
print_r($dm->getAppends());
We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:
class ReConstructedModel extends BaseModel {
use AppendingGlue { __construct as private appendingGlueConstructor; }
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Call the mixed-in constructor explicitly, like you would the parent
// Note that it will call the real parent as well, as though it was a grand-parent
$this->appendingGlueConstructor();
echo "New constructor executed!\n";
}
}
This can be avoided by inheriting from a class which either exists instead of the AppendingGlue trait, or already uses it:
class GluedModel extends BaseModel {
use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Standard call to the parent constructor
parent::__construct();
echo "New constructor executed!\n";
}
}
Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to $appends from a trait, you could simply do this...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
protected $appends = array('age','saucedByCurrentUser');
You could do this:
protected $appends = array('age','sauced_by_current_user');
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);

FuelPHP simple class Not Found Error

This is my first deployment of FuelPHP, though I am a long time user of CodeIgniter.
I am getting the following error when I load the page:
ErrorException [ Fatal Error ]:
Class 'Model\Model_UPS' not found
/classes/controller/ups.php
<?php
use \Model\Model_UPS;
class Controller_UPS extends Controller {
public function action_index() {
$view = View::forge('json');
$view->title = Model_UPS::get_load();
return $view;
}
}
?>
/classes/model/model_ups.php or ups.php
<?php
namespace Model;
class Model_UPS extends \Model {
public static function get_load() {
return "This is the load!";
}
}
?>
/views/json.php
<?=$title;?>
The error page highlights the $view->title = Model_UPS::get_load(); line of ups.php. I have tried just about every configuration of use, namespace, model filename, and model class name that I can think of. I can't seem to find a super simple MVC example to use as a guide. I've tried to duplicate the FuelPHP Docs as best as I can, but have failed. Can anyone find anything wrong with this?
Rename file: model/model_ups.php to model/ups.php
Rename class: Model_UPS to UPS
Change: use \Model\Model_UPS; to use \Model\UPS;
Change: Model_UPS::get_load(); to UPS::get_load();

adding a custom function to a model in cakephp

I have a users table that has location attributes and would like to create a model function that retrieves nearby users (within a given radius). Here is my model:
class User extends AppModel {
public function getNearbyUsers($id,$dist=10) {
return $this->query(...);
}
}
And here is my controller where I am trying to invoke the function:
class UsersController extends AppController {
public function getNearbyUsers($id) {
...
$this->User->getNearbyUsers($id)
...
}
}
However doing so results in: PHP Fatal error: Call to a member function getNearbyUsers() on a non-object
What am I doing wrong?
EDIT: nevermind, it is not complaining about that anymore. But it is throwing an SQL error and my model function is never actually being called. Upon further inspection on the mysql query log I see this:
Query SHOW TABLES FROM `xxx`
Query getNearbyUsers
Quit
Seems CakePHP is interpreting $this->User->getNearbyUsers as a literal query. So my question still remains: how to add custom functions to a model in Cake?
See http://book.cakephp.org/2.0/en/models/additional-methods-and-properties.html:
While CakePHP’s model functions should get you where you need to go,
don’t forget that model classes are just that: classes that allow you
to write your own methods or define your own properties.
Any operation that handles the saving and fetching of data is best
housed in your model classes. This concept is often referred to as the
fat model.
Model
class Example extends AppModel {
function getRecent() {
$conditions = array(
'created BETWEEN (curdate() - interval 7 day) and (curdate() - interval 0 day)'
);
return $this->find('all', compact('conditions'));
}
}
This getRecent() method can now be used within the controller.
Controller
$recent = $this->Example->getRecent();
There are a few additional items that needs to be in the code, or else you will get the non-object error.
In the App Model :
<?php
class Get extends AppModel {
public function getRecent() {
// $conditions = array(
// 'created BETWEEN (curdate() - interval 7 day)' .
// ' and (curdate() - interval 0 day))'
// );
// return $this->find('all', compact('conditions'));
}
}
In the app controller,
?php
class GetsController extends AppController {
public $uses = array('Get'); // Needed, or the error will appear.
public function Example () {
$this->Get->getRecent();
}
}
Had the same issue with Cake 1.3, using Plugins (Modules), and even if we had the model name unique in the whole app (some model names are used in more then one plugin) it only worked once I requested the Model in the controller's $uses array with it's plugin to, like this: 'Module1.A'
app/plugins/plugin1/controllers/a_controller.php:
class AController extends AppController {
// using simple array('A') worked fine for cake methods (find, query ...)
// but did not recognized the custom method
public $uses = array('Plugin1.A');
public function Example () {
$this->A->customMethod();
}
}
app/plugins/plugin1/models/a.php:
<?php
class A extends AppModel {
public function customMethod() {
// ...
}
}

Critique abstract class for handling GET and POST requests?

I'm only interested in handling GET or POST requests, so I designed this abstract class to determine which request has been made and to subsequently call the appropriate function. I would really appreciate feedback on this. Thanks!
PS I think this should be a community wiki, but I'm not sure how to set it as that.
abstract class AHttpRequestHandler
{
public function handleRequest()
{
if($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePostRequest();
} else if($_SERVER['REQUEST_METHOD'] == 'GET') {
$this->handleGetRequest();
} else {
$this->handleIllegalRequest();
}
}
abstract protected function handleGetRequest();
abstract protected function handlePostRequest();
protected function handleIllegalRequest()
{
throw new Exception('Illegal request detected in HttpRequestHandler::handleIllegalRequest().');
}
}
In response to comments:
I will only be handling one or the other (GET or POST), never both at the same time.
Either an HTML form will be submitted via POST, or a redirect will be made with a query string, which will be a GET request. I am not familiar with how a mixed request could be made (both GET and POST), but since this is a personal project I have control over whether it happens or not.
I use the AHttpRequestHandler class (above) by implementing the handleGetRequest() and handlePostRequest() methods in a sub-class, which is and abstract controller, AController. Then, for each page of my CMS, I create a sub-class of AController, such as ImageUpload or ImageDetailsEditor. I can provide more details if it will help.
Here are the AController, Controller, and View classes:
AController
abstract class AController extends AHttpRequestHandler
{
protected $view;
public function __construct()
{
$this->handleRequest();
}
protected function handleGetRequest()
{
throw new Exception('handleGetRequest not yet implemented.');
}
protected function handlePostRequest()
{
throw new Exception('handlePostRequest not yet implemented.');
}
abstract protected function initView();
}
Controller
class Controller extends AController
{
protected $content;
public function __construct()
{
$this->view = new View();
parent::__construct();
}
protected function handleGetRequest()
{
$this->content = 'GET Request';
$this->initView();
}
protected function handlePostRequest()
{
$this->content = 'POST Request';
$this->initView();
}
protected function initView()
{
$this->view->content = $this->content;
$this->view->display();
}
}
View
//An over-simplified view for example use only
class View
{
public $content;
public function display()
{
echo "<p>$this->content</p>";
}
}
The actual use:
require_once 'Controller.php';
$controller = new Controller();
First of all you can make a GET request and a POST request in the same time. Think of a form that you post but the url has some variables in the query ( get ).
1.I don't understand the need for such a class but the first thing you could do is make two separate classes for post and get that extend the AHttpRequestHandler class. That way you only need an abstract function handleRequest that you will implement in the child classes.
2.You should apply "Intention Revealing Names". Your class should be RequestHandler and your methods should not contain Request in them. You know that from the class name.
3.Think about this: you might need to handle the post request in one controller. So you will have to add the second abstract method each time just to respect the abstract class.
4.You should not make circular calls between classes ( The Hollywood principle ). handleRequest is called from the child class, and then the parent calls handleGetRequest or handlePostRequest from the child.
Like I said, you are the developer, you know each controller what will use:POST or GET ( what about COOKIEs? ), so you can handle them at controller level without the need to extra classes just for the sake of it.
see ref
see ref
see ref
see ref
And the Controller should receive a request (command), not extend the request to keep things apart. Have no catch phrase for that, perhaps seperation of concerns. That's an extension to 1. above but only if you really need a request object.
Having an abstract class for requests is a good idea and it is there in all frameworks. But I dont think its good to extend this class by all controllers. A better solution will be to separate this to two, an abstract request class and base controller class. In request class you can have methods to identify whether it is a get request or post request, like
class Request{
public function isPost() {
return ($_SERVER['REQUEST_METHOD'] == 'POST');
}
public function isGet() {
return ($_SERVER['REQUEST_METHOD'] == 'GET');
}
}
Also we will have a base controller class with at least the following options
class Controller
{
public $request;
public function __construct() {
$this->setRequest(new Request());
}
public function setRequest(Request $request) {
$this->request = $request;
}
}
All our client controllers will extend the base controller as usual. The advantage of this method is client controllers will have the freedom to determine the request type. if they want to make use of GET and POST request at a time, that also will be possible. The above given is of course an incomplete one. You need to add more methods to the base classes or not is your choice.

Categories