Skip to content

Commit

Permalink
Merge branch '7.0' into 7.1
Browse files Browse the repository at this point in the history
* 7.0: (22 commits)
  fix merge
  [AssetMapper] Check asset/vendor directory is writable
  detect wrong e-mail validation modes
  detect wrong usages of minMessage/maxMessage in options
  [Security] Remove workflow from empty folder
  read form values using the chain data accessor
  [Validator] Review Bulgarian (bg) translation
  Reviewed italian translation
  Fix french translation
  call substr() with integer offsets
  [Finder] Also consider .git inside the basedir of in() directory
  review: FR translation
  [Serializer] Add AbstractNormalizerContextBuilder::defaultConstructorArguments()
  Update spanish and catalan translations
  Update AbstractSchemaListener.php to adjust more database params
  Updated id=113 Arabic translation.
  #53771 Updated validator Lithuanian translations
  review: translation RU
  [PropertyInfo] Fix PHPStan properties type in trait
  explicitly cast boolean SSL stream options
  ...
  • Loading branch information
xabbuh committed Apr 28, 2024
2 parents c834064 + ea2eafa commit f738888
Show file tree
Hide file tree
Showing 34 changed files with 332 additions and 150 deletions.
Expand Up @@ -13,6 +13,8 @@

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;

abstract class AbstractSchemaListener
Expand All @@ -22,8 +24,16 @@ abstract public function postGenerateSchema(GenerateSchemaEventArgs $event): voi
protected function getIsSameDatabaseChecker(Connection $connection): \Closure
{
return static function (\Closure $exec) use ($connection): bool {
$schemaManager = $connection->createSchemaManager();

$checkTable = 'schema_subscriber_check_'.bin2hex(random_bytes(7));
$connection->executeStatement(sprintf('CREATE TABLE %s (id INTEGER NOT NULL)', $checkTable));
$table = new Table($checkTable);
$table->addColumn('id', Types::INTEGER)
->setAutoincrement(true)
->setNotnull(true);
$table->setPrimaryKey(['id']);

$schemaManager->createTable($table);

try {
$exec(sprintf('DROP TABLE %s', $checkTable));
Expand All @@ -32,7 +42,7 @@ protected function getIsSameDatabaseChecker(Connection $connection): \Closure
}

try {
$connection->executeStatement(sprintf('DROP TABLE %s', $checkTable));
$schemaManager->dropTable($checkTable);

return false;
} catch (TableNotFoundException) {
Expand Down
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\AssetMapper\ImportMap;

use Symfony\Component\AssetMapper\Exception\RuntimeException;

/**
* Manages the local storage of remote/vendor importmap packages.
*/
Expand Down Expand Up @@ -52,7 +54,9 @@ public function save(ImportMapEntry $entry, string $contents): void
$vendorPath = $this->getDownloadPath($entry->packageModuleSpecifier, $entry->type);

@mkdir(\dirname($vendorPath), 0777, true);
file_put_contents($vendorPath, $contents);
if (false === @file_put_contents($vendorPath, $contents)) {
throw new RuntimeException(error_get_last()['message'] ?? sprintf('Failed to write file "%s".', $vendorPath));
}
}

public function saveExtraFile(ImportMapEntry $entry, string $extraFilename, string $contents): void
Expand All @@ -64,7 +68,9 @@ public function saveExtraFile(ImportMapEntry $entry, string $extraFilename, stri
$vendorPath = $this->getExtraFileDownloadPath($entry, $extraFilename);

@mkdir(\dirname($vendorPath), 0777, true);
file_put_contents($vendorPath, $contents);
if (false === @file_put_contents($vendorPath, $contents)) {
throw new RuntimeException(error_get_last()['message'] ?? sprintf('Failed to write file "%s".', $vendorPath));
}
}

/**
Expand Down
Expand Up @@ -201,15 +201,15 @@ public static function getRequirePackageTests(): iterable
];

yield 'single_package_with_a_path' => [
'packages' => [new PackageRequireOptions('some/module', path: self::$writableRoot.'/assets/some_file.js')],
'expectedProviderPackageArgumentCount' => 0,
'resolvedPackages' => [],
'expectedImportMap' => [
'some/module' => [
// converted to relative path
'path' => './assets/some_file.js',
'packages' => [new PackageRequireOptions('some/module', path: self::$writableRoot.'/assets/some_file.js')],
'expectedProviderPackageArgumentCount' => 0,
'resolvedPackages' => [],
'expectedImportMap' => [
'some/module' => [
// converted to relative path
'path' => './assets/some_file.js',
],
],
],
];
}

Expand Down
Expand Up @@ -25,7 +25,7 @@ class RemotePackageStorageTest extends TestCase
protected function setUp(): void
{
$this->filesystem = new Filesystem();
if (!file_exists(self::$writableRoot)) {
if (!$this->filesystem->exists(self::$writableRoot)) {
$this->filesystem->mkdir(self::$writableRoot);
}
}
Expand All @@ -41,14 +41,30 @@ public function testGetStorageDir()
$this->assertSame(realpath(self::$writableRoot.'/assets/vendor'), realpath($storage->getStorageDir()));
}

public function testSaveThrowsWhenVendorDirectoryIsNotWritable()
{
$this->filesystem->mkdir($vendorDir = self::$writableRoot.'/assets/acme/vendor');
$this->filesystem->chmod($vendorDir, 0555);

$storage = new RemotePackageStorage($vendorDir);
$entry = ImportMapEntry::createRemote('foo', ImportMapType::JS, '/does/not/matter', '1.0.0', 'module_specifier', false);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('file_put_contents('.$vendorDir.'/module_specifier/module_specifier.index.js): Failed to open stream: No such file or directory');
$storage->save($entry, 'any content');

$this->filesystem->remove($vendorDir);
}

public function testIsDownloaded()
{
$storage = new RemotePackageStorage(self::$writableRoot.'/assets/vendor');
$entry = ImportMapEntry::createRemote('foo', ImportMapType::JS, '/does/not/matter', '1.0.0', 'module_specifier', false);
$this->assertFalse($storage->isDownloaded($entry));

$targetPath = self::$writableRoot.'/assets/vendor/module_specifier/module_specifier.index.js';
@mkdir(\dirname($targetPath), 0777, true);
file_put_contents($targetPath, 'any content');
$this->filesystem->mkdir(\dirname($targetPath));
$this->filesystem->dumpFile($targetPath, 'any content');
$this->assertTrue($storage->isDownloaded($entry));
}

Expand All @@ -57,9 +73,10 @@ public function testIsExtraFileDownloaded()
$storage = new RemotePackageStorage(self::$writableRoot.'/assets/vendor');
$entry = ImportMapEntry::createRemote('foo', ImportMapType::JS, '/does/not/matter', '1.0.0', 'module_specifier', false);
$this->assertFalse($storage->isExtraFileDownloaded($entry, '/path/to/extra.woff'));

$targetPath = self::$writableRoot.'/assets/vendor/module_specifier/path/to/extra.woff';
@mkdir(\dirname($targetPath), 0777, true);
file_put_contents($targetPath, 'any content');
$this->filesystem->mkdir(\dirname($targetPath));
$this->filesystem->dumpFile($targetPath, 'any content');
$this->assertTrue($storage->isExtraFileDownloaded($entry, '/path/to/extra.woff'));
}

Expand Down Expand Up @@ -92,7 +109,7 @@ public function testGetDownloadedPath(string $packageModuleSpecifier, ImportMapT
$this->assertSame($expectedPath, $storage->getDownloadPath($packageModuleSpecifier, $importMapType));
}

public static function getDownloadPathTests()
public static function getDownloadPathTests(): iterable
{
yield 'javascript bare package' => [
'packageModuleSpecifier' => 'foo',
Expand Down
Expand Up @@ -37,9 +37,9 @@ public function __construct(\Iterator $iterator, string $baseDir)
{
$this->baseDir = $this->normalizePath($baseDir);

foreach ($this->parentDirectoriesUpwards($this->baseDir) as $parentDirectory) {
if (@is_dir("{$parentDirectory}/.git")) {
$this->baseDir = $parentDirectory;
foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) {
if (@is_dir("{$directory}/.git")) {
$this->baseDir = $directory;
break;
}
}
Expand Down
Expand Up @@ -34,7 +34,7 @@ protected function tearDown(): void
*
* @dataProvider getAcceptData
*/
public function testAccept(array $gitIgnoreFiles, array $otherFileNames, array $expectedResult)
public function testAccept(array $gitIgnoreFiles, array $otherFileNames, array $expectedResult, string $baseDir = '')
{
$otherFileNames = $this->toAbsolute($otherFileNames);
foreach ($otherFileNames as $path) {
Expand All @@ -51,7 +51,8 @@ public function testAccept(array $gitIgnoreFiles, array $otherFileNames, array $

$inner = new InnerNameIterator($otherFileNames);

$iterator = new VcsIgnoredFilterIterator($inner, $this->tmpDir);
$baseDir = $this->tmpDir.('' !== $baseDir ? '/'.$baseDir : '');
$iterator = new VcsIgnoredFilterIterator($inner, $baseDir);

$this->assertIterator($this->toAbsolute($expectedResult), $iterator);
}
Expand All @@ -74,6 +75,55 @@ public static function getAcceptData(): iterable
],
];

yield 'simple file - .gitignore and in() from repository root' => [
[
'.gitignore' => 'a.txt',
],
[
'.git',
'a.txt',
'b.txt',
'dir/',
'dir/a.txt',
],
[
'.git',
'b.txt',
'dir',
],
];

yield 'nested git repositories only consider .gitignore files of the most inner repository' => [
[
'.gitignore' => "nested/*\na.txt",
'nested/.gitignore' => 'c.txt',
'nested/dir/.gitignore' => 'f.txt',
],
[
'.git',
'a.txt',
'b.txt',
'nested/',
'nested/.git',
'nested/c.txt',
'nested/d.txt',
'nested/dir/',
'nested/dir/e.txt',
'nested/dir/f.txt',
],
[
'.git',
'a.txt',
'b.txt',
'nested',
'nested/.git',
'nested/d.txt',
'nested/dir',
'nested/dir/e.txt',
],
'nested',
];

yield 'simple file at root' => [
[
'.gitignore' => '/a.txt',
Expand Down
Expand Up @@ -12,7 +12,9 @@
namespace Symfony\Component\Form\Extension\Core\DataAccessor;

use Symfony\Component\Form\DataAccessorInterface;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\AccessException;
use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\Exception\AccessException as PropertyAccessException;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
Expand Down Expand Up @@ -52,15 +54,25 @@ public function setValue(object|array &$data, mixed $value, FormInterface $form)
throw new AccessException('Unable to write the given value as no property path is defined.');
}

$getValue = function () use ($data, $form, $propertyPath) {
$dataMapper = $this->getDataMapper($form);

if ($dataMapper instanceof DataMapper && null !== $dataAccessor = $dataMapper->getDataAccessor()) {
return $dataAccessor->getValue($data, $form);
}

return $this->getPropertyValue($data, $propertyPath);
};

// If the field is of type DateTimeInterface and the data is the same skip the update to
// keep the original object hash
if ($value instanceof \DateTimeInterface && $value == $this->getPropertyValue($data, $propertyPath)) {
if ($value instanceof \DateTimeInterface && $value == $getValue()) {
return;
}

// If the data is identical to the value in $data, we are
// dealing with a reference
if (!\is_object($data) || !$form->getConfig()->getByReference() || $value !== $this->getPropertyValue($data, $propertyPath)) {
if (!\is_object($data) || !$form->getConfig()->getByReference() || $value !== $getValue()) {
try {
$this->propertyAccessor->setValue($data, $propertyPath, $value);
} catch (NoSuchPropertyException $e) {
Expand Down Expand Up @@ -98,4 +110,13 @@ private function getPropertyValue(object|array $data, PropertyPathInterface $pro
return null;
}
}

private function getDataMapper(FormInterface $form): ?DataMapperInterface
{
do {
$dataMapper = $form->getConfig()->getDataMapper();
} while (null === $dataMapper && null !== $form = $form->getParent());

return $dataMapper;
}
}
Expand Up @@ -74,4 +74,12 @@ public function mapFormsToData(\Traversable $forms, mixed &$data): void
}
}
}

/**
* @internal
*/
public function getDataAccessor(): DataAccessorInterface
{
return $this->dataAccessor;
}
}
Expand Up @@ -16,6 +16,8 @@
use Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor;
use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfigBuilder;
use Symfony\Component\Form\FormFactoryBuilder;
Expand Down Expand Up @@ -403,6 +405,25 @@ public function testMapFormsToDataMapsDateTimeInstanceToArrayIfNotSetBefore()

$this->assertEquals(['date' => new \DateTime('2022-08-04', new \DateTimeZone('UTC'))], $form->getData());
}

