Skip to content

Commit

Permalink
Merge pull request #23 from intaro/attributes-v3
Browse files Browse the repository at this point in the history
Drop PHP 7.4, 8.0 and Symfony 4.4 support + migrate from annotations to attributes
  • Loading branch information
muxx authored Sep 16, 2024
2 parents 209d359 + ef6c069 commit 34ff40f
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 312 deletions.
14 changes: 4 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,12 @@ jobs:
fail-fast: false
matrix:
php-version:
- '7.4'
- '8.0'
- '8.1'
- '8.2'
- '8.3'
symfony-version:
- '4.4.*'
- '5.4.*'
- '6.3.*'
exclude:
- php-version: '7.4'
symfony-version: '6.3.*'
- php-version: '8.0'
symfony-version: '6.3.*'
coverage: [ 'none' ]
steps:
- name: "Checkout"
Expand Down Expand Up @@ -53,9 +47,9 @@ jobs:
fail-fast: false
matrix:
php-version:
- '7.4'
- '8.0'
- '8.1'
- '8.2'
- '8.3'
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
Expand Down
2 changes: 2 additions & 0 deletions Annotation/Sandbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Intaro\TwigSandboxBundle\Annotation;

/**
* @deprecated left only for automatic conversion of annotations to attributes
*
* @Annotation
* @Target({"METHOD", "PROPERTY"})
*/
Expand Down
16 changes: 16 additions & 0 deletions Attribute/Sandbox.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Intaro\TwigSandboxBundle\Attribute;

#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
class Sandbox
{
public function __construct(
/**
* Type of returned value of target to which annotation applied
* List of allowed types is defined in `intaro.twig_sandbox.sandbox_annotation.value_types` parameter
*/
public string $type = 'string'
) {
}
}
9 changes: 3 additions & 6 deletions Builder/EnvironmentBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class EnvironmentBuilder implements WarmableInterface
/**
* @param array<string, mixed> $options
*/
public function __construct(LoaderInterface $loader, DumperInterface $dumper, SecurityPolicy $policy = null, array $options = [])
public function __construct(LoaderInterface $loader, DumperInterface $dumper, ?SecurityPolicy $policy = null, array $options = [])
{
$this->loader = $loader;
$this->policy = $policy;
Expand Down Expand Up @@ -65,7 +65,7 @@ public function addExtensions(?array $extensions = null): void
*
* @param array<string, mixed> $params
*/
public function getSandboxEnvironment(array $params = [], SecurityPolicy $securityPolicy = null): TwigAdapter
public function getSandboxEnvironment(array $params = [], ?SecurityPolicy $securityPolicy = null): TwigAdapter
{
$loader = new ArrayLoader();
$twig = new Environment($loader, $params);
Expand Down Expand Up @@ -162,10 +162,7 @@ public function getPolicyRules(): SecurityPolicyRules
return $this->rules;
}

/**
* @param string $cacheDir
*/
public function warmUp(/*string */ $cacheDir/*, ?string $buildDir = null*/)/*: array*/
public function warmUp(string $cacheDir/* , ?string $buildDir = null */)/* : array */
{
$currentDir = $this->options['cache_dir'];

Expand Down
4 changes: 1 addition & 3 deletions Builder/TwigAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ public function __construct(Environment $twigEnvironment)

/**
* @param mixed[] $args
*
* @return mixed
*/
public function __call(string $method, array $args)
public function __call(string $method, array $args): mixed
{
if (method_exists($this, $method)) {
return $this->$method(...$args);
Expand Down
5 changes: 1 addition & 4 deletions CacheWarmer/TwigSandboxCacheWarmer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ public function __construct(EnvironmentBuilder $builder)
$this->environmentBuilder = $builder;
}

/**
* @param string $cacheDir
*/
public function warmUp(/*string */ $cacheDir/*, ?string $buildDir = null*/)/*: array*/
public function warmUp(string $cacheDir/* , ?string $buildDir = null */)/* : array */
{
return $this->environmentBuilder->warmUp($cacheDir);
}
Expand Down
13 changes: 1 addition & 12 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,8 @@
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritDoc}
*/
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('intaro_twig_sandbox');
if (\method_exists($treeBuilder, 'getRootNode')) {
$rootNode = $treeBuilder->getRootNode();
} else {
// BC layer for symfony/config 4.1 and older
$rootNode = $treeBuilder->root('intaro_twig_sandbox');
}

return $treeBuilder;
return new TreeBuilder('intaro_twig_sandbox');
}
}
69 changes: 20 additions & 49 deletions Loader/AnnotationClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,58 @@

