Merge branch '2.2' into 2.3

* 2.2:
  corrected English grammar (s/does not exists/does not exist)
  [Process] Add more precision to Process::stop timeout
  [Process] Avoid zombie process in case of unit tests failure
  [Process] Fix #8739
  [Process] Add failing test for #8739
  [Process] Fix CS
  Fixed documentation grammar for AuthenticationManagerInterface::authenticate()
  [Validator] fixed the wrong isAbstract() check against the class (fixed #8589)
  [TwigBridge] Prevent code extension to display warning
  Use strstr instead of strpos

Conflicts:
	src/Symfony/Component/Finder/Shell/Command.php
	src/Symfony/Component/Process/Process.php
This commit is contained in:
Fabien Potencier 2013-08-13 22:18:00 +02:00
commit 11018011dd
15 changed files with 212 additions and 177 deletions

View File

@ -137,7 +137,9 @@ class CodeExtension extends \Twig_Extension
public function fileExcerpt($file, $line) public function fileExcerpt($file, $line)
{ {
if (is_readable($file)) { if (is_readable($file)) {
$code = highlight_file($file, true); // highlight_file could throw warnings
// see https://bugs.php.net/bug.php?id=25725
$code = @highlight_file($file, true);
// remove main code/span tags // remove main code/span tags
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code); $code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
$content = preg_split('#<br />#', $code); $content = preg_split('#<br />#', $code);

View File

@ -131,7 +131,9 @@ class CodeHelper extends Helper
} }
} }
$code = highlight_file($file, true); // highlight_file could throw warnings
// see https://bugs.php.net/bug.php?id=25725
$code = @highlight_file($file, true);
// remove main code/span tags // remove main code/span tags
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code); $code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
$content = preg_split('#<br />#', $code); $content = preg_split('#<br />#', $code);

View File