public function testMapFormToDataWithOnlyGetterConfigured()
{
$person = new DummyPerson('foo');
$form = (new FormFactoryBuilder())
->getFormFactory()
->createBuilder(FormType::class, $person)
->add('name', TextType::class, [
'getter' => function (DummyPerson $person) {
return $person->myName();
},
])
->getForm();
$form->submit([
'name' => 'bar',
]);

$this->assertSame('bar', $person->myName());
}
}

class SubmittedForm extends Form
Expand Down Expand Up @@ -439,4 +460,9 @@ public function rename($name): void
{
$this->name = $name;
}

public function setName($name): void
{
$this->name = $name;
}
}
Expand Up @@ -94,6 +94,22 @@ public function __construct(array $options, \Redis|Relay|\RedisCluster|null $red
throw new InvalidArgumentException('Cannot configure Redis Sentinel and Redis Cluster instance at the same time.');
}

$booleanStreamOptions = [
'allow_self_signed',
'capture_peer_cert',
'capture_peer_cert_chain',
'disable_compression',
'SNI_enabled',
'verify_peer',
'verify_peer_name',
];

foreach ($options['ssl'] ?? [] as $streamOption => $value) {
if (\in_array($streamOption, $booleanStreamOptions, true) && \is_string($value)) {
$options['ssl'][$streamOption] = filter_var($value, \FILTER_VALIDATE_BOOL);
}
}

if ((\is_array($host) && null === $sentinelMaster) || $redis instanceof \RedisCluster) {
$hosts = \is_string($host) ? [$host.':'.$port] : $host; // Always ensure we have an array
$this->redis = static fn () => self::initializeRedisCluster($redis, $hosts, $auth, $options);
Expand Down
Expand Up @@ -314,6 +314,15 @@ private function getDocBlockFromProperty(string $class, string $property): ?arra
return null;
}

$reflector = $reflectionProperty->getDeclaringClass();

foreach ($reflector->getTraits() as $trait) {
if ($trait->hasProperty($property)) {
return $this->getDocBlockFromProperty($trait->getName(), $property);
}

}

// Type can be inside property docblock as `@var`
$rawDocNode = $reflectionProperty->getDocComment();
$phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null;
Expand Down

0 comments on commit f738888

Please sign in to comment.