[Form] Some code cleanup

This commit is contained in:
Victor Berchet 2012-05-25 09:17:31 +02:00 committed by Bernhard Schussek
parent 94f6f7776a
commit bc15e2d68b
7 changed files with 199 additions and 370 deletions

View File

@ -137,11 +137,7 @@ class FormType extends AbstractType
{ {
// Derive "data_class" option from passed "data" object // Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) { $dataClass = function (Options $options) {
if (is_object($options['data'])) { return is_object($options['data']) ? get_class($options['data']) : null;
return get_class($options['data']);
}
return null;
}; };
// Derive "empty_data" closure from "data_class" option // Derive "empty_data" closure from "data_class" option
@ -150,20 +146,12 @@ class FormType extends AbstractType
if (null !== $class) { if (null !== $class) {
return function (FormInterface $form) use ($class) { return function (FormInterface $form) use ($class) {
if ($form->isEmpty() && !$form->isRequired()) { return $form->isEmpty() && !$form->isRequired() ? null : new $class();
return null;
}
return new $class();
}; };
} }
return function (FormInterface $form) { return function (FormInterface $form) {
if (count($form) > 0) { return count($form) > 0 ? array() : '';
return array();
}
return '';
}; };
}; };

View File

@ -34,11 +34,7 @@ class FormValidator extends ConstraintValidator
*/ */
public function __construct(ServerParams $params = null) public function __construct(ServerParams $params = null)
{ {
if (null === $params) { $this->serverParams = $params ?: new ServerParams();
$params = new ServerParams();
}
$this->serverParams = $params;
} }
/** /**
@ -107,31 +103,14 @@ class FormValidator extends ConstraintValidator
$length = $this->serverParams->getContentLength(); $length = $this->serverParams->getContentLength();
if ($form->isRoot() && null !== $length) { if ($form->isRoot() && null !== $length) {
$max = strtoupper(trim($this->serverParams->getPostMaxSize())); $max = $this->serverParams->getPostMaxSize();
if ('' !== $max) { if (null !== $max && $length > $max) {
$maxLength = (int) $max; $this->context->addViolation(
$config->getOption('post_max_size_message'),
switch (substr($max, -1)) { array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()),
// The 'G' modifier is available since PHP 5.1.0 $length
case 'G': );
$maxLength *= pow(1024, 3);
break;
case 'M':
$maxLength *= pow(1024, 2);
break;
case 'K':
$maxLength *= 1024;
break;
}
if ($length > $maxLength) {
$this->context->addViolation(
$config->getOption('post_max_size_message'),
array('{{ max }}' => $max),
$length
);
}
} }
} }
} }
@ -159,14 +138,10 @@ class FormValidator extends ConstraintValidator
// Non-root forms are validated if validation cascading // Non-root forms are validated if validation cascading
// is enabled in all ancestor forms // is enabled in all ancestor forms
$parent = $form->getParent(); while (null !== ($form = $form->getParent())) {
if (!$form->getConfig()->getOption('cascade_validation')) {
while (null !== $parent) {
if (!$parent->getConfig()->getOption('cascade_validation')) {
return false; return false;
} }
$parent = $parent->getParent();
} }
return true; return true;
@ -181,22 +156,20 @@ class FormValidator extends ConstraintValidator
*/ */
private function getValidationGroups(FormInterface $form) private function getValidationGroups(FormInterface $form)
{ {
$groups = null; do {
while (null !== $form && null === $groups) {
$groups = $form->getConfig()->getOption('validation_groups'); $groups = $form->getConfig()->getOption('validation_groups');
if (is_callable($groups)) { if (null !== $groups) {
$groups = (array) call_user_func($groups, $form); if (is_callable($groups)) {
$groups = call_user_func($groups, $form);
}
return (array) $groups;
} }
$form = $form->getParent(); $form = $form->getParent();
} } while (null !== $form);
if (null === $groups) { return array(Constraint::DEFAULT_GROUP);
$groups = array(Constraint::DEFAULT_GROUP);
}
return (array) $groups;
} }
} }

View File

@ -12,18 +12,11 @@
namespace Symfony\Component\Form\Extension\Validator\EventListener; namespace Symfony\Component\Form\Extension\Validator\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Event\DataEvent; use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\Validator\ExecutionContext;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>

View File