namespace Intaro\TwigSandboxBundle\Loader;

use Doctrine\Common\Annotations\Reader;
use Intaro\TwigSandboxBundle\Attribute\Sandbox;
use Intaro\TwigSandboxBundle\SecurityPolicy\SecurityPolicyRules;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Loader\LoaderResolverInterface;
use Symfony\Component\Config\Resource\FileResource;

class AnnotationClassLoader implements LoaderInterface
{
protected Reader $reader;
protected string $annotationClass = 'Intaro\\TwigSandboxBundle\\Annotation\\Sandbox';

public function __construct(Reader $reader)
{
$this->reader = $reader;
}

/**
* Sets the annotation class to read properties from.
*
* @param string $class A fully-qualified class name
* @param string $resource A class name
*/
public function setAnnotationClass(string $class): void
public function load(mixed $resource, ?string $type = null): SecurityPolicyRules
{
$this->annotationClass = $class;
}

/**
* Loads from annotations from a class.
*
* @param string $class A class name
* @param string $type The resource type
*
* @return SecurityPolicyRules A Rules instance
*
* @throws \InvalidArgumentException When annotations can't be parsed
*/
public function load($class, $type = null): SecurityPolicyRules
{
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
if (!class_exists($resource)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $resource));
}

$class = new \ReflectionClass($class);
$class = new \ReflectionClass($resource);
if ($class->isAbstract()) {
throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class));
throw new \InvalidArgumentException(sprintf('Attributes from the class "%s" cannot be read as it is abstract.', $resource));
}

$rules = new SecurityPolicyRules();
$rules->addResource(new FileResource((string) $class->getFileName()));

foreach ($class->getMethods() as $method) {
foreach ($this->reader->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $this->annotationClass) {
$methodName = $method->getName();
$rules->addMethod($class->getName(), $methodName);
}
if (!count($method->getAttributes(Sandbox::class, \ReflectionAttribute::IS_INSTANCEOF))) {
continue;
}

$rules->addMethod($class->getName(), $method->getName());
}

foreach ($class->getProperties() as $property) {
foreach ($this->reader->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $this->annotationClass) {
$rules->addProperty($class->getName(), $property->getName());
}
if (!count($property->getAttributes(Sandbox::class, \ReflectionAttribute::IS_INSTANCEOF))) {
continue;
}

$rules->addProperty($class->getName(), $property->getName());
}

return $rules;
}

/**
* @param ?string $type
*/
public function supports($resource, $type = null): bool
public function supports(mixed $resource, ?string $type = null): bool
{
return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type);
return
is_string($resource)
&& preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource)
&& (!$type || in_array($type, ['annotation', 'attribute'], true));
}

/**
* {@inheritdoc}
*/
public function setResolver(LoaderResolverInterface $resolver): void
{
}
Expand Down
38 changes: 16 additions & 22 deletions Loader/AnnotationDirectoryLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,28 @@
class AnnotationDirectoryLoader extends AnnotationFileLoader
{
/**
* Loads from annotations from a directory.
*
* @param string $path A directory path
* @param ?string $type The resource type
*
* @return SecurityPolicyRules A Rules instance
*
* @throws \InvalidArgumentException When annotations can't be parsed
* @param string $resource A directory path
*/
public function load($path, $type = null): SecurityPolicyRules
public function load(mixed $resource, ?string $type = null): SecurityPolicyRules
{
$dir = $this->locator->locate($path);
$dir = $this->locator->locate($resource);

$rules = new SecurityPolicyRules();
$rules->addResource(new DirectoryResource($dir, '/\.php$/'));
$files = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY));
usort($files, function (\SplFileInfo $a, \SplFileInfo $b) {
return (string) $a > (string) $b ? 1 : -1;
});
usort(
$files,
static fn (\SplFileInfo $a, \SplFileInfo $b) => (string) $a > (string) $b ? 1 : -1
);

