Problem/Motivation

Drupal makes use of annotations which appear in comments. As such, comments must be preserved by opcode caches. PHP's Opcache, for example, allows you to omit comments in the cached opcode, which will result in errors like this:

$ drush cr
exception 'Doctrine\Common\Annotations\AnnotationException' with message '[Semantical Error] The class "Drupal\Core\Entity\Annotation\ContentEntityType" is not annotated with @Annotation. Are you sure this class  [error]
can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "Drupal\Core\Entity\Annotation\ContentEntityType". If it is indeed no annotation, then you need to add
@IgnoreAnnotation("ContentEntityType") to the _class_ doc comment of class Drupal\aggregator\Entity\Feed.' in
/www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php:54
Stack trace:
#0 /www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php(727): Doctrine\Common\Annotations\AnnotationException::semanticalError('The class "Drup...')
#1 /www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php(641): Doctrine\Common\Annotations\DocParser->Annotation()
#2 /www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php(334): Doctrine\Common\Annotations\DocParser->Annotations()
#3 /www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php(67): Doctrine\Common\Annotations\DocParser->parse('/**\n * Defines ...', 'class Drupal\\ag...')
#4 /www/d8/core/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php(91):
Doctrine\Common\Annotations\SimpleAnnotationReader->getClassAnnotations(Object(Doctrine\Common\Reflection\StaticReflectionClass))
#5 /www/d8/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php(114):
Doctrine\Common\Annotations\SimpleAnnotationReader->getClassAnnotation(Object(Doctrine\Common\Reflection\StaticReflectionClass), 'Drupal\\Core\\Ent...')
#6 /www/d8/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php(90): Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery->getDefinitions()
#7 /www/d8/core/lib/Drupal/Core/Entity/EntityManager.php(229): Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator->getDefinitions()
#8 /www/d8/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php(151): Drupal\Core\Entity\EntityManager->findDefinitions()
#9 /www/d8/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryCachedTrait.php(27): Drupal\Core\Plugin\DefaultPluginManager->getDefinitions()
#10 /www/d8/core/lib/Drupal/Core/Entity/EntityManager.php(250): Drupal\Core\Plugin\DefaultPluginManager->getDefinition('field_storage_c...', false)
#11 /www/d8/core/lib/Drupal/Core/Entity/EntityManager.php(338): Drupal\Core\Entity\EntityManager->getDefinition('field_storage_c...')
#12 /www/d8/core/lib/Drupal/Core/Entity/EntityManager.php(274): Drupal\Core\Entity\EntityManager->getHandler('field_storage_c...', 'storage')
#13 /www/d8/core/includes/entity.inc(189): Drupal\Core\Entity\EntityManager->getStorage('field_storage_c...')
#14 /www/d8/core/modules/field/field.module(148): entity_load_multiple_by_properties('field_storage_c...', Array)
#15 /www/d8/core/lib/Drupal/Core/Extension/ModuleHandler.php(494): field_system_info_alter(Array, Object(Drupal\Core\Extension\Extension), 'module')
#16 /www/d8/core/modules/system/system.module(928): Drupal\Core\Extension\ModuleHandler->alter('system_info', Array, Object(Drupal\Core\Extension\Extension), 'module')
#17 /www/d8/core/modules/system/system.module(986): _system_rebuild_module_data()
#18 /www/d8/core/includes/common.inc(1754): system_rebuild_module_data()
#19 /www/d8/core/includes/utility.inc(50): drupal_flush_all_caches()
#20 /.composer/vendor/drush/drush/commands/core/cache.drush.inc(278): drupal_rebuild(Object(Symfony\Component\ClassLoader\ApcClassLoader), Object(Symfony\Component\HttpFoundation\Request))
#21 [internal function]: drush_cache_rebuild()
#22 /.composer/vendor/drush/drush/includes/command.inc(359): call_user_func_array('drush_cache_reb...', Array)
#23 /.composer/vendor/drush/drush/includes/command.inc(210): _drush_invoke_hooks(Array, Array)
#24 [internal function]: drush_command()
#25 /.composer/vendor/drush/drush/includes/command.inc(178): call_user_func_array('drush_command', Array)
#26 /.composer/vendor/drush/drush/lib/Drush/Boot/DrupalBoot.php(46): drush_dispatch(Array)
#27 /.composer/vendor/drush/drush/drush.php(74): Drush\Boot\DrupalBoot->bootstrap_and_dispatch()
#28 /.composer/vendor/drush/drush/drush.php(11): drush_main()
#29 {main}

The problem is insidious in HEAD, however, because it is not possible to use hook_requirements() to warn someone installing Drupal. Annotations are needed before hook_requirements() has a chance to be called.

Steps to reproduce

  • Add opcache.save_comments=0 into your php 5.5 php.ini
  • Run drush cr

Proposed resolution

Remaining tasks

  • Discuss whether this limitation is okay.

User interface changes

API changes

Comments

dawehner’s picture

Component: base system » system.module

IMHO we have to add a hook_requirements check here, moving to system module for that

cilefen’s picture

Assigned: Unassigned » cilefen
cilefen’s picture

Status: Active » Needs review
StatusFileSize
new1.12 KB

Unfortunately, this doesn't work because annotations are already needed before this hook is invoked.

cilefen’s picture

Assigned: cilefen » Unassigned
dawehner’s picture

Unfortunately, this doesn't work because annotations are already needed before this hook is invoked.

OH wow really? Do you mind explaining what subsystem (I bet users => entity) needs it that early?