@ -177,7 +177,7 @@ class ClassLoader
$classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php';
foreach ($this->prefixes as $prefix => $dirs) { foreach ($this->prefixes as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) { if ($class === strstr($class, $prefix)) {
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) {
return $dir.DIRECTORY_SEPARATOR.$classPath; return $dir.DIRECTORY_SEPARATOR.$classPath;

View File

@ -187,7 +187,7 @@ class Command
public function get($label) public function get($label)
{ {
if (!isset($this->labels[$label])) { if (!isset($this->labels[$label])) {
throw new \RuntimeException(sprintf('Label "%s" does not exists.', $label)); throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label));
} }
return $this->bits[$this->labels[$label]]; return $this->bits[$this->labels[$label]];

View File

@ -35,7 +35,7 @@ interface ProfilerStorageInterface
/** /**
* Reads data associated with the given token. * Reads data associated with the given token.
* *
* The method returns false if the token does not exists in the storage. * The method returns false if the token does not exist in the storage.
* *
* @param string $token A token * @param string $token A token
* *

View File

@ -39,6 +39,7 @@ class Process
// Timeout Precision in seconds. // Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2; const TIMEOUT_PRECISION = 0.2;
private $callback;
private $commandline; private $commandline;
private $cwd; private $cwd;
private $env; private $env;
@ -166,6 +167,7 @@ class Process
public function __clone() public function __clone()
{ {
$this->callback = null;
$this->exitcode = null; $this->exitcode = null;
$this->fallbackExitcode = null; $this->fallbackExitcode = null;
$this->processInformation = null; $this->processInformation = null;
@ -201,7 +203,7 @@ class Process
{ {
$this->start($callback); $this->start($callback);
return $this->wait($callback); return $this->wait();
} }
/** /**
@ -236,7 +238,7 @@ class Process
$this->stderr = ''; $this->stderr = '';
$this->incrementalOutputOffset = 0; $this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0; $this->incrementalErrorOutputOffset = 0;
$callback = $this->buildCallback($callback); $this->callback = $this->buildCallback($callback);
$descriptors = $this->getDescriptors(); $descriptors = $this->getDescriptors();
$commandline = $this->commandline; $commandline = $this->commandline;
@ -259,73 +261,9 @@ class Process
stream_set_blocking($pipe, false); stream_set_blocking($pipe, false);
} }
if ($this->tty) { $this->writePipes(false);
$this->status = self::STATUS_TERMINATED; $this->updateStatus(false);
$this->checkTimeout();
return;
}
if (null === $this->stdin) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
return;
}
$writePipes = array($this->pipes[0]);
unset($this->pipes[0]);
$stdinLen = strlen($this->stdin);
$stdinOffset = 0;
while ($writePipes) {
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->processFileHandles($callback);
}
$r = $this->pipes;
$w = $writePipes;
$e = null;
if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
// if a system call has been interrupted, forget about it, let's try again
if ($this->hasSystemCallBeenInterrupted()) {
continue;
}
break;
}
// nothing has changed, let's wait until the process is ready
if (0 === $n) {
continue;
}
if ($w) {
$written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
foreach ($r as $pipe) {
$type = array_search($pipe, $this->pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
$this->checkTimeout();
}
$this->updateStatus();
} }
/** /**
@ -371,55 +309,15 @@ class Process
*/ */
public function wait($callback = null) public function wait($callback = null)
{ {
$this->updateStatus(); $this->updateStatus(false);
$callback = $this->buildCallback($callback); if (null !== $callback) {
while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) { $this->callback = $this->buildCallback($callback);
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
$this->processFileHandles($callback, !$this->pipes);
}
$this->checkTimeout();
if ($this->pipes) {
$r = $this->pipes;
$w = null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occured, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
continue;
}
// nothing has changed
if (0 === $n) {
continue;
}
foreach ($r as $pipe) {
$type = array_search($pipe, $this->pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
// last exit code is output and caught to work around --enable-sigchild
if (3 == $type) {
$this->fallbackExitcode = (int) $data;
} else {
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data);
}
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
}
} }
$this->updateStatus(); while ($this->processInformation['running']) {
$this->updateStatus(true);
$this->checkTimeout();
}
$this->updateStatus(false);
if ($this->processInformation['signaled']) { if ($this->processInformation['signaled']) {
if ($this->isSigchildEnabled()) { if ($this->isSigchildEnabled()) {
throw new RuntimeException('The process has been signaled.'); throw new RuntimeException('The process has been signaled.');
@ -466,7 +364,7 @@ class Process
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
} }
$this->updateStatus(); $this->updateStatus(false);
return $this->isRunning() ? $this->processInformation['pid'] : null; return $this->isRunning() ? $this->processInformation['pid'] : null;
} }
@ -507,7 +405,7 @@ class Process
*/ */
public function getOutput() public function getOutput()
{ {
$this->updateOutput(); $this->readPipes(false);
return $this->stdout; return $this->stdout;
} }
@ -539,7 +437,7 @@ class Process
*/ */
public function getErrorOutput() public function getErrorOutput()
{ {
$this->updateErrorOutput(); $this->readPipes(false);
return $this->stderr; return $this->stderr;
} }
@ -578,7 +476,7 @@ class Process
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method');
} }
$this->updateStatus(); $this->updateStatus(false);
return $this->exitcode; return $this->exitcode;
} }
@ -630,7 +528,7 @@ class Process
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
} }
$this->updateStatus(); $this->updateStatus(false);
return $this->processInformation['signaled']; return $this->processInformation['signaled'];
} }
@ -652,7 +550,7 @@ class Process
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
} }
$this->updateStatus(); $this->updateStatus(false);
return $this->processInformation['termsig']; return $this->processInformation['termsig'];
} }
@ -668,7 +566,7 @@ class Process
*/ */
public function hasBeenStopped() public function hasBeenStopped()
{ {
$this->updateStatus(); $this->updateStatus(false);
return $this->processInformation['stopped']; return $this->processInformation['stopped'];
} }
@ -684,7 +582,7 @@ class Process
*/ */
public function getStopSignal() public function getStopSignal()
{ {
$this->updateStatus(); $this->updateStatus(false);
return $this->processInformation['stopsig']; return $this->processInformation['stopsig'];
} }
@ -700,7 +598,7 @@ class Process
return false; return false;
} }
$this->updateStatus(); $this->updateStatus(false);
return $this->processInformation['running']; return $this->processInformation['running'];
} }
@ -722,7 +620,7 @@ class Process
*/ */
public function isTerminated() public function isTerminated()
{ {
$this->updateStatus(); $this->updateStatus(false);
return $this->status == self::STATUS_TERMINATED; return $this->status == self::STATUS_TERMINATED;
} }
@ -736,7 +634,7 @@ class Process
*/ */
public function getStatus() public function getStatus()
{ {
$this->updateStatus(); $this->updateStatus(false);
return $this->status; return $this->status;
} }
@ -753,12 +651,10 @@ class Process
*/ */
public function stop($timeout = 10, $signal = null) public function stop($timeout = 10, $signal = null)
{ {
$timeoutMicro = (int) $timeout*1E6; $timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) { if ($this->isRunning()) {
proc_terminate($this->process); proc_terminate($this->process);
$time = 0; while ($this->isRunning() && microtime(true) < $timeoutMicro) {
while (1 == $this->isRunning() && $time < $timeoutMicro) {
$time += 1000;
usleep(1000); usleep(1000);
} }
@ -1161,14 +1057,18 @@ class Process
} }
/** /**
* Updates the status of the process. * Updates the status of the process, reads pipes.
*
* @param Boolean $blocking Whether to use a clocking read call.
*/ */
protected function updateStatus() protected function updateStatus($blocking)
{ {
if (self::STATUS_STARTED !== $this->status) { if (self::STATUS_STARTED !== $this->status) {
return; return;
} }
$this->readPipes($blocking);
$this->processInformation = proc_get_status($this->process); $this->processInformation = proc_get_status($this->process);
if (!$this->processInformation['running']) { if (!$this->processInformation['running']) {
$this->status = self::STATUS_TERMINATED; $this->status = self::STATUS_TERMINATED;
@ -1178,29 +1078,6 @@ class Process
} }
} }
/**
* Updates the current error output of the process (STDERR).
*/
protected function updateErrorOutput()
{
if (isset($this->pipes[self::STDERR]) && is_resource($this->pipes[self::STDERR])) {
$this->addErrorOutput(stream_get_contents($this->pipes[self::STDERR]));
}
}
/**
* Updates the current output of the process (STDOUT).
*/
protected function updateOutput()
{
if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($this->fileHandles[self::STDOUT]) && is_resource($this->fileHandles[self::STDOUT])) {
fseek($this->fileHandles[self::STDOUT], $this->readBytes[self::STDOUT]);
$this->addOutput(stream_get_contents($this->fileHandles[self::STDOUT]));
} elseif (isset($this->pipes[self::STDOUT]) && is_resource($this->pipes[self::STDOUT])) {
$this->addOutput(stream_get_contents($this->pipes[self::STDOUT]));
}
}
/** /**
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not. * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
* *
@ -1221,10 +1098,9 @@ class Process
/** /**
* Handles the windows file handles fallbacks. * Handles the windows file handles fallbacks.
* *
* @param callable $callback A valid PHP callback
* @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed
*/ */
private function processFileHandles($callback, $closeEmptyHandles = false) private function processFileHandles($closeEmptyHandles = false)
{ {
$fh = $this->fileHandles; $fh = $this->fileHandles;
foreach ($fh as $type => $fileHandle) { foreach ($fh as $type => $fileHandle) {
@ -1232,7 +1108,7 @@ class Process
$data = fread($fileHandle, 8192); $data = fread($fileHandle, 8192);
if (strlen($data) > 0) { if (strlen($data) > 0) {
$this->readBytes[$type] += strlen($data); $this->readBytes[$type] += strlen($data);
call_user_func($callback, $type == 1 ? self::OUT : self::ERR, $data); call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
} }
if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) { if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) {
fclose($fileHandle); fclose($fileHandle);
@ -1253,4 +1129,112 @@ class Process
// stream_select returns false when the `select` system call is interrupted by an incoming signal // stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
} }
/**
* Reads pipes, executes callback.
*
* @param Boolean $blocking Whether to use blocking calls or not.
*/
private function readPipes($blocking)
{
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
$this->processFileHandles(!$this->pipes);
}
if ($this->pipes) {
$r = $this->pipes;
$w = null;
$e = null;
// let's have a look if something changed in streams
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(self::TIMEOUT_PRECISION * 1E6) : 0)) {
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occured, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return;
}
// nothing has changed
if (0 === $n) {
return;
}
foreach ($r as $pipe) {
$type = array_search($pipe, $this->pipes);
$data = fread($pipe, 8192);
if (strlen($data) > 0) {
// last exit code is output and caught to work around --enable-sigchild
if (3 == $type) {
$this->fallbackExitcode = (int) $data;
} else {
call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data);
}
}
if (false === $data || feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
}
}
/**
* Writes data to pipes.
*
* @param Boolean $blocking Whether to use blocking calls or not.
*/
private function writePipes($blocking)
{
if ($this->tty) {
$this->status = self::STATUS_TERMINATED;
return;
}
if (null === $this->stdin) {
fclose($this->pipes[0]);
unset($this->pipes[0]);
return;
}
$writePipes = array($this->pipes[0]);
unset($this->pipes[0]);
$stdinLen = strlen($this->stdin);
$stdinOffset = 0;
while ($writePipes) {
$r = array();
$w = $writePipes;
$e = null;
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::TIMEOUT_PRECISION * 1E6) : 0)) {
// if a system call has been interrupted, forget about it, let's try again
if ($this->hasSystemCallBeenInterrupted()) {
continue;
}
break;
}
// nothing has changed, let's wait until the process is ready
if (0 === $n) {
continue;
}
if ($w) {
$written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192);
if (false !== $written) {
$stdinOffset += $written;
}
if ($stdinOffset >= $stdinLen) {
fclose($writePipes[0]);
$writePipes = null;
}
}
}
}
} }

