Laravel validate request as a whole - php

I would like to validate given lecture. There are some validation rules but I want to extend it somehow with validation function which takes 3 attributes as arguments and check others lectures in database and return valid if none of lectures overlap. Lectures have start, end DateTime attributes.
Validation function is
private function noOtherLecturesOnGivenTime($start, $end, $hall)
{
if(!$start instanceof Carbon) {
$start = new Carbon($start);
}
if(!$end instanceof Carbon) {
$end = new Carbon($end);
}
if(is_null($hall)) {
return true; // if there is no hall defined, all times are acceptable
}
if(!$hall instanceof Model) {
/** #var Hall $hall */
$hall = Hall::find($hall);
}
$overlappingLectures = $hall->lectures()->get()->filter(function ($lecture) use ($start, $end) {
// (Ts < e) and (Te > s)
return $start->lt($lecture->end) and $end->gt($lecture->start);
});
return $overlappingLectures->count() === 0;
}
I don't know how and where to put this function so Laravel's validator throws exception and I could specify error message with i18n.
Thanks

Check out the documentation about custom validation rules.
From creating the rule to specifiying error messages, there is a full exemple there :
Laravel custom validation rules documentation
For your case, you can fetch the parameters from the request in the validation method.
I think the validation rule will still occur even if other parameters aren't set, don't forget to check if they are in the request

Related

Zend 2: Route constraints log specific error

I try to log if user type wrong url parameter for a route with
'constraints' => array('personalnumber' => '[0-9]*')
$error = $e->getError();
if ($error == Application::ERROR_ROUTER_NO_MATCH) {
$url = $e->getRequest()->getUriString();
$sm->get('Zend\Log\RouteLogger')->warn('Url could not match to routing: ' . $url);
}
Can I get a specific error like: Value for Parameter "id" must type integer?
That won't be so easy. You would have to build your own functionality to find out the exact details on why the route didn't match.
Route matching is checked using the RouteInterface::match method from the corresponding class. For example for segment routes this method can be found in the Zend\Router\Http\Segment class on line 359-404.
If there is no match, the class returns null/void. Details on why the route didn't match is not part of the response, so you would have to do such in depth analysis yourself and write your own custom error response.
Such a solution could be to do manually validate the person number (for example by isolating it from the request url) when the dispatch error event is triggered and return your own custom response before the default 404 response.
<?php
namespace Application;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Router\Http\RouteMatch;
class Module{
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'validatePersonNumber'), 1000);
}
public function validatePersonNumber(MvcEvent $event)
{
if ($event->getError() !== Application::ERROR_ROUTER_NO_MATCH) {
// Not a 404 error
return;
}
$request = $event->getRequest();
$controller = $event->getController();
if($controller !== 'Application\Expected\ControllerName'){
// not a controller for person number route
return;
}
$url = $request->getRequestUri();
$personNumber = ''; //...manually isolate the person number from the request...
/** #var Response $response */
$response = $event->getResponse();
$response->setStatusCode(404);
$viewModel = $event->getViewModel();
$viewModel->setTemplate('error/404');
$event->setViewModel($viewModel);
$event->stopPropagation(true);
if (strlen($personNumber) !== 12) {
$viewModel->setVariable('message', 'A person number should have 12 characters');
}
if(...additional check...){
$viewModel->setVariable('message', 'Some other message');
}
}
}
To make things prettier you could consider moving all this into a Listener class (instead of polluting your module.php file) and you could also consider the 404 code here. Most likely there is a more suitable status code for such validation response.
Note: This is not a completely finished example, it needs more work!

DDD - Entity creation business rule validation

