I'm trying to add some validation rules for my form by using callbacks for a few choice fields.
This callback I saw in the documentation should be in the entity class for that form. But in my case I'm having all the time this problem:
"ContextErrorException: Notice: Array to string conversion in /Applications/MAMP/htdocs/bbc/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php line 462"
My code in my entity looks like this:
namespace Cgboard\AppBundle\Forms;
use Cgboard\AppBundle\Classes\Iplayer;
class SearchEntity
{
public $category;
public $coming_soon_within;
public $local_radio;
public $masterbrand;
public $max_tleos;
public $media_set;
public $page;
public $perPage;
public $q;
public $search_availibity;
public $service_type;
public $signed;
public static function getCategories() // all this getters are firing the error, why?
{
return ['undef',
'arts',
'cbcc',
'comedy'];
}
public static function getLocalRadio()
{
......
I want to use these callbacks from my validation.yml file, that looks like this one:
Cgboard\AppBundle\Forms\SearchEntity:
properties:
category:
- Choice: { callback: getCategories }
coming_soon_within:
- Range:
min: 1
max: 168
local_radio:
- Choice: { callback: getLocalRadio }
masterbrand:
- Choice: { callback: getMasterbrand }
....
Even if I delete the whole content from validation.yml I still have the error. So I think the problem is just with the entity form class. Any idea about how to work around this problem?
Because I think can be handy for someone else. The problem was that I was using getters (getLocalRadio for instance) that were creating conflicts for the internal getters used by SYmfony. I just changed the name of these getter and everything worked fine
Related
I want to build a base controller that I can put some reusable methods so I do not have to put a bunch of repeat code in all my controllers. So I built a BaseController.cs
public class BaseController : Controller
{
public IHttpClientFactory _clientFactory;
public BaseController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
}
Then in one of my contollers I do public class TokenController : BaseController. But then it wants me to add the following but then it gives me errors
public TokenController(IHttpClientFactory clientFactory)
{
// I guess something goes here
}
But then VS Code tells me
There is no argument given that corresponds to the required formal parameter 'clientFactory' of 'BaseController.BaseController(IHttpClientFactory)' (CS7036)
What am I missing here? I been in JS world to long :)
When inheriting classes without default constructors you have to pass parameters to them using the following syntax:
public TokenController(IHttpClientFactory clientFactory) : base (clientFactory)
{
/* other initializations */
}
So add the following expression: : base (clientFactory)
See more information here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/using-constructors
I have an entity BlogPost with a status property. This status property depends on an external API call which is handled via the doctrine postLoad event. All other properties are stored in the local database.
public function postLoad(BlogPost $post)
{
$this->postHandler->calculateStatus($post);
}
The problem is, in some cases i don't want to calculate the status at all. For example if i want to get only the description of all blogposts.
With the code above, all blog entities being loaded will trigger the postLoad event even if i just want to have values from a local database. That is very expensive and not acceptable.
So for example in my repository class i want to get all BlogPosts having a website without invoking the postLoad event.
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getResult();
}
Is there a way to say "Yes, load the BlogPost collection, but do not fire event!" ???
Any other approaches? Custom event?
Thanks
Why don't just move this logic outside the post entity and event listener? If you know when you need to calculate the status you can do it explicitly.
For example
$post = $this->entityManager->find(BlogPost::class, $postId);
$status = $this->postHandler->calculateStatus($post);
The other approach I could suggest is not good but works. You could use lazy calculation and instead of calling $this->postHandler->calculateStatus($this) in postLoad event listener you could inject postHandler service into entity and perform the calculation in the moment you actually need it.
For example if you need calculation when calling $blogPost->getStatus() method, you could do it this way:
interface PostHandlerAwareInterface
{
public function setPostHandler(PostHandlerInterface $postHandler): void;
}
class EntityServiceInjectorEventSubscriber implements EventSubscriber
{
/** #var PostHandlerInterface */
private $postHandler;
public function postLoad($entity): void
{
$this->injectServices($entity);
}
public function postPersist($entity): void
{
$this->injectServices($entity);
}
private function injectServices($entity): void
{
if ($entity instanceof PostHandlerAwareInterface) {
$entity->setPostHandler($this->postHandler);
}
}
}
class BlogPost extends PostHandlerAwareInterface
{
/** #var PostHandlerInterface */
private $postHandler;
private $status;
public function setPostHandler(PostHandlerInterface $postHandler): void
{
$this->postHandler = $postHandler;
}
public function getStatus()
{
if (null === $this->status) {
$this->postHandler->calculateStatus($this);
}
return $this->status;
}
}
If you don't like this idea you still could manage it via (BUT I STRONGLY DO NOT RECOMMEND DO THIS DIRTY HACK) setting the flag to your entity event listener.
You could inject your entity event listener to the code and set flag before fetching data:
class BlogPostCalculateStatusListener
{
/** #var bool */
private $calculationEnabled = true;
public function suspendCalculation(): void
{
$this->calculationEnabled = false;
}
public function resumeCalculation(): void
{
$this->calculationEnabled = true;
}
public function postLoad(BlogPost $post): void
{
if ($this->calculationEnabled) {
$this->postHandler->calculateStatus($post);
}
}
}
$this->calculateStatusListener->suspendCalculation();
$blogPosts = $blogPostRepository->findBlogPosts();
$this->calculateStatusListener->resumeCalculation();
Hope this helps.
PS. If you want to get only the descriptions of all blog posts you can do this way:
class BlogPostRepository
{
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp.description')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getArrayResult();
}
}
getArrayResult does not invoke lifecycle callbacks.
Since i haven't found a real similar use case on the internet, i'll go for the following solution which seems the easiest and most acceptable cleanest to me. Maybe someone else could find this useful.
Implement a TransientLoadable Interface
interface TransientLoadable
{
public function isLoaded() : bool;
public function setLoaded(bool $loaded) : TransientLoadable;
public function setTransientLoadingFunction(\Closure $loadingFunction) :
TransientLoadable;
}
Implement the entity
class BlogPost implements TransientLoadable
{
...
}
Setup Loading function on postLoad Event
public function postLoad(BlogPost $post)
{
$func = function() use ($postHandler, $post)
{
//Since there may be another fields being loaded from the same API, catch them also since data is anyway in the same request
$postHandler->setAllDataFromAPI($post)
//Set the loading state to true to prevent calling the API again for the next property which may also be transient
$post->setLoaded(true);
}
$post->setTransientLoadingFunction($func)
}
Use the built-in lazy loading mechanism to get the property from the API only when it's needed
class BlogPost implements TransientLoadable
{
private function getStatus() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->status;
}
private function getVisitorCount() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->visitorCount;
}
}
So what's happening? Let's imagine we want to get the status and the visitor count, both are loaded via a single external API call.
If some api-dependent property of the entity is needed, all other properties gets loaded too (since we don't want to have for each property another call). This in ensured through the loaded function of the TransientLoadable interface. All data gets loaded by the setAllDataFromAPI function which is injected as a closure function.
I think that is not the cleanest solution. The loading stuf should be done by an extra layer on top of the entity class. Since sonata admin does not deal with such an layer, i think that this solution is cleaner than writing the loading mechanism directly to the entity class.
I am open to another suggestions or feedback
Thanks
I would like to find a a way to validate a rest method based on all parameters outside the Controller.
First Question: Is there already a way to do it?
Second Question: If not - how can I hook the validation into spring mvc binding prozess.
A way how it could look like. It would be nice to mark the method with a new #MethodValidation Annotation:
#Validate
#MethodValidation(MyValidator.class)
public Response doSomthing(String param1, Integer param2, Something param3){}
Annotation
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MethodValidation{
Class<? extends MethodValidator<?, ?>>[] value();
}
Implement a Validator
public class MyValidator implements MethodValidator{
public void validate(Object[] params, Errors errors){
String param1 = (String ) params[0];
Integer param2 = (Integer) params[1];
Something param3 = (Something)params[3];
// .... do some validations
if(error)
errors.reject("Some.error.done");
}
}
what kind of parameters exactly? a lot of spring stuff is actually available in ThreadLocals, if you dare to dig into it.
you CAN inject stuff into the binding process:
#ControllerAdvice
public class FooControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
Date.class,
new CustomFooEditor()
);
}
}
and the actual editor:
public class CustomFooEditor extends PropertyEditorSupport {
}
but this doesn't give you that much of an edge over regular validation.
or you can use spring aop triggered by an annotation, then annotate your methods, with the config:
#EnableAspectJAutoProxy(proxyTargetClass=true)
an aspect:
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(public * * (..))")
private void anyPublicMethod() {}
#Around("anyPublicMethod() && #annotation(foo)")
public Object all(
ProceedingJoinPoint proceedingJoinPoint,
Foo ann) throws Throwable {
}
[...]
}
an annotation:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Foo {
}
public String value();
and then annotating your method:
#RequestMapping...
#Foo(value="foo.bar.ValidatorClassname")
public Response x() {
}
... so you see, there's a lot of ways you can go. i'd really like to know what keeps you from using standard validation?
.rm
thanx for the answer.
I hope I am right: The standard validation outside the controller just allows me to to validate each method parameter separately.
I actually get into problems when the validation depends on 2 or more method parameter. This could be in following situation: Some thing is a part of an Object hierarchy:
public class Parent{
private Integer id;
private List<Something> childs;
...
}
public class Something{
private Integer id;
private String name;
...
}
The Constrain: it is not allowed that a Parent has 2 somethings in the list with the same name. For saving a new some thing I am calling the method.
#RequestMapping(
value = "/chargingstation/{parentId}",
method = RequestMethod.Post)
public Response doSomthing(
#PathVariable("parentId") Integer parentId,
Something param3)
Add the parentId to the Something-ModelOject was not an option.
So is there a way to handle this situation with the standard validation?
I have installed a custom validator which checks if the generated slug is unique.
Now I am testing the validator and it seems that the validator works (form doesn't get persisted) but I don't get an error message...
class Unique extends Constraint
{
public $message = 'The value of "%property%" already exists.';
public $property;
public function getDefaultOption()
{
return 'property';
}
public function getRequiredOptions()
{
return array('property');
}
public function validatedBy()
{
return 'loc_article_validator_unique_alias';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
The form errors are rendered through {{ form_rest(form) }} in twig
So I found the issue.
The Issue was that that Custom Constraints errors can't get rendered over foreach. They have to get rendered through
{{ form_errors(form) }}
My remaining questions are now:
1.) How can I render the Custom Constrain Errors like all other errors?
2.) Why does the Custom class extending Constrain requires an alias of the CustomValidator service?
By these lines
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
You make the constraint a class constraint wich means the errors will show up on top of the whole form and not next to the field.
Try defining it as property constraint
public function getTargets()
{
return self::PROPERTY_CONSTRAINT;
}
If this does not help, please post your validation definition and the form builder code that generates the form.
I am trying to create WebDriver UI tests framework using Page Object pattern, using the following URL as a reference: http://www.peternewhook.com/2010/09/automated-testing-pageobjects-webdriver/
As per example I have created 3 classes (see below). The problem is with the line return PageFactory.InitElements(_driver, page); in the Search method of the SearchPage class.
When I try to build I get the following error:
The type 'OpenQA.Selenium.ISearchContext' is defined in an assembly that is not referenced. You must add a reference to assembly 'WebDriver
Fair enough, as I am referencing WebDriver.Common.dll, so I tried removing it and added WebDriver.dll to my References and all of a sudden I get the following when I build:
Cannot implicitly convert type 'void' to 'ConsoleApplication1.ResultsPage'
and it fails on the same line; when I hover over it, it says:
Cannot convert expression type 'void' to 'ConsoleApplication1.ResultsPage'.
I also tried referencing both assemblies and thought I could use different usings but it is a no-go, didn't work.
Why can't PageFactory.InitElements be returned when using WebDriver.dll?
Is there a way around it, or can I achieve the same result by changing the architecture slightly?
Your help is much appreciated. Thanks.
using OpenQA.Selenium;
namespace ConsoleApplication1
{
public class Page
{
public IWebDriver _driver;
public Page(IWebDriver driver)
{
this._driver = driver;
}
}
}
using OpenQA.Selenium;
namespace ConsoleApplication1
{
public class ResultsPage : Page
{
public ResultsPage(IWebDriver driver)
: base(driver)
{
}
private IWebElement count;
public string GetPagesReturned()
{
return count.Text;
}
}
}
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
namespace ConsoleApplication1
{
public class SearchPage : Page
{
public SearchPage(IWebDriver driver) : base(driver)
{
}
private IWebElement q;
private IWebElement go;
public ResultsPage Search(string searchStatement)
{
q.SendKeys(searchStatement);
go.Click();
ResultsPage page = new ResultsPage(_driver);
return PageFactory.InitElements(_driver, page);
}
}
}
The problem is that PageFactory.InitElements() returns void. Rather, it modifies the page you've passed in. Your code should look something like this:
public ResultsPage Search(string searchStatement)
{
q.SendKeys(searchStatement);
go.Click();
ResultsPage page = new ResultsPage(_driver);
PageFactory.InitElements(_driver, page);
return page;
}
Looks like C# PageFactory does not init private superclass elements (via PageFactory.InitElements) per this:
http://code.google.com/p/selenium/issues/detail?id=1189#makechanges
Using PageFactory.InitElements(_driver, this); on the constructor of your base page class .
public class Page
{
public IWebDriver _driver;
public Page(IWebDriver driver)
{
this._driver = driver;
PageFactory.InitElements(_driver, this);
}
}
I agree with JimEvans. But instead of having PAgeFactory code in seperate place, have that in constructor of Results page.
Your results page class should be something like this.
public class result
{
public result(driver,this)
{
}
}