@ -17,13 +17,40 @@ namespace Symfony\Component\Form\Extension\Validator\Util;
class ServerParams class ServerParams
{ {
/** /**
* Returns the "post_max_size" ini setting. * Returns maximum post size in bytes.
* *
* @return string The value of the ini setting. * @return null|integer The maximum post size in bytes
*/ */
public function getPostMaxSize() public function getPostMaxSize()
{ {
return ini_get('post_max_size'); $iniMax = $this->getNormalizedIniPostMaxSize();
if ('' === $iniMax) {
return null;
}
$max = (int) $iniMax;
switch (substr($iniMax, -1)) {
case 'G':
$max *= 1024;
case 'M':
$max *= 1024;
case 'K':
$max *= 1024;
}
return $max;
}
/**
* Returns the normalized "post_max_size" ini setting.
*
* @return string
*/
public function getNormalizedIniPostMaxSize()
{
return strtoupper(trim(ini_get('post_max_size')));
} }
/** /**

View File

@ -55,7 +55,10 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->serverParams = $this->getMock('Symfony\Component\Form\Extension\Validator\Util\ServerParams'); $this->serverParams = $this->getMock(
'Symfony\Component\Form\Extension\Validator\Util\ServerParams',
array('getNormalizedIniPostMaxSize', 'getContentLength')
);
$this->validator = new FormValidator($this->serverParams); $this->validator = new FormValidator($this->serverParams);
} }
@ -408,14 +411,18 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('Extra!', $context->getViolations()->get(0)->getMessage()); $this->assertEquals('Extra!', $context->getViolations()->get(0)->getMessage());
} }
public function testViolationIfPostMaxSizeExceeded_GigaUpper()
/**
* @dataProvider getPostMaxSizeFixtures
*/
public function testPostMaxSizeViolation($contentLength, $iniMax, $nbViolation, $msg)
{ {
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->once())
->method('getContentLength') ->method('getContentLength')
->will($this->returnValue(pow(1024, 3) + 1)); ->will($this->returnValue($contentLength));
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->any())
->method('getPostMaxSize') ->method('getNormalizedIniPostMaxSize')
->will($this->returnValue('1G')); ->will($this->returnValue($iniMax));
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!'); $options = array('post_max_size_message' => 'Max {{ max }}!');
@ -424,132 +431,33 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->validator->initialize($context); $this->validator->initialize($context);
$this->validator->validate($form, new Form()); $this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations()); $this->assertCount($nbViolation, $context->getViolations());
$this->assertEquals('Max 1G!', $context->getViolations()->get(0)->getMessage()); if (null !== $msg) {
$this->assertEquals($msg, $context->getViolations()->get(0)->getMessage());
}
} }
public function testViolationIfPostMaxSizeExceeded_GigaLower() public function getPostMaxSizeFixtures()
{ {
$this->serverParams->expects($this->any()) return array(
->method('getContentLength') array(pow(1024, 3) + 1, '1G', 1, 'Max 1G!'),
->will($this->returnValue(pow(1024, 3) + 1)); array(pow(1024, 3), '1G', 0, null),
$this->serverParams->expects($this->any()) array(pow(1024, 2) + 1, '1M', 1, 'Max 1M!'),
->method('getPostMaxSize') array(pow(1024, 2), '1M', 0, null),
->will($this->returnValue('1g')); array(1024 + 1, '1K', 1, 'Max 1K!'),
array(1024, '1K', 0, null),
$context = $this->getExecutionContext(); array(null, '1K', 0, null),
$options = array('post_max_size_message' => 'Max {{ max }}!'); array(1024, '', 0, null),
$form = $this->getBuilder('name', null, $options)->getForm(); );
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1G!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Giga()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 3)));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1G'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testViolationIfPostMaxSizeExceeded_Mega()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 2) + 1));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1M'));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1M!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Mega()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 2)));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1M'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testViolationIfPostMaxSizeExceeded_Kilo()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1K!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Kilo()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1024));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
} }
public function testNoViolationIfNotRoot() public function testNoViolationIfNotRoot()
{ {
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->once())
->method('getContentLength') ->method('getContentLength')
->will($this->returnValue(1025)); ->will($this->returnValue(1025));
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->never())
->method('getPostMaxSize') ->method('getNormalizedIniPostMaxSize');
->will($this->returnValue('1K'));
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$parent = $this->getForm(); $parent = $this->getForm();
@ -562,80 +470,6 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertCount(0, $context->getViolations()); $this->assertCount(0, $context->getViolations());
} }
public function testNoViolationIfContentLengthNull()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(null));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testTrimPostMaxSize()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(' 1K '));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1K!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeEmpty()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(' '));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testNoViolationIfPostMaxSizeNull()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(null));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
/** /**
* Access has to be public, as this method is called via callback array * Access has to be public, as this method is called via callback array
* in {@link testValidateFormDataCanHandleCallbackValidationGroups()} * in {@link testValidateFormDataCanHandleCallbackValidationGroups()}

View File

@ -22,7 +22,7 @@ class FormTypeValidatorExtensionTest extends TypeTestCase
$this->assertNull($form->getConfig()->getOption('validation_groups')); $this->assertNull($form->getConfig()->getOption('validation_groups'));
} }
public function testValidationGroupsCanBeSetToString() public function testValidationGroupsTransformedToArray()
{ {
$form = $this->factory->create('form', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => 'group', 'validation_groups' => 'group',

View File

@ -353,9 +353,9 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
/** /**
* Reads the a property from an object or array. * Reads the a property from an object or array.
* *
* @param object|array $objectOrArray The object or array to read from. * @param object|array $objectOrArray The object or array to read from.
* @param string $property The property to read. * @param string $property The property to read.
* @param integer $isIndex Whether to interpret the property as index. * @param Boolean $isIndex Whether to interpret the property as index.
* *
* @return mixed The value of the read property * @return mixed The value of the read property
* *
@ -425,11 +425,11 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
/** /**
* Sets the value of the property at the given index in the path * Sets the value of the property at the given index in the path
* *
* @param object|array $objectOrArray The object or array to write to. * @param object|array $objectOrArray The object or array to write to.
* @param string $property The property to write. * @param string $property The property to write.
* @param string $singular The singular form of the property name or null. * @param string $singular The singular form of the property name or null.
* @param integer $isIndex Whether to interpret the property as index. * @param Boolean $isIndex Whether to interpret the property as index.
* @param mixed $value The value to write. * @param mixed $value The value to write.
* *
* @throws InvalidPropertyException If the property does not exist. * @throws InvalidPropertyException If the property does not exist.
* @throws PropertyAccessDeniedException If the property cannot be accessed due to * @throws PropertyAccessDeniedException If the property cannot be accessed due to
@ -445,107 +445,46 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
$objectOrArray[$property] = $value; $objectOrArray[$property] = $value;
} elseif (is_object($objectOrArray)) { } elseif (is_object($objectOrArray)) {
$reflClass = new ReflectionClass($objectOrArray); $reflClass = new ReflectionClass($objectOrArray);
$setter = 'set'.$this->camelize($property);
$addMethod = null;
$removeMethod = null;
$plural = null;
// Check if the parent has matching methods to add/remove items
if (is_array($value) || $value instanceof Traversable) { if (is_array($value) || $value instanceof Traversable) {
if (null !== $singular) { $methods = $this->findAdderAndRemover($reflClass, $singular);
$addMethod = 'add' . ucfirst($singular); if (null !== $methods) {
$removeMethod = 'remove' . ucfirst($singular); // At this point the add and remove methods have been found
$itemsToAdd = is_object($value) ? clone $value : $value;
$itemToRemove = array();
$previousValue = $this->readProperty($objectOrArray, $property, $isIndex);
if (!$this->isAccessible($reflClass, $addMethod, 1)) { if (is_array($previousValue) || $previousValue instanceof Traversable) {
throw new InvalidPropertyException(sprintf( foreach ($previousValue as $previousItem) {
'The public method "%s" with exactly one required parameter was not found on class %s', foreach ($value as $key => $item) {
$addMethod, if ($item === $previousItem) {
$reflClass->getName() // Item found, don't add
)); unset($itemsToAdd[$key]);
}
if (!$this->isAccessible($reflClass, $removeMethod, 1)) { // Next $previousItem
throw new InvalidPropertyException(sprintf( continue 2;
'The public method "%s" with exactly one required parameter was not found on class %s', }
$removeMethod, }
$reflClass->getName()
));
}
} else {
// The plural form is the last element of the property path
$plural = ucfirst($this->elements[$this->length - 1]);
// Any of the two methods is required, but not yet known // Item not found, add to remove list
$singulars = (array) FormUtil::singularify($plural); $itemToRemove[] = $previousItem;
foreach ($singulars as $singular) {
$addMethodName = 'add' . $singular;
$removeMethodName = 'remove' . $singular;
if ($this->isAccessible($reflClass, $addMethodName, 1)) {
$addMethod = $addMethodName;
}
if ($this->isAccessible($reflClass, $removeMethodName, 1)) {
$removeMethod = $removeMethodName;
}
if ($addMethod && !$removeMethod) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$addMethodName,
$removeMethodName,
$reflClass->getName()
));
}
if ($removeMethod && !$addMethod) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$removeMethodName,
$addMethodName,
$reflClass->getName()
));
}
if ($addMethod && $removeMethod) {
break;
} }
} }
foreach ($itemToRemove as $item) {
call_user_func(array($objectOrArray, $methods[1]), $item);
}
foreach ($itemsToAdd as $item) {
call_user_func(array($objectOrArray, $methods[0]), $item);
}
return;
} }
} }
// Collection with matching adder/remover in $objectOrArray $setter = 'set'.$this->camelize($property);
if ($addMethod && $removeMethod) { if ($reflClass->hasMethod($setter)) {
$itemsToAdd = is_object($value) ? clone $value : $value;
$itemToRemove = array();
$previousValue = $this->readProperty($objectOrArray, $property, $isIndex);
if (is_array($previousValue) || $previousValue instanceof Traversable) {
foreach ($previousValue as $previousItem) {
foreach ($value as $key => $item) {
if ($item === $previousItem) {
// Item found, don't add
unset($itemsToAdd[$key]);
// Next $previousItem
continue 2;
}
}
// Item not found, add to remove list
$itemToRemove[] = $previousItem;
}
}
foreach ($itemToRemove as $item) {
$objectOrArray->$removeMethod($item);
}
foreach ($itemsToAdd as $item) {
$objectOrArray->$addMethod($item);
}
} elseif ($reflClass->hasMethod($setter)) {
if (!$reflClass->getMethod($setter)->isPublic()) { if (!$reflClass->getMethod($setter)->isPublic()) {
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName())); throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
} }
@ -583,6 +522,81 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string); return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
} }
/**
* Searches for add and remove methods.
*
* @param \ReflectionClass $reflClass The reflection class for the given object
* @param string $singular The singular form of the property name or null.
*
* @return array|null An array containin the adder and remover when found, null otherwise.
*
* @throws InvalidPropertyException If the property does not exist.
*/
private function findAdderAndRemover(\ReflectionClass $reflClass, $singular)
{
if (null !== $singular) {
$addMethod = 'add' . ucfirst($singular);
$removeMethod = 'remove' . ucfirst($singular);
if (!$this->isAccessible($reflClass, $addMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$addMethod,
$reflClass->getName()
));
}
if (!$this->isAccessible($reflClass, $removeMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$removeMethod,
$reflClass->getName()
));
}
return array($addMethod, $removeMethod);
} else {
// The plural form is the last element of the property path
$plural = ucfirst($this->elements[$this->length - 1]);
// Any of the two methods is required, but not yet known
$singulars = (array) FormUtil::singularify($plural);
foreach ($singulars as $singular) {
$methodsFound = 0;
$addMethodFound = false;
$addMethodName = 'add' . $singular;
$removeMethodName = 'remove' . $singular;
if ($this->isAccessible($reflClass, $addMethodName, 1)) {
$addMethod = $addMethodName;
$addMethodFound = true;
$methodsFound++;
}
if ($this->isAccessible($reflClass, $removeMethodName, 1)) {
$removeMethod = $removeMethodName;
$methodsFound++;
}
if (2 == $methodsFound) {
return array($addMethod, $removeMethod);
}
if (1 == $methodsFound) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$addMethodFound ? $addMethodName : $removeMethodName,
$addMethodFound ? $removeMethodName : $addMethodName,
$reflClass->getName()
));
}
}
}
return null;
}
/** /**
* Returns whether a method is public and has a specific number of required parameters. * Returns whether a method is public and has a specific number of required parameters.
* *