I have a business rule such as this :
If a JobSeeker wants to apply to a Vacancy, make sure that the Resume used in application is completed and that JobSeeker hadn't
applied to that Vacancy already. If the condition is satisfied, then
the application will be written in a JobApplicatiion.
This is what I came up with :
JobSeeker.php
class JobSeeker {
private $applications;
private $resume;
/** Other irrelevant props **/
public function apply(Vacancy $vacancy, Resume $resume) {
// Business rule #1
if(!$resume->isCompleted()) throw new \Exception('Resume '.$resume->getTitle().' is incomplete.');
// Business rule #2
$alreadyApplied = array_filter($this->applications->toArray(), function(JobApplication $application) use($vacancy) {
return $application->getVacancy() === $vacancy;
});
if($alreadyApplied) throw new \Exception('Vacancy '.$vacancy->getTitle().' is already applied');
// If both rules passed, then create a JobApplication
$application = new JobApplication($this, $vacancy, $resume);
$this->applications->add($application);
return $application;
}
}
JobApplication.php
class JobApplication {
private $applicant;
private $vacancy;
private $resume;
public function __construct(JobSeeker $applicant, Vacancy $vacancy, Resume $resume) {
$this->applicant = $applicant;
$this->vacancy = $vacancy;
$this->resume = $resume;
}
}
If I was to expect that everyone would just use
$jobApplication = $jobSeeker->apply($vacancy, $jobSeeker->getResume());
Then there's no problem.
The problem arise when someone do this
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
The second example will bypass the business rule validation.
It did occurred to me to separate the rule checking to a different method :
JobSeeker.php
class JobSeeker {
public function canApply() {
// Here goes those 2 business rules mentioned
}
public function apply(Vacancy $vacancy, Resume $resume) {
if($this->canApply($vacancy, $resume)) {
return new JobApplication($this, $vacancy, $resume);
}
}
}
JobApplication.php
class JobApplication {
public function __construct(JobSeeker $jobSeeker, Vacancy $vacancy, Resume $resume) {
if($jobSeeker->canApply($vacancy, $resume)) {
// Same as before
}
}
}
While the second approach guarantees the business rule constraint, it's very redundant and still does not provides the expected result.
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
I need an insight in this.
Thanks !
Depending how you do it you have 2 aggregate roots as I see it
JobSeeker
Vacancy
Resume is an like a profile for an user
Well DDD likes to uses services, for almost everything.
So we have the JobSeekerApplicaitonService this services will be used for the external world.
On the JobSeekerApplicaitonService I would add the method apply
public function apply(JobSeeker $jobSeeker, Vacancy $vacancy);
First we check if the bussiness rules are met.
ie.
$jobSeeker->getResume()->isCompleted();
This check throws an error if it is not completed.
Next we make another function at the JobSeekerApplicaitonService which checks if an JobSeeker already has applied, can also be used for the view to let the user already see he has applied for example.
public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy);
But this method can now be used in our apply function
$this->hasApplied($jobSeeker, $vacancy);
Again throw an exception when already applied.
You can now savely reutrn the new JobApplication. Although I would say the JobSeekerApplicaitonService repository and create it there, so it is saved in the db because that is what an application service is, a delegator.
Code
class JobSeekerApplicaitonService {
public function apply(JobSeeker $jobSeeker, Vacancy $vacancy) {
if ($jobSeeker->getResume()->isCompleted()) {
// throw exception
} elseif ($this->hasApplied($jobSeeker, $vacancy)) {
// throw exception
}
// save logic or something else you want
}
public function hasApplied(JobSeeker $jobSeeker, Vacancy $vacancy) {
// your check, I would now use the JobApplicationRepository
return false;
}
}
Your first examples of JobSeeker & JobApplication are correct. The JobSeeker.apply method is acting as the factory for JobApplications:
job_application = job_seeker.apply(vacancy, resume)
Looks good.
The following statement, however, doesn't make much sense:
$jobApplication = new JobApplication($jobSeeker, $vacancy, $resume);
Considering the real world, have you ever seen a JobApplication randomly burst into existence out of thin air and land on your desk? I haven't :) In most cases, one entity is created from another:
employee = company.hire(person)
invoice = employee.create_invoice(customer, invoice_terms)
etc...
If you see an entity being 'new-ed' in a command handler, it should raise an eyebrow.

Zend Action Controller - refactoring strategy