cilefen’s picture

StatusFileSize
new641 bytes

This one works.

cilefen’s picture

#6 plus an explaining comment could help for such requirements that are so fundamental that we cannot invoke hook_requirements().

cilefen’s picture

Issue summary: View changes

The summary says:

Discuss whether this limitation is okay

I can see no way around this since annotations are a fundamental part of Drupal now, unless there is some way to parse them into something else.

dawehner’s picture

@cilefen
Do you understand why we need the annotations that early?

dawehner’s picture

And well, the question is why is this actually broken. Don't we scan those files using file_get_contents() and static code parsing, not loading those files in the first place?

chx’s picture

Yes, we do, relevant call chain

              $class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
              $parser = new StaticReflectionParser($class, $finder, TRUE);

              /** @var $annotation \Drupal\Component\Annotation\AnnotationInterface */
              if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(),

and then StaticReflectionParser::parse has the $contents = file_get_contents($fileName); indeed. So: no idea.

Also look at #3 in the stack trace, that's a comment there.

catch’s picture

Priority: Normal » Major
dawehner’s picture

Status: Needs review » Needs work

The tag is a bit confusing, this is not just about performance, its about taking care whether something works ... even I still don't get why we actually need it, given that our annotation parser is as chx described earlier. Having protection for those installations would be nice.

+++ b/core/install.php
@@ -26,6 +26,12 @@
+$opcache_enabled = (function_exists('opcache_get_status') && opcache_get_status()['opcache_enabled']);
+if ($opcache_enabled && !ini_get('opcache.save_comments')) {

IMHO there is no point in the local variable but I don't care ...

cilefen’s picture

Status: Needs work » Needs review
StatusFileSize
new601 bytes

Note that this install.php approach does not trigger in drush installs.

cilefen’s picture

(magic double-post)

chx’s picture

Unless there's a burning desire to show this warning to users in 8.0.1 I would much rather have the new Drupal-PHP crack team (neclimdul and Fabian) poke at this. I hate things we do not understand -- what if there are other bugs due to the non-understood root?

catch’s picture

I'd also rather fix it than add the warning.

We can add a note to drupal.org/requirements and/or the 8.0.0 release notes until it's fixed too.

cilefen’s picture

It is already noted on the requirements.

fabianx’s picture

Status: Needs review » Needs work

The bug is that DocParser.php (Doctrine/Common/Annotations/DocParser.php) does use Reflection just to check that there are annotations on the annotation class:

private function Annotation()

calls

a)

  if ( ! $this->classExists($name)) { (which loads the annotation class)

b)

        if ( ! isset(self::$annotationMetadata[$name])) {
            $this->collectAnnotationMetadata($name);
        }

        // verify that the class is really meant to be an annotation and not just any ordinary class
        if (self::$annotationMetadata[$name]['is_annotation'] === false) {

collectAnnotationMetadata in itself does do:

        $class      = new \ReflectionClass($name);
        $docComment = $class->getDocComment();

which completely circumvents the parsing before ...

Which means:

TL;DR: We load all annotation classes into opcache while parsing them - the exact thing we wanted to avoid with using doctrine. While we do not load _all_ classes, this is still more than we want.

neclimdul’s picture

Gugh... For some other reasons we need to rewrite SimpleAnnotationReader where this is used so I guess that can come along for the ride. Don't have the issue handy but I'll try to dig it up.

chx’s picture

Well, we need the annotation class anyways because that's where the contents of the annotation is going. Nonetheless, we have wanted our own reader for some time now...

But I believe there's nothing better for now than just warning users. Shame.

dawehner’s picture

Yeah at least for now we should add the warning and then maybe figure out whether we can workaround that somehow.

chx’s picture

Status: Needs work » Reviewed & tested by the community

Yay, PHP renaissance!

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

SimpleAnnotationReader seems to be on the way out :( https://github.com/doctrine/annotations/pull/56#issuecomment-96204292

The problem that this patch will also have is what will happen when this flag is switched on an already installed site - just reporting in install.php does not seem sufficient.

dawehner’s picture

Yeah sometimes I don't get how people define collaboration.

cilefen’s picture

What will happen is the site will crash. ;-)

Certainly this issue as-is is not really fixing the problem. The issue is not critical because we documented the requirement for the time being. So, what is the plan?

catch’s picture

Status: Needs review » Reviewed & tested by the community

The problem that this patch will also have is what will happen when this flag is switched on an already installed site - just reporting in install.php does not seem sufficient.

The install will already fail with a fatal error, this just gives more information.

We'll need a follow-up to avoid using reflection for annotations altogether, which was the idea of the original Doctrine patch, but I think adding the warning is a lot better than nothing, and given we don't have an immediate fix for upstream, nothing seems likely for the next patch release or so.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This is a good stop-gap fix. Fixing the annotation reader to not need this is going to take a lot longer and at least people will know what is wrong during installation and https://www.drupal.org/requirements/php has been updated. Committed 42cad40 and pushed to 8.0.x and 8.1.x. Thanks!

  • alexpott committed 71a15fe on 8.1.x
    Issue #2421451 by cilefen, dawehner: Drupal needs comments in opcache
    

  • alexpott committed 42cad40 on
    Issue #2421451 by cilefen, dawehner: Drupal needs comments in opcache
    
    (...
effulgentsia’s picture

Issue tags: +Needs followup

This is a good stop-gap fix.

Do we have an issue open for the proper fix?

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.