When attempting to use Joshua Bloch's "Builder Pattern" [Item 2 in Effective Java Second Edition] with reflection [object = constructors[index].newInstance(constructorParameterValues);] the following exception occurs:
java.lang.IllegalAccessException: Class info.soaj.core.util.SjUtilReflection can not access a member of class info.soaj.core.attribute.SjAttributesForThrowable with modifiers "private"
Note: This has been resolved. The accessible (private) constructor was being discarded and a non-accessible (override = false) was being attempted. Bottom Line: Programmer Error
An example Builder Class follows:
package info.soaj.core.attribute;
import info.soaj.core.attribute.internal.SjAttributesForStronglyTypedWrappers;
import info.soaj.core.internal.string.SjPopulatedClassName;
import info.soaj.core.internal.string.SjPopulatedMethodName;
import info.soaj.core.util.internal.SjUtilThrowable;
import java.io.Serializable;
/**
* <p>
* The "Builder" pattern as documented by Joshua Bloch ("Effective Java" -
* Second Edition) is utilized to handle the variable number of required and
* optional parameters.
* </p>
*
* <p style="font-family:Verdana; font-size:10px; font-style:italic"> Copyright
* (c) 2006 - 2008 by Global Technology Consulting Group, Inc. at <a
* href="http://gtcGroup.com">gtcGroup.com </a>. </p>
*
* #author MarvinToll#gtcGroup.com
* #since v. 1.0
*/
public class SjAttributesExample implements Serializable {
/** UID */
private static final long serialVersionUID = 1L;
/** The name of class throwing the exception. */
protected final SjPopulatedClassName classname;
/** The name of method throwing the exception. */
protected final SjPopulatedMethodName methodname;
/**
* Suppresses logging; default is <code>false</code>.
*/
protected final boolean suppressLoggingOnly;
/**
* Constructor - private
*
* #param builderThrowable
*/
private SjAttributesExample(final BuilderThrowable builderThrowable) {
this.classname = builderThrowable.classname;
this.methodname = builderThrowable.methodname;
this.suppressLoggingOnly = builderThrowable.suppressLoggingOnly;
}
/**
* This static member immutable class is used to implement the builder
* pattern.
*
* #author MarvinToll#gtcGroup.com
* #since v. 1.0
*/
public static class BuilderThrowable {
/** Class name. */
private static final String CLASS_NAME = BuilderThrowable.class
.getName();
// Required attributes.
/** The name of class throwing the exception. */
protected final SjPopulatedClassName classname;
/** The name of method throwing the exception. */
protected final SjPopulatedMethodName methodname;
// Optional attributes.
/** Prevents action from occurring. Default is false. */
protected boolean suppressLoggingOnly = false;
/**
* Constructor
*
* #param classname
* #param methodname
*/
public BuilderThrowable(final String classname, final String methodname) {
super();
final String Method_Name = "BuilderThrowable";
// What happens when handling an exception throws an exception?
try {
this.classname = new SjPopulatedClassName(classname,
new SjAttributesForStronglyTypedWrappers(CLASS_NAME,
Method_Name));
this.methodname = new SjPopulatedMethodName(methodname,
new SjAttributesForStronglyTypedWrappers(CLASS_NAME,
Method_Name));
} catch (final RuntimeException e) {
// Log the contextual details.
SjUtilThrowable.logExceptionOccuredWhileThrowingException(
CLASS_NAME, Method_Name, e);
throw e;
}
return;
}
/**
* This method sets a flag to suppress logging.
*
* #param isLoggingSuppressed
* #return BuilderThrowable
*/
public BuilderThrowable suppressLoggingOnly(
final boolean isLoggingSuppressed) {
this.suppressLoggingOnly = isLoggingSuppressed;
return this;
}
/**
* This method is used for instantiating this class.
*
* #return SjAttributesForThrowable
*/
#SuppressWarnings("synthetic-access")
public SjAttributesExample build() {
return new SjAttributesExample(this);
}
}
/**
* This method returns an attribute.
*
* #return String - Returns the <code>classname</code> attribute.
*/
public String getClassname() {
return this.classname.getString();
}
/**
* This method returns an attribute.
*
* #return String - Returns the <code>methodname</code> attribute.
*/
public String getMethodname() {
return this.methodname.getString();
}
/**
* This method returns an attribute.
*
* #return boolean - Returns the <code>suppressLoggingOnly</code> attribute.
*/
public boolean isLoggingSuppressed() {
return this.suppressLoggingOnly;
}
}
Note: This has been resolved. The accessible (private) constructor was being discarded and a non-accessible (override = false) was being attempted. Bottom Line: Programmer Error
Related
I am getting Mass Assignment: Insecure Binder Configuration in fortify analysis.
Here is the AuthorisationController.class
#Controller
public class AuthorisationController {
#RequestMapping(value = "/authorisation_request", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<AuthorisationRequest> createAuthorisation(HttpServletRequest request,
#RequestBody AuthorisationRequestInfo createAuthorisation) {
//processing code
}
}
Here is the AuthorisationRequestInfo.class on which the http request params will be mapped.
import com.fasterxml.jackson.annotation.JsonProperty;
public class OrderAuthorisationRequestInfo {
private String hashValue;
private String expiryDateTime;
private Integer initiatingRolePlayerId;
#JsonProperty("feedbackURI")
private String feedbackUri;
/**
* Gets the hash value.
*
* #return the hash value
*/
public String getHashValue() {
return hashValue;
}
/**
* Sets the hash value.
*
* #param hashValue the new hash value
*/
public void setHashValue(String hashValue) {
this.hashValue = hashValue;
}
/**
* Gets the expiry date time.
*
* #return the expiry date time
*/
public String getExpiryDateTime() {
return expiryDateTime;
}
/**
* Sets the expiry date time.
*
* #param expiryDateTime the new expiry date time
*/
public void setExpiryDateTime(String expiryDateTime) {
this.expiryDateTime = expiryDateTime;
}
/**
* Gets the initiating role player id.
*
* #return the initiating role player id
*/
public Integer getInitiatingRolePlayerId() {
return initiatingRolePlayerId;
}
/**
* Sets the initiating role player id.
*
* #param initiatingRolePlayerId the new initiating role player id
*/
public void setInitiatingRolePlayerId(Integer initiatingRolePlayerId) {
this.initiatingRolePlayerId = initiatingRolePlayerId;
}
/**
* Gets the feedback URI.
*
* #return the feedback URI
*/
public String getFeedbackUri() {
return feedbackUri;
}
/**
* Sets the feedback URI.
*
* #param feedbackUri the new feedback URI
*/
public void setFeedbackUri(String feedbackUri) {
this.feedbackUri = feedbackUri;
}
}
The interesting thing is that I only started getting this error after adding the #JsonProperty("feedbackURI") annotation on the feedbackUri column.
#InitBinder was not being used before and there was no fortify error and all the parameters in the request are mandatory.
All other APIs are fine and do not report any fortify issues. Only this api and another one in which the #JsonProperty was added have started showing this error.
Any help would be appreciated.
You can use #JsonIgnoreProperties in your case:
#JsonIgnoreProperties(ignoreUnknown = true)
public class OrderAuthorisationRequestInfo {
Today I started upgrading my application from symfony 3 to 4 (and so the related libraries) and I couldn't understand why I couldn't make certain routes work (I had a 401 error but they were supposed to be public routes so no security checks were made there), then I ended up finding this question: #Security annotation on controller class being overridden by action method
A recent comment on the question says that while in a previous version of symfony framework extra bundle, if you put the security annotation on both a class and a method inside that class, the method annotation would override the class annotation, now they stack instead.
This can also be seen (altough it's not very clear since you could already put a #Security annotation on both class and method) on the SensioFramework changelog https://github.com/sensiolabs/SensioFrameworkExtraBundle/blob/master/CHANGELOG.md for version 4.0
allowed using multiple #Security annotations (class and method)
This is a very big change for me since a lot of routes in my application relied on that behavior (which was similar to Symfony 1 where you could set a default security behavior and then a more specific one for each action)
/**
* #Route("my-route")
* #Security("is_granted('IS_AUTHENTICATED_FULLY')")
*/
class MyController extends Controller {
/**
* In Symfony 3.x this would've removed security checks for the route,
* now it checks both the class and the method Security expressions
* #Security(true)
*/
public function myAction(Request $request) {
}
}
Is there some way other than "don't upgrade to symfony 4" or "reorganize your code" (which is my "plan B") to have this behavior back? Something like a configuration option or similar...
I can't seem to find anything about this
I had forgot about this question but I did solve this issue by making my own annotation and EventListener.
Disclaimers:
1) My code uses the Dependency Injection bundle to inject and declare services using annotations
2) I'm sharing the code AS IS, with no warranty it'd work for you too, but i hope you can get the gist of it
I created 2 annotations (#IsGrantedDefault and #SecurityDefault) that work exactly like #IsGranted and #Security (they actually extend the original annotations) except they can be applied only to classes, then i created 2 event listeners, one for each annotation. The event listeners also extend the original event listeners, but they just check if a method already has a Security or IsGranted annotation, in which case they do nothing.
IsGrantedDefault.php
<?php
/*
* #author valepu
*/
namespace App\Project\AppBundle\Annotation;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* #Annotation
* #Target("CLASS")
*/
class IsGrantedDefault extends IsGranted {
public function getAliasName() {
return 'is_granted_default';
}
public function allowArray() {
return false;
}
}
SecurityDefault.php
<?php
/*
* #author valepu
*/
namespace App\Project\AppBundle\Annotation;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* #Annotation
* #Target("CLASS")
*/
class SecurityDefault extends Security {
public function getAliasName() {
return 'security_default';
}
public function allowArray() {
return false;
}
}
DefaultListenerTrait.php (Values::DEFAULT_LISTENER_PREFIX is just a string with an underscore "_")
<?php
/*
* #author valepu
*/
namespace App\Project\AppBundle\Event\Traits;
use App\Project\AppBundle\Utils\Values;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
Trait DefaultListenerTrait {
/**
* #var string
*/
private $defaultAttribute;
/**
* #var string
*/
private $otherAttributes = [];
/**
* #var string
*/
private $attribute;
/**
* Sets the class attributes
* #param [type] $defaultAnnotation
* #param string|null $modifyAttr
* #return void
*/
protected function setAttributes($defaultAnnotation, ?string $modifyAttr) {
//Get the attirbutes names
$this->attribute = $modifyAttr;
$this->defaultAttribute = Values::DEFAULT_LISTENER_PREFIX . $defaultAnnotation->getAliasName();
$annotations = [new IsGranted([]), new Security([])];
foreach($annotations as $annotation) {
$this->otherAttributes[] = Values::DEFAULT_LISTENER_PREFIX . $annotation->getAliasName();
}
}
/**
* Checks wheter or not the request needs to be handled by the annotation. If it does adds the correct attribute to the request
* #param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
* #return boolean
*/
protected function updateDefaultListener(FilterControllerArgumentsEvent $event) {
$request = $event->getRequest();
$default = $request->attributes->get($this->defaultAttribute);
//If there's already an "IsGranted" annotation or there's no "IsGrantedDefault" annotation
if (!$default) {
return false;
}
foreach($this->otherAttributes as $attr) {
if ($request->attributes->get($attr) || !$default) {
return false;
}
}
//We set IsGranted from the default and then call the parent eventListener so that it can handle the security
$request->attributes->set($this->attribute, [$default]);
return true;
}
/**
* Calls the event listener for the class if the request is handled by the class
* #param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
* #return void
*/
protected function callEventListener(FilterControllerArgumentsEvent $event) {
if($this->updateDefaultListener($event)) {
parent::onKernelControllerArguments($event);
}
}
}
IsGrantedDefaultListener.php
<?php
/*
* #author valepu
*/
namespace App\Project\AppBundle\Event;
use App\Project\AppBundle\Annotation\IsGrantedDefault;
use App\Project\AppBundle\Event\Traits\DefaultListenerTrait;
use App\Project\AppBundle\Utils\Values;
use RS\DiExtraBundle\Annotation as DI;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\IsGrantedListener;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
* #DI\Service(autowire = true)
* #DI\Tag("kernel.event_subscriber")
*/
class IsGrantedDefaultListener extends IsGrantedListener {
use DefaultListenerTrait;
/**
* #param \Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter $argumentNameConverter
* #param \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authChecker
* #DI\InjectParams({
* "argumentNameConverter" = #DI\Inject("framework_extra_bundle.argument_name_convertor"),
* "authChecker" = #DI\Inject("security.authorization_checker")
* })
*/
public function __construct(ArgumentNameConverter $argumentNameConverter, AuthorizationCheckerInterface $authChecker = null) {
parent::__construct($argumentNameConverter, $authChecker);
$modifyAttr = new IsGranted([]);
$this->setAttributes(new IsGrantedDefault([]), Values::DEFAULT_LISTENER_PREFIX . $modifyAttr->getAliasName());
}
/**
* #param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
* #return void
*/
public function onKernelControllerArguments(FilterControllerArgumentsEvent $event) {
$this->callEventListener($event);
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
}
}
SecurityDefaultListener.php
<?php
/*
* #author valepu
*/
namespace App\Project\AppBundle\Event;
use App\Project\AppBundle\Annotation\SecurityDefault;
use App\Project\AppBundle\Event\Traits\DefaultListenerTrait;
use App\Project\AppBundle\Utils\Values;
use Psr\Log\LoggerInterface;
use RS\DiExtraBundle\Annotation as DI;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* #DI\Service(autowire = true)
* #DI\Tag("kernel.event_subscriber")
*/
class SecurityDefaultListener extends SecurityListener {
use DefaultListenerTrait;
/**
* #param \Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter $argumentNameConverter
* #param \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authChecker
* #DI\InjectParams({
* "argumentNameConverter" = #DI\Inject("framework_extra_bundle.argument_name_convertor"),
* "language" = #DI\Inject("sensio_framework_extra.security.expression_language.default"),
* "trustResolver" = #DI\Inject("security.authentication.trust_resolver"),
* "roleHierarchy" = #DI\Inject("security.role_hierarchy"),
* "tokenStorage" = #DI\Inject("security.token_storage"),
* "authChecker" = #DI\Inject("security.authorization_checker"),
* "logger" = #DI\Inject("logger")
* })
*
*/
public function __construct(ArgumentNameConverter $argumentNameConverter, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null, LoggerInterface $logger = null) {
parent::__construct($argumentNameConverter, $language, $trustResolver, $roleHierarchy, $tokenStorage, $authChecker, $logger);
$modifyAttr = new Security([]);
$this->setAttributes(new SecurityDefault([]), Values::DEFAULT_LISTENER_PREFIX . $modifyAttr->getAliasName());
}
public function onKernelControllerArguments(FilterControllerArgumentsEvent $event) {
$this->callEventListener($event);
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
}
}
You can delete the class annotation and declare them on all methods
I have these entities
/**
* nmarea
* #ORM\Entity
* #UniqueEntity(fields={"area"}, message="error.nmArea.area.unique")
* #ORM\Table(name="master.nmarea")
* #Tenant
*/
class nmArea
{
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
**/
private $id;
/**
* #var string
* #Assert\NotBlank(message="error.nmArea.area.blank")
* #ORM\Column(name="area", type="string", length=100, nullable=false)
**/
private $area;
/**
* #var boolean
*
* #ORM\Column(name="eliminado", type="boolean", nullable=false)
**/
private $eliminado;
/**
* #ORM\ManyToOne(targetEntity="nmEmpresa")
* #ORM\JoinColumn(name="id_empresa", referencedColumnName="id", nullable=false)
*/
private $empresa;
/**
* #ORM\ManyToOne(targetEntity="nmUsuario")
* #ORM\JoinColumn(name="id_representante", referencedColumnName="id", nullable=false)
* #Assert\NotBlank(message="error.nmArea.representante.blank")
*/
private $representante;
/**
* #return mixed
*/
public function getEmpresa()
{
return $this->empresa;
}
/**
* #param mixed $empresa
*/
public function setEmpresa($empresa)
{
$this->empresa = $empresa;
}
/**
* #return mixed
*/
public function getRepresentante()
{
return $this->representante;
}
/**
* #param mixed $representante
*/
public function setRepresentante($representante)
{
$this->representante = $representante;
}
/**
* #return string
*/
public function getArea()
{
return $this->area;
}
/**
* #param string $area
*/
public function setArea($area)
{
$this->area = $area;
}
/**
* #return boolean
*/
public function getEliminado()
{
return $this->eliminado;
}
/**
* #param boolean $eliminado
*/
public function setEliminado($eliminado)
{
$this->eliminado = $eliminado;
}
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
public function getName()
{
return $this->area;
}
}
and
/**
* nmentidad_area
* #ORM\Entity
* #ORM\Table(name="master.nmentidad_area")
* #Tenant
*/
class nmEntidadArea
{
/**
* #var int
*
* #ORM\Column(name="id_entidad_area", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
**/
private $id;
/**
* #var boolean
*
* #ORM\Column(name="eliminado", type="boolean")
**/
private $eliminado;
/**
* #ORM\ManyToOne(targetEntity="nmEmpresa")
* #ORM\JoinColumn(name="id_empresa", referencedColumnName="id")
*/
private $empresa;
/**
* #ORM\ManyToOne(targetEntity="nmEntidad", cascade={"persist"})
* #ORM\JoinColumn(name="id_entidad", referencedColumnName="id")
*/
private $entidad;
/**
* #ORM\ManyToOne(targetEntity="nmArea")
* #ORM\JoinColumn(name="id_area", referencedColumnName="id")
*/
private $area;
/**
* #return mixed
*/
public function getArea()
{
return $this->area;
}
/**
* #param mixed $area
*/
public function setArea($area)
{
$this->area = $area;
}
/**
* #return mixed
*/
public function getEntidad()
{
return $this->entidad;
}
/**
* #param mixed $entidad
*/
public function setEntidad($entidad)
{
$this->entidad = $entidad;
}
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getEmpresa()
{
return $this->empresa;
}
/**
* #param mixed $empresa
*/
public function setEmpresa($empresa)
{
$this->empresa = $empresa;
}
/**
* #return boolean
*/
public function getEliminado()
{
return $this->eliminado;
}
/**
* #param boolean $eliminado
*/
public function setEliminado($eliminado)
{
$this->eliminado = $eliminado;
}
public function getName()
{
return $this->entidad->getNombre() . $this->area->getArea();
}
}
When I want to delete an nmArea instance I use this service
namespace AplicacionBaseBundle\DependencyInjection\Helpers;
use AplicacionBaseBundle\Entity\nmEntidad;
use AplicacionBaseBundle\Entity\nmEntidadArea;
use AplicacionBaseBundle\Entity\nmArea;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\PDOException;
use Doctrine\DBAL\Exception\ConstraintViolationException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\DBAL\Connection;
class SpamEntityHelper implements ContainerAwareInterface
{
use ContainerAwareTrait;
//... other code
public function deleteEntitySpam($repository, $id, $trueDelete = false, $hasRelations = false)
{
$em = $this->container->get('doctrine')->getManager();
$em->getConnection()->beginTransaction();
$em->getConnection()->setTransactionIsolation(Connection::TRANSACTION_SERIALIZABLE);
try {
$spamManager = $this->container->get('doctrine')->getRepository($repository);
$spam = $spamManager->find($id);
if ($trueDelete == false) {
$spam->setEliminado(true);
if (!$hasRelations) {
$cloned = clone $spam;
$em->remove($spam);
$em->flush();
$em->persist($cloned);
$em->flush();
}
} else {
$em->remove($spam);
$em->flush();
}
$em->getConnection()->commit();
return true;
} catch (DBALException $e) {
$em->getConnection()->rollBack();
$em->clear();
$this->container->get('doctrine')->resetEntityManager();
return false;
}
}
Thing is when I try to delete a nmArea object without removing it from nmEntidadArea first, Doctrine throws a ForeignKeyConstraintViolationException right in the commit line of the above service, and that is good: you cant delete if stills referenced, of course. What I cant figure out is why that error cant be catched with the try/catch block, nor like DBALException nor ForeignKeyConstraintViolationException: it doesnt matter how many catch I put, there is no way to deal with it.
I was thinking on subscribing to kernel.exception, but then I break the flow of the method and $em->getConnection()->rollBack(); throws that there is no active transaction
The full trace of error is this
exception […]
0 PDOException
message SQLSTATE[23503]: Foreign key violation: 7 ERROR: update o delete en «nmarea» viola la llave foránea «const_fk_nmentidadarea_representante» en la tabla «nmentidad_area» DETAIL: La llave (id)=(17) todavía es referida desde la tabla «nmentidad_area».
class PDOException
trace […]
0 {…}
namespace
short_class
class
type
function
file D:\xmp\htdocs\protosoft\vendor\doctrine\dbal\lib\Doctrine\DBAL\Connection.php
line 1289
args []
1 PDO
namespace
short_class PDO
class PDO
type ->
function commit
file D:\xmp\htdocs\protosoft\vendor\doctrine\dbal\lib\Doctrine\DBAL\Connection.php
line 1289
args []
2 Doctrine\DBAL\Connection
namespace Doctrine\DBAL
short_class Connection
class Doctrine\DBAL\Connection
type ->
function commit
file D:\xmp\htdocs\protosoft\src\AplicacionBaseBundle\DependencyInjection\Helpers\SpamEntityHelper.php
line 103
args []
3 AplicacionBaseBundle\DependencyInjection\Helpers\SpamEntityHelper
namespace AplicacionBaseBundle\DependencyInjection\Helpers
short_class SpamEntityHelper
class AplicacionBaseBundle\DependencyInjection\Helpers\SpamEntityHelper
type ->
function deleteEntitySpam
file D:\xmp\htdocs\protosoft\src\AplicacionBaseBundle\Controller\NomenclatorsController.php
line 45
args […]
4 AplicacionBaseBundle\Controller\NomenclatorsController
namespace AplicacionBaseBundle\Controller
short_class NomenclatorsController
class AplicacionBaseBundle\Controller\NomenclatorsController
type ->
function deleteENTITYAction
file D:\xmp\htdocs\protosoft\src\AplicacionBaseBundle\Controller\NomenclatorsController.php
line 82
args […]
5 AplicacionBaseBundle\Controller\NomenclatorsController
namespace AplicacionBaseBundle\Controller
short_class NomenclatorsController
class AplicacionBaseBundle\Controller\NomenclatorsController
type ->
function performDeleteAction
file D:\xmp\htdocs\protosoft\src\AplicacionBaseBundle\Controller\AreaController.php
line 58
args […]
6 AplicacionBaseBundle\Controller\AreaController
namespace AplicacionBaseBundle\Controller
short_class AreaController
class AplicacionBaseBundle\Controller\AreaController
type ->
function deleteAreaAction
file D:\xmp\htdocs\protosoft\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php
line 151
args […]
7 Symfony\Component\HttpKernel\HttpKernel
namespace Symfony\Component\HttpKernel
short_class HttpKernel
class Symfony\Component\HttpKernel\HttpKernel
type ->
function handleRaw
file D:\xmp\htdocs\protosoft\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php
line 68
args […]
8 Symfony\Component\HttpKernel\HttpKernel
namespace Symfony\Component\HttpKernel
short_class HttpKernel
class Symfony\Component\HttpKernel\HttpKernel
type ->
function handle
file D:\xmp\htdocs\protosoft\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Kernel.php
line 169
args […]
9 Symfony\Component\HttpKernel\Kernel
namespace Symfony\Component\HttpKernel
short_class Kernel
class Symfony\Component\HttpKernel\Kernel
type ->
function handle
file D:\xmp\htdocs\protosoft\web\app_dev.php
line 29
UPDATE 1
After trying( again) the solutions provided in the answers without result I began to believe that it was not a properly said 'doctrine or symfony problem'. So i ran the delete query on my pgadmin(I m using postgresql) and the query result was exactly the same that in my app, literally the same:
update o delete en «nmarea» viola la llave foránea «const_fk_nmentidadarea_representante» en la tabla «nmentidad_area»
DETAIL: La llave (id)=(17) todavía es referida desde la tabla «nmentidad_area».
So, what I think is that when I try to delete Symfony and/or Doctrine look for errors and everything is Ok, but then the database returns the error, and for some reason the server does not handle that error and ends up being a fatal error, which is handled as a kernel error. But why?
You’re getting a PDOException, while you’re catch is limited to DBALException – which is not an instance of PDOException! So, obviously, the exception doesn’t get caught.
If you want to catch both types of exceptions, you can use the newly introduced catch pipe operator:
try
{
// …
}
catch (PDOException | DBALException $e)
{
// …
}
This works in PHP 7.1 and higher. If your code needs to run on servers with PHP < 7.1, you could listen for just any exception and then determine the type of exception within the catch block:
try
{
// …
}
catch (Exception $e)
{
if ($e instanceof PDOException || $e instanceof DBALException)
{
// …
}
else
{
throw $e;
}
}
Or you could use multiple catch blocks:
try
{
// …
}
catch (PDOException $e)
{
// …
}
catch (DBALException $e)
{
// …
}
Either way, remember to import namespaces properly, otherwise exceptions will not be caught.
I have three controller named BlogController, PostController, CommentController that CommentController is sub resource of PostController and PostController sub resource of BlogController.
/**
* #Rest\RouteResource("blog", pluralize=false)
*/
class BlogController extends FOSRestController
{
public function getAction($blogUri)
{
...
}
}
/**
* #Rest\RouteResource("post", pluralize=false)
*/
class PostController extends FOSRestController
{
public function getAction($postId)
{
...
}
}
/**
* #Rest\RouteResource("comment", pluralize=false)
*/
class CommentController extends FOSRestController
{
public function getAction($commentId)
{
...
}
}
routing.yml
mgh_blog:
resource: MGH\BlogBundle\Controller\BlogController
type: rest
mgh_blog_post:
resource: MGH\BlogBundle\Controller\PostController
type: rest
parent: mgh_blog
mgh_blog_post_comment:
resource: MGH\PostBundle\Controller\CommentController
type: rest
parent: mgh_blog_post
I define getAction methods, but i get following error:
[InvalidArgumentException]
Every parent controller must have `get{SINGULAR}Action($id)` method
where {SINGULAR} is a singular form of associated object
Edit:
I also try to change the method's name to getCommentAction($commentId), getPostAction($postId) and getBlogAction, but it no work.
When I use #RouteResource annotations, method name must be getAction($id), otherwise it doesn't work.
When I change parent of mgh_blog_post_comment router to mgh_blog, it's working!
That error description is awful and a big time waster because it doesn't tell you what the real problem is. Try the following:
/**
* #Rest\RouteResource("blog", pluralize=false)
*/
class BlogController extends FOSRestController
{
public function getAction($blogUri)
{
...
}
}
/**
* #Rest\RouteResource("post", pluralize=false)
*/
class PostController extends FOSRestController
{
public function getAction($blogUri, $postId)
{
...
}
}
/**
* #Rest\RouteResource("comment", pluralize=false)
*/
class CommentController extends FOSRestController
{
public function getAction($blogUri, $postId, $commentId)
{
...
}
}
You didn't have the correct number of arguments in the descendant controller actions. It took me two days of step debugging to figure this out.
The parent route, Blog, looks like:
/blog/{blogUri}
It will match
public function getAction($blogUri)
The child route, Post, looks like:
/blog/{blogUri}/post/{postId}
It will not match the code below because it needs two parameters. The same is true for the grandchild--which is looking for three parameters:
public function getAction($postId)
The grandchild route, Comment, looks like:
/blog/{blogUri}/post/{postId}/comment/{commentId}
The code keeps track of the ancestors of each controller.
The post has 1 ancestor. When building the routes for the post controller, the code looks at the number of parameters on the 'get action'. It take the number of parameters and subtracts the number of ancestors. If the difference is not equal to one, it throws the error.
Conclusion, for each descendant, it needs to include the ID parameters of it ancestors AND its own ID. There should always be one more parameter than there are ancestors.
Have you tried?:
class CommentController extends FOSRestController
{
public function getCommentAction($commentId)
{
...
}
}
Try:
public function cgetAction(Request $request)
{
...
}
This is my controller example:
<?php
namespace Cf\SClinicBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use Cf\SClinicBundle\Entity\CfAcquireImage;
use Doctrine\DBAL\DBALException as DBALException;
use Doctrine\ORM\NoResultException as NoResultException;
/**
* CfAcquireImage controller.
*
* #RouteResource("acquire-image")
*/
class ApiCfAcquireImageController extends FOSRestController
{
/**
* #var array
*/
public $status;
/**
* #var
*/
public $parameter;
/**
* #var
*/
private $role_name;
/**
* Constructor
*/
public function __construct()
{
}
/**
* Lists all Cf Acquire Image entities.
*
* #param Request $request
*
* #return mixed
*/
public function cgetAction(Request $request)
{
}
/**
* Finds a Cf Acquire Image entity by id.
*
* #param Request $request
* #param $id $id
*
* #return array
*/
public function getAction(Request $request, $id)
{
}
/**
* Create a new Cf Acquire Image entity.
*
* #param Request $request
*
* #return mixed
*/
public function postAction(Request $request)
{
}
/**
* #param Request $request
* #param $id
*
* #return array
*/
public function putAction(Request $request, $id)
{
}
/**
* Deletes a Cf Acquire Image entity.
*
* #param Request $request
* #param $id
*
* #return mixed
*/
public function deleteAction(Request $request, $id)
{
}
}
I'm experimenting with Hibernate Validator's method validation.
I've written the simplest EJB I can imagine and annotating the constraints on it.
However, it won't work with my custom annotations.
public String checkString(#NotNull #NotBlank #Characters(invalidCharacters = { '1' }) String string);
The last annotation checks that a String contains certain characters of a character set (with a default) and is a custom constraint. This works in a normal Validator environment.
However in my test, only the standard annotations work, my custom annotations don't.
this.parameterValidation.checkString(null); // Throws Exception
this.parameterValidation.checkString(""); // Throws Exception
this.parameterValidation.checkString("123"); // Does NOT throw Exception - why?
I've also tested another custom annotation, and that remains inactive as well.
What am I doing wrong?
Here's the code of the custom annotation:
/**
* Checks if a String contains only characters from the given character set. Note that this sets a parameter
* {#code positions} with a comma-separated list of the positions of invalid characters (based on 1, not 0!).
*/
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#Constraint(validatedBy = CharactersCheck.class)
public #interface Characters {
/**
* The I18N key of the error message.
*/
public static final String I18N_KEY = "ipi.msg.validation.characters";
/**
* Error message.
*/
String message() default LEFT_CURLY_BRACKET + I18N_KEY + RIGHT_CURLY_BRACKET;
/**
* The associated violation groups.
*/
Class<?>[] groups() default {};
/**
* The payload.
*/
Class<? extends Payload>[] payload() default {};
/**
* The character set to which the text must conform.
*/
CharacterSet characterSet() default CharacterSet.ISO_8859_15;
/**
* Additional characters which must not be found in the text.
*/
char[] invalidCharacters() default {};
/**
* If this is {#code true}, carriage returns and line feeds are allowed in the text, making it a multi-line text.
*/
boolean carriageReturnAllowed() default false;
/**
* Defines several {#link Characters} annotations on the same element.
*/
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#interface List {
/**
* The {#link Characters} annotations.
*/
Characters[] value();
}
}
Here's the implementation:
public class CharactersCheck implements ConstraintValidator<Characters, CharSequence>, MessageAttributeModifier {
/**
* The character set to check.
*/
private CharacterSet characterSet;
/**
* Additional invalid characters.
*/
private char[] invalidCharacters;
/**
* If this is {#code true}, carriage returns and line feeds are allowed in the text, making it a multi-line text.
*/
private boolean carriageReturnAllowed;
private SortedSet<Integer> invalidCharacterPositions;
/**
* {#inheritDoc}
*/
#Override
public void initialize(final Characters constraintAnnotation) {
this.characterSet = constraintAnnotation.characterSet();
this.carriageReturnAllowed = constraintAnnotation.carriageReturnAllowed();
if (this.carriageReturnAllowed) {
this.invalidCharacters = constraintAnnotation.invalidCharacters();
} else {
final int invalidCharactersLength = constraintAnnotation.invalidCharacters().length;
this.invalidCharacters = Arrays.copyOf(constraintAnnotation.invalidCharacters(), invalidCharactersLength + 2);
this.invalidCharacters[invalidCharactersLength] = '\r';
this.invalidCharacters[invalidCharactersLength + 1] = '\n';
}
this.invalidCharacterPositions = new TreeSet<>();
}
/**
* {#inheritDoc}
*/
#Override
public boolean isValid(final CharSequence value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
} else {
setInvalidCharacterPositions(value);
return this.invalidCharacterPositions.isEmpty();
}
}
private void setInvalidCharacterPositions(final CharSequence value) {
this.invalidCharacterPositions = CharacterChecker.checkCharacters(String.valueOf(value), this.characterSet,
this.invalidCharacters);
}
/**
* {#inheritDoc}
*/
#Override
public void modifyAttributes(final Map<String, Object> attributes, final Context context) {
setInvalidCharacterPositions((CharSequence) context.getValidatedValue());
if (!this.invalidCharacterPositions.isEmpty()) {
attributes.put(MessageVariables.POSITIONS, StringUtils.join(this.invalidCharacterPositions, ", "));
}
}
}
Note that I created a custom message interpolator which lets checks add message variables to the given Map. For this I create a new instance of the check within the message interpolator and call it using this interface:
/**
* Modifies attributes within a validation message.
*/
public interface MessageAttributeModifier {
/**
* Modifies the attributes within the validation message.
* #param attributes The existing attributes, which can be modified; the attributes already contain the values from
* the annotation and additionally also {#link MessageVariables#VALIDATED_VALUE}
* #param context The validation context
*/
public void modifyAttributes(Map<String, Object> attributes, final Context context);
}