foreach ($files as $file) {
if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) {
if (!$file->isFile() || !str_ends_with($file->getFilename(), '.php')) {
continue;
}

if ($class = $this->findClass($file)) {
$refl = new \ReflectionClass($class);
if ($refl->isAbstract()) {
$r = new \ReflectionClass($class);
if ($r->isAbstract()) {
continue;
}

Expand All @@ -46,17 +40,17 @@ public function load($path, $type = null): SecurityPolicyRules
return $rules;
}

/**
* @param ?string $type
*/
public function supports($resource, $type = null): bool
public function supports(mixed $resource, ?string $type = null): bool
{
try {
$path = $this->locator->locate($resource);
} catch (\Exception $e) {
} catch (\Exception) {
return false;
}

return is_string($resource) && is_dir($path) && (!$type || 'annotation' === $type);
return
is_string($resource)
&& is_dir($path)
&& (!$type || in_array($type, ['annotation', 'attribute'], true));
}
}
41 changes: 14 additions & 27 deletions Loader/AnnotationFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ class AnnotationFileLoader extends FileLoader
{
protected AnnotationClassLoader $loader;

/**
* Constructor.
*
* @param FileLocator $locator A FileLocator instance
* @param AnnotationClassLoader $loader An AnnotationClassLoader instance
*/
public function __construct(FileLocator $locator, AnnotationClassLoader $loader, string $env = null)
public function __construct(FileLocator $locator, AnnotationClassLoader $loader, ?string $env = null)
{
if (!function_exists('token_get_all')) {
throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.');
Expand All @@ -29,18 +23,11 @@ public function __construct(FileLocator $locator, AnnotationClassLoader $loader,
}

/**
* Loads from annotations from a file.
*
* @param string $file A PHP file path
* @param ?string $type The resource type
*
* @return SecurityPolicyRules A Rules instance
*
* @throws \InvalidArgumentException When annotations can't be parsed
* @param string $resource A PHP file path
*/
public function load($file, $type = null): SecurityPolicyRules
public function load(mixed $resource, ?string $type = null): SecurityPolicyRules
{
$path = $this->locator->locate($file);
$path = $this->locator->locate($resource);

$rules = new SecurityPolicyRules();
if ($class = $this->findClass($path)) {
Expand All @@ -51,22 +38,20 @@ public function load($file, $type = null): SecurityPolicyRules
return $rules;
}

/**
* @param ?string $type
*/
public function supports($resource, $type = null): bool
public function supports(mixed $resource, ?string $type = null): bool
{
return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
return
is_string($resource)
&& 'php' === pathinfo($resource, PATHINFO_EXTENSION)
&& (!$type || in_array($type, ['annotation', 'attribute'], true));
}

/**
* Returns the full class name for the first class in the file.
*
* @param string $file A PHP file path
*
* @return class-string|false Full class name if found, false otherwise
* @return class-string|false
*/
protected function findClass(string $file)
protected function findClass(string $file): string|false
{
$class = false;
$namespace = false;
Expand Down Expand Up @@ -117,7 +102,9 @@ protected function findClass(string $file)
if (\T_DOUBLE_COLON === $tokens[$j][0] || \T_NEW === $tokens[$j][0]) {
$skipClassToken = true;
break;
} elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) {
}

if (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) {
break;
}
}
Expand Down
Loading

0 comments on commit 34ff40f

Please sign in to comment.