I've built a first-run web service on Zend Framework (1.10), and now I'm looking at ways to refactor some of the logic in my Action Controllers so that it will be easier for me and the rest of my team to expand and maintain the service.
I can see where there are opportunities for refactoring, but I'm not clear on the best strategies on how. The best documentation and tutorials on controllers only talk about small scale applications, and don't really discuss how to abstract the more repetitive code that creeps into larger scales.
The basic structure for our action controllers are:
Extract XML message from the request body - This includes validation against an action-specific relaxNG schema
Prepare the XML response
Validate the data in the request message (invalid data throws an exception - a message is added to the response which is sent immediately)
Perform database action (select/insert/update/delete)
Return success or failure of action, with required information
A simple example is this action which returns a list of vendors based on a flexible set of criteria:
class Api_VendorController extends Lib_Controller_Action
{
public function getDetailsAction()
{
try {
$request = new Lib_XML_Request('1.0');
$request->load($this->getRequest()->getRawBody(), dirname(__FILE__) . '/../resources/xml/relaxng/vendor/getDetails.xml');
} catch (Lib_XML_Request_Exception $e) {
// Log exception, if logger available
if ($log = $this->getLog()) {
$log->warn('API/Vendor/getDetails: Error validating incoming request message', $e);
}
// Elevate as general error
throw new Zend_Controller_Action_Exception($e->getMessage(), 400);
}
$response = new Lib_XML_Response('API/vendor/getDetails');
try {
$criteria = array();
$fields = $request->getElementsByTagName('field');
for ($i = 0; $i < $fields->length; $i++) {
$name = trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue);
if (!isset($criteria[$name])) {
$criteria[$name] = array();
}
$criteria[$name][] = trim($fields->item($i)->childNodes->item(0)->nodeValue);
}
$vendors = $this->_mappers['vendor']->find($criteria);
if (count($vendors) < 1) {
throw new Api_VendorController_Exception('Could not find any vendors matching your criteria');
}
$response->append('success');
foreach ($vendors as $vendor) {
$v = $vendor->toArray();
$response->append('vendor', $v);
}
} catch (Api_VendorController_Exception $e) {
// Send failure message
$error = $response->append('error');
$response->appendChild($error, 'message', $e->getMessage());
// Log exception, if logger available
if ($log = $this->getLog()) {
$log->warn('API/Account/GetDetails: ' . $e->getMessage(), $e);
}
}
echo $response->save();
}
}
So - knowing where the commonalities are in my controllers, what's the best strategy for refactoring while keeping it Zend-like and also testable with PHPUnit?
I did think about abstracting more of the controller logic into a parent class (Lib_Controller_Action), but this makes unit testing more complicated in a way that seems to me to be wrong.
Two ideas (just creating an answer from the comments above):
Push commonality down into service/repository classes? Such classes would be testable, would be usable across controllers, and could make controller code more compact.
Gather commonality into action helpers.
Since you have to do this step every time a request is made, you could store that receive, parse and validate the received request in a Zend_Controller_Plugin which would be run every PreDispatch of all controllers. (Only do-able if your XML request are standardized) (If you use XMLRPC, REST or some standard way to build requests to your service, you could look forward those modules built in ZF)
The validation of the data (action specific) could be done in a controller method (which would then be called by the action(s) needing it) (if your parametters are specific to one or many actions of that controller) or you could do it with the patterns Factory and Builder in the case that you have a lot of shared params between controllers/actions
// call to the factory
$filteredRequest = My_Param_Factory::factory($controller, $action, $paramsArray) // call the right builder based on the action/controller combination
// the actual Factory
class My_Param_Factory
{
public static function factory($controller, $action, $params)
{
$builderClass = "My_Param_Builder_" . ucfirst($controller) . '_' . ucfirst($action);
$builder = new $builderClass($params);
return $builder->build();
}
}
Your builder would then call specific parameters validating classes based on that specific builder needs (which would improve re-usability)
In your controller, if every required params are valid, you pass the processing to the right method of the right model
$userModel->getUserInfo($id) // for example
Which would remove all of the dataprocessing operations from the controllers which would only have to check if input is ok and then dispatch accordingly.
Store the results (error or succes) in a variable that will be sent to the view
Process the data (format and escape (replace < with < if they are to be included in the response for example)), send to a view helper to build XML then print (echo) the data in the view (which will be the response for your user).
public function getDetailsAction()
{
if ($areRequestParamsValid === true) {
// process data
} else {
// Build specific error message (or call action helper or controller method if error is general)
}
$this->view->data = $result
}

Is this correct use of Exception handling in PHP / Symfony2

I'm creating a service to fetch some user data
class ExampleService{
// ...
public function getValueByUser($user)
{
$result = $this->em->getRepository('SomeBundle:SomeEntity')->getValue($user);
if (!$result instanceof Entity\SomeEntity) {
throw new Exception\InvalidArgumentException("no value found for that user");
}
return $result;
}
}
Then in my controller I have
// ...
$ExampleService = $this->get('example_serivce');
$value = $ExampleService->getValueByUser($user);
Should I be using an exception here to indicate that no value was found for that user in the database?
If I should, how do I handle what is returned from $ExampleService->getValueByUser($user) in the controller - let's say I just want to set a default value if nothing is found (or exception returned)
Here is how I do it. Let's use a user service and a controller as an example. It's not an exceptional condition in the service layer — it just returns the result without checking it:
class UserService
{
public function find($id)
{
return $this->em->getRepository('UserBundle:User')->find($id);
}
}
But in the controllers layer I throw an exception if the requested user not found:
class UserController
{
public function viewAction($id)
{
$user = $this->get('user.service')->find($id);
if (!$user) {
throw $this->createNotFoundException(
$this->get('translator')->trans('user.not_found')
);
}
// ...
}
}
Where you want to handle the exception is kind of up to you, however I would handle it in the controller (and throw it in the model). I usually try to call a different template if there is an error so as to avoid a bunch of conditionals, but sometimes you just have to put extra logic in your template instead.
Also, you have to ask yourself if this is really an exceptional condition - it might be easier to return null and handle that return value in your controller. I can't really tell from the data objects (value, service, and user) whether this is something that will happen all the time or not.

custom validator in symfony

I would like create custom validator for Symfony 1.4, for example check length name. I know that it exist, but i would like own.
I create /myapp/lib/validator/sfValidatorName.class.php
Must be there:
class sfValidatorName extends sfValidatorBase
{
protected function configure($options = array(),
$messages = array()) {
$this->addMessage('invalid', 'Invalid name!');
}
protected function doClean($value) {
}
}
and how can i add for this my function, for example:
if (count($letters) < 3) {
return 'too small';
} else if (count($letters) > 43) {
return 'too long';
}
Open /lib/validator/sfValidatorString.class.php
Model your validator after that one.
Since your example is exactly what sfValidatorString does, why don't you go look at it's source? Basically you just throw a validation error with the relevant error code (eg. invalid, min_length, max_length, ...).
By default any validator has the errors 'invalid' and 'required', but you can add your own with addMessage().
For this specific example, a much smarter choice is to configure or extend sfValidatorString.

Categories