View File

@ -65,6 +65,23 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertLessThan(1.8, $duration); $this->assertLessThan(1.8, $duration);
} }
public function testCallbacksAreExecutedWithStart()
{
$data = '';
$process = $this->getProcess('echo "foo";sleep 1;echo "foo"');
$process->start(function ($type, $buffer) use (&$data) {
$data .= $buffer;
});
$start = microtime(true);
while ($process->isRunning()) {
usleep(10000);
}
$this->assertEquals("foo\nfoo\n", $data);
}
/** /**
* tests results from sub processes * tests results from sub processes
* *
@ -254,7 +271,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStop() public function testStop()
{ {
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess('php -r "sleep(4);"');
$process->start(); $process->start();
$this->assertTrue($process->isRunning()); $this->assertTrue($process->isRunning());
$process->stop(); $process->stop();
@ -270,7 +287,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIsNotSuccessful() public function testIsNotSuccessful()
{ {
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess('php -r "sleep(4);"');
$process->start(); $process->start();
$this->assertTrue($process->isRunning()); $this->assertTrue($process->isRunning());
$process->stop(); $process->stop();
@ -316,7 +333,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals'); $this->markTestSkipped('Windows does not support POSIX signals');
} }
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess('php -r "sleep(4);"');
$process->start(); $process->start();
$process->stop(); $process->stop();
$this->assertTrue($process->hasBeenSignaled()); $this->assertTrue($process->hasBeenSignaled());
@ -331,7 +348,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// SIGTERM is only defined if pcntl extension is present // SIGTERM is only defined if pcntl extension is present
$termSignal = defined('SIGTERM') ? SIGTERM : 15; $termSignal = defined('SIGTERM') ? SIGTERM : 15;
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess('php -r "sleep(4);"');
$process->start(); $process->start();
$process->stop(); $process->stop();
@ -382,7 +399,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// Sleep doesn't work as it will allow the process to handle signals and close // Sleep doesn't work as it will allow the process to handle signals and close
// file handles from the other end. // file handles from the other end.
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess('php -r "sleep 4"');
$process->start(); $process->start();
// PHP will deadlock when it tries to cleanup $process // PHP will deadlock when it tries to cleanup $process

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\Routing\Exception; namespace Symfony\Component\Routing\Exception;
/** /**
* Exception thrown when a route does not exists * Exception thrown when a route does not exist
* *
* @author Alexandre Salomé <alexandre.salome@gmail.com> * @author Alexandre Salomé <alexandre.salome@gmail.com>
* *

View File

@ -23,7 +23,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
interface AuthenticationManagerInterface interface AuthenticationManagerInterface
{ {
/** /**
* Attempts to authenticates a TokenInterface object. * Attempts to authenticate a TokenInterface object.
* *
* @param TokenInterface $token The TokenInterface instance to authenticate * @param TokenInterface $token The TokenInterface instance to authenticate
* *

View File

@ -45,7 +45,7 @@ class ChainLoaderTest extends \PHPUnit_Framework_TestCase
{ {
$loader = new ProjectTemplateLoader1(array($this->loader1, $this->loader2)); $loader = new ProjectTemplateLoader1(array($this->loader1, $this->loader2));
$this->assertFalse($loader->load(new TemplateReference('bar', 'php')), '->load() returns false if the template is not found'); $this->assertFalse($loader->load(new TemplateReference('bar', 'php')), '->load() returns false if the template is not found');
$this->assertFalse($loader->load(new TemplateReference('foo', 'php')), '->load() returns false if the template does not exists for the given renderer'); $this->assertFalse($loader->load(new TemplateReference('foo', 'php')), '->load() returns false if the template does not exist for the given renderer');
$this->assertInstanceOf( $this->assertInstanceOf(
'Symfony\Component\Templating\Storage\FileStorage', 'Symfony\Component\Templating\Storage\FileStorage',
$loader->load(new TemplateReference('foo.php', 'php')), $loader->load(new TemplateReference('foo.php', 'php')),

View File

@ -61,7 +61,7 @@ class FilesystemLoaderTest extends \PHPUnit_Framework_TestCase
$loader = new ProjectTemplateLoader2($pathPattern); $loader = new ProjectTemplateLoader2($pathPattern);
$loader->setDebugger($debugger = new \Symfony\Component\Templating\Tests\Fixtures\ProjectTemplateDebugger()); $loader->setDebugger($debugger = new \Symfony\Component\Templating\Tests\Fixtures\ProjectTemplateDebugger());
$this->assertFalse($loader->load(new TemplateReference('foo.xml', 'php')), '->load() returns false if the template does not exists for the given engine'); $this->assertFalse($loader->load(new TemplateReference('foo.xml', 'php')), '->load() returns false if the template does not exist for the given engine');
$this->assertTrue($debugger->hasMessage('Failed loading template'), '->load() logs a "Failed loading template" message if the template is not found'); $this->assertTrue($debugger->hasMessage('Failed loading template'), '->load() logs a "Failed loading template" message if the template is not found');
$loader = new ProjectTemplateLoader2(array(self::$fixturesPath.'/null/%name%', $pathPattern)); $loader = new ProjectTemplateLoader2(array(self::$fixturesPath.'/null/%name%', $pathPattern));

View File

@ -31,9 +31,13 @@ class StaticMethodLoader implements LoaderInterface
/** @var \ReflectionClass $reflClass */ /** @var \ReflectionClass $reflClass */
$reflClass = $metadata->getReflectionClass(); $reflClass = $metadata->getReflectionClass();
if (!$reflClass->isInterface() && !$reflClass->isAbstract() && $reflClass->hasMethod($this->methodName)) { if (!$reflClass->isInterface() && $reflClass->hasMethod($this->methodName)) {
$reflMethod = $reflClass->getMethod($this->methodName); $reflMethod = $reflClass->getMethod($this->methodName);
if ($reflMethod->isAbstract()) {
return false;
}
if (!$reflMethod->isStatic()) { if (!$reflMethod->isStatic()) {
throw new MappingException(sprintf('The method %s::%s should be static', $reflClass->name, $this->methodName)); throw new MappingException(sprintf('The method %s::%s should be static', $reflClass->name, $this->methodName));
} }

View File

@ -28,7 +28,7 @@ class GetterMetadataTest extends \PHPUnit_Framework_TestCase
public function testGetPropertyValueFromPublicGetter() public function testGetPropertyValueFromPublicGetter()
{ {
// private getters don't work yet because ReflectionMethod::setAccessible() // private getters don't work yet because ReflectionMethod::setAccessible()
// does not exists yet in a stable PHP release // does not exist yet in a stable PHP release
$entity = new Entity('foobar'); $entity = new Entity('foobar');
$metadata = new GetterMetadata(self::CLASSNAME, 'internal'); $metadata = new GetterMetadata(self::CLASSNAME, 'internal');

View File

@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\Validator\Tests\Mapping\Loader;
use Symfony\Component\Validator\Mapping\ClassMetadata;
abstract class AbstractMethodStaticLoader
{
abstract public static function loadMetadata(ClassMetadata $metadata);
}

View File

@ -66,13 +66,28 @@ class StaticMethodLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertCount(0, $metadata->getConstraints()); $this->assertCount(0, $metadata->getConstraints());
} }
public function testLoadClassMetadataIgnoresAbstractClasses() public function testLoadClassMetadataInAbstractClasses()
{ {
$loader = new StaticMethodLoader('loadMetadata'); $loader = new StaticMethodLoader('loadMetadata');
$metadata = new ClassMetadata(__NAMESPACE__.'\AbstractStaticLoader'); $metadata = new ClassMetadata(__NAMESPACE__.'\AbstractStaticLoader');
$loader->loadClassMetadata($metadata); $loader->loadClassMetadata($metadata);
$this->assertCount(1, $metadata->getConstraints());
}
public function testLoadClassMetadataIgnoresAbstractMethods()
{
$loader = new StaticMethodLoader('loadMetadata');
try {
include __DIR__ . '/AbstractMethodStaticLoader.php';
$this->fail('AbstractMethodStaticLoader should produce a strict standard error.');
} catch (\Exception $e) {
}
$metadata = new ClassMetadata(__NAMESPACE__.'\AbstractMethodStaticLoader');
$loader->loadClassMetadata($metadata);
$this->assertCount(0, $metadata->getConstraints()); $this->assertCount(0, $metadata->getConstraints());
} }
} }
@ -86,6 +101,7 @@ abstract class AbstractStaticLoader
{ {
public static function loadMetadata(ClassMetadata $metadata) public static function loadMetadata(ClassMetadata $metadata)
{ {
$metadata->addConstraint(new ConstraintA());
} }
} }