I would like to override the standard URL validation rule to make it more tolerant of a whitespace character before or after the URL. Basically use the trim() function on the url before passing it to the standard URL validation handler.
I know I need to override that rule but I'm not exactly where and how I need to do it.
(Plus, the CakePHP API and book documentation are currently offline. Upgrades, I know...)
You can add custom validation rules in your Model classes, your Behavior classes, or in the AppModel class:
http://book.cakephp.org/view/150/Custom-Validation-Rules#Adding-your-own-Validation-Methods-152
Since you want to override an existing method, just give it the same name and signature as the original. Something like this might do the trick:
function url($check, $strict = false) {
return Validation::url(trim($check), $strict);
}
Why would you wanna do that?
Simply make sure all posted data is always trimmed.
Thats cleaner and more secure, anyway.
I have a component doing that in beforeFilter:
/** DATA PREPARATION **/
if (!empty($controller->data) && !Configure::read('DataPreparation.notrim')) {
$controller->data = $this->trimDeep($controller->data);
}
The trimDeep method:
/**
* #static
*/
function trimDeep($value) {
$value = is_array($value) ? array_map(array(&$this, 'trimDeep'), $value) : trim($value);
return $value;
}
Related
I am trying to overwrite a bulk clone function.
I just want to clone some values, and the rest assign a static value. For example, I just want to clone the name and description values, and the date I want to assign it the current date.
And well I don't know how to do it.
use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation { bulkClone as traitBulkClone; }
public function bulkClone($id) {
// your custom code here
$this->traitBulkClone($id);
}
TLDR: The most efficient way would probably be to overwrite the replicate() method on your model. Note that it is not a Backpack method, but an Eloquent method that BulkCloneOperation uses to duplicate a particular entry.
WHY?
Inside the BulkCloneOperation that you're using, you'll notice the route calls the bulkClone() method, that itself is just making some calls to the replicate() method on the model. That means you have two options to override this behaviour:
(Option A). Override the bulkClone() method in your CrudController. This will override the behaviour only on that particular admin operation.
(Option B). Override the replicate() method in you Model. That way, any time replicate() is called (by your admin panel or any other part of your software), the duplication is done in the way you specified.
In most cases, I think Option B is more appropriate, since it would avoid future code duplication. Here's Laravel's replicate() method at this time, just copy-pasting it into your model and modifying it to fit your needs is the best solution, if you ask me:
/**
* Clone the model into a new, non-existing instance.
*
* #param array|null $except
* #return static
*/
public function replicate(array $except = null)
{
$defaults = [
$this->getKeyName(),
$this->getCreatedAtColumn(),
$this->getUpdatedAtColumn(),
];
$attributes = Arr::except(
$this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
);
return tap(new static, function ($instance) use ($attributes) {
$instance->setRawAttributes($attributes);
$instance->setRelations($this->relations);
$instance->fireModelEvent('replicating', false);
});
}
To give you a brief view: I am trying to make redirect based on conditions. If request satisfy conditions of given destination you will get redirect to certain destination.
I have Destination model which has many conditions (Condition model). I have many Conditions which extends basic Condition model like DateCondition, LocationCondition etc (made by polimorphic relation). Each of condition type should has 'service' which tells if given condition match do the request. Example DateCondition should has own DateConditionMatcher which implements ConditionMatcherInterface.
(just public function match($condition, $request)).
I would like to write it with Open Closed principle from SOLID. Firstly I thought to add getMatcher() function straight to condition model and return different ConditionMatcher for each condition type, but some of ConditionMatchers need some other services passed in contructor, so it would force me to inject them also in Condition models which is bad pratice.
Maybe using Contextual binding in ServiceProvider could resolve this but how?
I have no idea how to couple Model to to the right one ConditionMatcher to then use it freely like this:
foreach ($destination->conditions as $condition){
$isMatched = $this->conditionMatcher->match($condition, $request);
}
To always have correct ConditionMatcher under $this->conditionMatcher.
I hope someone understood my not very clear message.
I had an idea overnight to make service which keeps all the matchers. The only downside to this solution is injecting every matcher.
class ConditionResolver implements ConditionResolverInterface
{
/** #var ConditionMatcherInterface[] */
private $conditionMatchers = [];
public function __construct(
DateConditionMatcher $dateConditionMatcher,
TimeConditionMatcher $timeConditionMatcher,
LocationConditionMatcher $locationConditionMatcher
) {
$this->conditionMatchers[DateCondition::class] = $dateConditionMatcher;
$this->conditionMatchers[TimeCondition::class] = $timeConditionMatcher;
$this->conditionMatchers[LocationCondition::class] = $locationConditionMatcher;
}
}
so now I am able to use correct matcher to given condition in this way(simplified):
foreach ($model->conditions as $condition)
{
$this->conditionMatchers[get_class($condition)]->match($condition, $request);
}
It allows me to inject various of services into that matchers in AppServiceProvider.
$this->app->singleton(LocationServiceInterface::class, function($app){
return new LocationService();
});
$this->app->singleton(DateConditionMatcher::class, function($app){
return new DateConditionMatcher();
});
$this->app->singleton(TimeConditionMatcher::class, function($app){
return new TimeConditionMatcher();
});
$this->app->singleton(LocationConditionMatcher::class, function($app){
return new LocationConditionMatcher($app->make(LocationServiceInterface::class));
});
Overall I think I miessed something and it would be done in more elegant way, but for now I treat it as an answer. If you have better solution, please share :)
How can I call the CakePHP 3.x built-in 'rule'=>'email' inside of my own validation rule? I would like to make this check among other customized checks not in e.g. validationDefault function.
public function myValidationRule($value,$context){
// HERE -- how can I call standard email rule
}
Except for requirePresence, allowEmpty and notEmpty, all built-in rules map to corresponding static methods on the \Cake\Validation\Validation class, which you can invoke manually yourself if necessary.
The email rule uses Validation::email(), so you can use it like
public function myValidationRule($value, $context) {
// ...
$isValid = \Cake\Validation\Validation::email($value);
// ...
}
See also
Cookbook > Validation > Core Validation Rules
API > \Cake\Validation\Validation::email()
public function myValidationRule($value,$context){
// HERE -- you can get your email in $value and other values in $context
// HERE you can add any of your custome validation rule
// for example
return $value==null;
// it will return true if your email is null.
}
The Model View Controller architecture tells me that all my business logic should be inside the Model, while the data flow should be handled by the Controller.
Knowing this, while I'm dealing with my logic inside the Model, I need to let the Controller know if he's supposed to redirect to another url, redirect back, what kind of message or variable to pass during the redirection, etc.
What is the best way of doing this?
I can think of some ways, like throwing exceptions on the Modeland catching them on the Controller or returning an array from the Model and treating it on the Controller, but none of them seem very nice. The easiest way would be calling the Redirect->to() (or back()) inside the Model and just returning the Model's return on the Controller, but it seem to break the architecture's separation of rules.
Is there a "right" way of doing this? What would be the pros and cons of each way?
EDIT:
The answer below is old. Laravel now includes a bunch of different ways of handling common problems.
For example, use Laravel's FormRequest's as a way of validating data easily on controller methods, and Jobs to handle business logic for creating / updating models.
OLD POST:
This is a common question, and while the 'MVC' pattern is nice for a basic starting point for a web app, I feel like the majority of developers always need another intermediate service for validation, data handling, and other problems that come up during development.
To answer your question without bias: There is no right way.
To answer your question with my own personal bias, I feel the majority of developers will use the Repositories or Services pattern to handle intermediate data handling between the controller and the model, and also have separate classes for validation as well.
In my opinion, Repositories are better for a framework and data agnostic design (due their interface driven implementation), and Services are better for handling the business logic / rules. Controllers are better used for handling responses and for passing the input data to the repository or the service.
The paths for each of these patterns are the same though:
Request -> Controller (Validation) -> Service -> Model -> Database
Request -> Controller (Validation) -> RepositoryInterface -> Model -> Database
Validation is in brackets since input isn't passed from the validator to the service / repository, the input sent to the validator, gives the 'OK', and let's the controller know it's ok to send the data to the Service / Repository to be processed.
I only use Services when I'm absolutely positive I won't be changing frameworks or data sources. Otherwise I'll use Repositories. Repositories are just a little more work to setup, since you'll need to make Laravel resolve the interface to your repository class through its IoC.
Services Example:
The Service:
namespace App\Services;
use App\Models\Post;
class PostService
{
/**
* #var Post
*/
protected $model;
/**
* Constructor.
*
* #param Post $post
*/
public function __construct(Post $post)
{
$this->model = $post;
}
/**
* Creates a new post.
*
* #param array $input
*/
public function create(array $input)
{
// Perform business rules on data
$post = $this->model->create($input);
if($post) return $post;
return false;
}
}
The Controller:
namespace App\Http\Controllers;
use App\Services\PostService;
use App\Validators\PostValidaor;
class PostController extends Controller
{
/**
* #var PostService
*/
protected $postService;
/**
* #var PostValidator
*/
protected $postValidator;
/**
* Constructor.
*
* #param PostService $postService
* #param PostValidator $postValidator
*/
public function __construct(PostService $postService, PostValidator $post Validator)
{
$this->postService = $postService;
$this->postValidator = $postValidator;
}
/**
* Processes creating a new post.
*/
public function store()
{
$input = Input::all();
if($this->postValidator->passes($input)) {
// Validation passed, lets send off the data to the service
$post = $this->postService->create($input);
if($post) {
return 'A post was successfully created!';
} else {
return 'Uh oh, looks like there was an issue creating a post.';
}
} else {
// Validation failed, return the errors
return $this->postValidator->errors();
}
}
}
Now with this pattern, you have a nice separation of all your processes, and a clear indication of what each of them do.
For a repository example, Google 'Laravel Repository Pattern'. There are tons of articles about this.
Actually - in Laravel 5 that is not the best way to do it. Business logic should not be in models. The only thing that models should do is retrieve and store data from your database.
You are better off using the CommandBus or ServiceProviders to handle application logic and business rules. There are many articles on the web about these, but personally I prefer laracasts.com as the best learning resource.
I'm trying to overwrite some methods in models, and I'm on a mission to avoid overwrites and rewrites of models for maximum compatibility with other modules.
I figured the best way would be to simply decorate models after they are loaded from Magento, however as far as I can tell because of the way the observer pattern in Magento is written it's impossible to accomplish this. ( As Magento always returns the reference to $this ), and the lack of interfaces might also cause trouble later down the road? See this partial of Mage/Core/Model/Abstract.php
/**
* Processing object after load data
*
* #return Mage_Core_Model_Abstract
*/
protected function _afterLoad()
{
Mage::dispatchEvent('model_load_after', array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
return $this;
}
My question boils down to the title, is there a decent way of accomplishing this?, or am I simply stuck with rewrites :(?
The path I would like to take is;
On event [model]_load_after
return new Decorator($event->getObject())
Where the decorator class in my case would be something like;
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model); // sets $this->model on parent class, see below
}
// overwrite the getIncrementId method
public function getIncrementId()
{
return '12345';
}
// partial of parent class
public function __call($method, array $args)
{
return call_user_func_array(array($this->model, $method), $args);
}
And just some pseudo-code for extra clarification;
$model = Mage::getModel('sales/order_invoice')->load(1);
echo get_class($model);
Namespace_Decorator **INSTEAD OF** Mage_Sales_Model_...
echo $model->getIncrementId();
'12345' **INSTEAD OF** '1000001' ( or whatever the format might be )
Thanks for your time reading / commenting, I really hope there actually is a way to accomplish this in a clean fashion without making use of code overrides or rewrites of models.
Edit: extra clarification
Basically what I would like is to return an instance of the Decorator in a few cases, the sales_invoice being one of them and customer the other. So when any load() call is made on these models, it will always return the instance of the Decorator instead of the Model. Only method calls that the decorator overrides would be returned, and any other method calls would "proxied" through __call to the decorated object.
I'm not sure if I got your question right but here goes.
I think you can use the event [model]_load_after and simply do this:
$object = $event->getObject();
$object->setIncrementId('12345');
Or if you want to use a decorator class make it look like this:
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model);
$model->setIncrementId($this->getIncrementId());
}
public function getIncrementId()
{
return '12345';
}
I know that this is not exactly a decorator pattern but it should work.
I know that when adding a new method to the 'decorator' class you need to add it to attach data to the main model.
This is just my idea. I haven't got an other.
[EDIT]
You can try to rewrite the load method on the object to make it return what you need. But I wouldn't go that way. You can end up screwing a lot of other things.
I don't think there is an other way to do it because load always returns the current object no mater what you do in the events dispatched in the method. see Mage_Core_Model_Abstract::load()
public function load($id, $field=null)
{
$this->_beforeLoad($id, $field);
$this->_getResource()->load($this, $id, $field);
$this->_afterLoad();
$this->setOrigData();
$this->_hasDataChanges = false;
return $this;
}
By making it return new Decorator($this), you might achieve what you need, but just make sure that when calling $model->doSomething() and doSomething() is not a method in your decorator you still end up calling the original method on the model.