Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sever ties to common #10998

Merged
merged 1 commit into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Upgrade to 3.0

## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader`

Make sure to use the former when writing a type declaration or an `instanceof` check.

## Minor BC BREAK: Changed order of arguments passed to `OneToOne`, `ManyToOne` and `Index` mapping PHP attributes

To keep PHP mapping attributes consistent, order of arguments passed to above attributes has been changed
Expand Down Expand Up @@ -328,10 +332,6 @@ Use `Doctrine\Persistence\Mapping\Driver\StaticPHPDriver` and
The second argument to `UnderscoreNamingStrategy::__construct()` was dropped,
the strategy can no longer be unaware of numbers.

## BC BREAK: Remove `Doctrine\ORM\Proxy\Autoloader`

Use `Doctrine\Common\Proxy\Autoloader` instead.

## BC BREAK: Remove `Doctrine\ORM\Tools\DisconnectedClassMetadataFactory`

No replacement is provided.
Expand Down
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/collections": "^2.1",
"doctrine/common": "^3.3",
greg0ire marked this conversation as resolved.
Show resolved Hide resolved
"doctrine/dbal": "^3.6 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
Expand Down
86 changes: 86 additions & 0 deletions lib/Doctrine/ORM/Proxy/Autoloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Proxy;

use Closure;

use function file_exists;
use function ltrim;
use function spl_autoload_register;
use function str_replace;
use function strlen;
use function strpos;
use function substr;

use const DIRECTORY_SEPARATOR;

/**
* Special Autoloader for Proxy classes, which are not PSR-0 compliant.
*/
final class Autoloader
{
/**
* Resolves proxy class name to a filename based on the following pattern.
*
* 1. Remove Proxy namespace from class name.
* 2. Remove namespace separators from remaining class name.
* 3. Return PHP filename from proxy-dir with the result from 2.
*
* @psalm-param class-string $className
*
* @throws NotAProxyClass
*/
public static function resolveFile(string $proxyDir, string $proxyNamespace, string $className): string
{
if (strpos($className, $proxyNamespace) !== 0) {
throw new NotAProxyClass($className, $proxyNamespace);

Check warning on line 38 in lib/Doctrine/ORM/Proxy/Autoloader.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Proxy/Autoloader.php#L38

Added line #L38 was not covered by tests
}

// remove proxy namespace from class name
$classNameRelativeToProxyNamespace = substr($className, strlen($proxyNamespace));

// remove namespace separators from remaining class name
$fileName = str_replace('\\', '', $classNameRelativeToProxyNamespace);

return $proxyDir . DIRECTORY_SEPARATOR . $fileName . '.php';
}

/**
* Registers and returns autoloader callback for the given proxy dir and namespace.
*
* @param Closure(string, string, class-string): void|null $notFoundCallback Invoked when the proxy file is not found.
*
* @return Closure(string): void
*/
public static function register(
string $proxyDir,
string $proxyNamespace,
Closure|null $notFoundCallback = null,
): Closure {
$proxyNamespace = ltrim($proxyNamespace, '\\');

$autoloader = /** @param class-string $className */ static function (string $className) use ($proxyDir, $proxyNamespace, $notFoundCallback): void {
if ($proxyNamespace === '') {
return;

Check warning on line 66 in lib/Doctrine/ORM/Proxy/Autoloader.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Proxy/Autoloader.php#L66

Added line #L66 was not covered by tests
}

if (strpos($className, $proxyNamespace) !== 0) {
return;

Check warning on line 70 in lib/Doctrine/ORM/Proxy/Autoloader.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Proxy/Autoloader.php#L70

Added line #L70 was not covered by tests
}

$file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);

if ($notFoundCallback && ! file_exists($file)) {
$notFoundCallback($proxyDir, $proxyNamespace, $className);
}

require $file;
};

spl_autoload_register($autoloader);

return $autoloader;
}
}
22 changes: 22 additions & 0 deletions lib/Doctrine/ORM/Proxy/NotAProxyClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Proxy;

use Doctrine\ORM\Exception\ORMException;
use InvalidArgumentException;

use function sprintf;

final class NotAProxyClass extends InvalidArgumentException implements ORMException
{
public function __construct(string $className, string $proxyNamespace)

Check warning on line 14 in lib/Doctrine/ORM/Proxy/NotAProxyClass.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Proxy/NotAProxyClass.php#L14

Added line #L14 was not covered by tests
{
parent::__construct(sprintf(
'The class "%s" is not part of the proxy namespace "%s"',
$className,
$proxyNamespace,
));

Check warning on line 20 in lib/Doctrine/ORM/Proxy/NotAProxyClass.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Proxy/NotAProxyClass.php#L16-L20

Added lines #L16 - L20 were not covered by tests
}
}
14 changes: 14 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,20 @@
<code>string</code>
</MoreSpecificReturnType>
</file>
<file src="lib/Doctrine/ORM/Proxy/Autoloader.php">
<ArgumentTypeCoercion>
<code>$autoloader</code>
</ArgumentTypeCoercion>
<LessSpecificReturnStatement>
<code>$autoloader</code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code>Closure(string): void</code>
</MoreSpecificReturnType>
<UnresolvableInclude>
<code>require $file</code>
</UnresolvableInclude>
</file>
<file src="lib/Doctrine/ORM/Proxy/ProxyFactory.php">
<ArgumentTypeCoercion>
<code>$classMetadata</code>
Expand Down
57 changes: 57 additions & 0 deletions tests/Doctrine/Tests/Proxy/AutoloaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Proxy;

use Doctrine\ORM\Proxy\Autoloader;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

use function class_exists;
use function file_exists;
use function file_put_contents;
use function sys_get_temp_dir;
use function unlink;

use const DIRECTORY_SEPARATOR;

class AutoloaderTest extends TestCase
{
/** @return iterable<string, array{string, string, class-string, string}> */
public static function dataResolveFile(): iterable
{
return [
['/tmp', 'MyProxy', 'MyProxy\RealClass', '/tmp' . DIRECTORY_SEPARATOR . 'RealClass.php'],
['/tmp', 'MyProxy', 'MyProxy\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'],
['/tmp', 'MyProxy\Subdir', 'MyProxy\Subdir\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'],
['/tmp', 'MyProxy', 'MyProxy\__CG__\Other\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__OtherRealClass.php'],
];
}

/** @param class-string $className */
#[DataProvider('dataResolveFile')]
public function testResolveFile(
string $proxyDir,
string $proxyNamespace,
string $className,
string $expectedProxyFile,
): void {
$actualProxyFile = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className);
self::assertEquals($expectedProxyFile, $actualProxyFile);
}

public function testAutoload(): void
{
if (file_exists(sys_get_temp_dir() . '/AutoloaderTestClass.php')) {
unlink(sys_get_temp_dir() . '/AutoloaderTestClass.php');
}

$autoloader = Autoloader::register(sys_get_temp_dir(), 'ProxyAutoloaderTest', static function ($proxyDir, $proxyNamespace, $className): void {
file_put_contents(sys_get_temp_dir() . '/AutoloaderTestClass.php', '<?php namespace ProxyAutoloaderTest; class AutoloaderTestClass {} ');
});

self::assertTrue(class_exists('ProxyAutoloaderTest\AutoloaderTestClass', true));
unlink(sys_get_temp_dir() . '/AutoloaderTestClass.php');
}
}