Subject: [PATCH] Fix "access array offset on value of type null"
From: Michele Locati <[email-masked]>
Date: Fri, 4 Dec 2020 12:19:31 +0100

Backport of https://github.com/doctrine/orm/pull/7785

--- a/lib/Doctrine/ORM/PersistentCollection.php
+++ b/lib/Doctrine/ORM/PersistentCollection.php
@@ -448,7 +448,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
      */
     public function count()
     {
-        if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
+        if (! $this->initialized && $this->association !== null && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
             $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
 
             return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);

--- a/lib/Doctrine/ORM/Query/Parser.php
+++ b/lib/Doctrine/ORM/Query/Parser.php
@@ -461,7 +461,7 @@ class Parser
     public function semanticalError($message = '', $token = null)
     {
         if ($token === null) {
-            $token = $this->lexer->lookahead;
+            $token = isset($this->lexer->lookahead) ? $this->lexer->lookahead : ['position' => null];
         }
 
         // Minimum exposed chars ahead of token
@@ -528,7 +528,7 @@ class Parser
      */
     private function isMathOperator($token)
     {
-        return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY));
+        return $token !== null && in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY));
     }
 
     /**
@@ -543,7 +543,7 @@ class Parser
 
         $this->lexer->resetPeek();
 
-        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
+        return $lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
     }
 
     /**
@@ -838,7 +838,7 @@ class Parser
     {
         $this->lexer->moveNext();
 
-        switch ($this->lexer->lookahead['type']) {
+        switch (isset($this->lexer->lookahead['type']) ? $this->lexer->lookahead['type'] : null) {
             case Lexer::T_SELECT:
                 $statement = $this->SelectStatement();
                 break;
@@ -1437,7 +1437,7 @@ class Parser
         // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
         $glimpse = $this->lexer->glimpse();
 
-        if ($glimpse['type'] === Lexer::T_DOT) {
+        if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
             return $this->SingleValuedPathExpression();
         }
 
@@ -1481,7 +1481,7 @@ class Parser
                 $expr = $this->SimpleArithmeticExpression();
                 break;
 
-            case ($glimpse['type'] === Lexer::T_DOT):
+            case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
                 $expr = $this->SingleValuedPathExpression();
                 break;
 
@@ -2451,10 +2451,11 @@ class Parser
 
         // Peek beyond the matching closing parenthesis ')'
         $peek = $this->peekBeyondClosingParenthesis();
-
-        if (in_array($peek['value'], array("=",  "<", "<=", "<>", ">", ">=", "!=")) ||
+        if ($peek !== null && (
+            in_array($peek['value'], array("=",  "<", "<=", "<>", ">", ">=", "!=")) ||
             in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) ||
-            $this->isMathOperator($peek)) {
+            $this->isMathOperator($peek)
+        )) {
             $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
 
             return $condPrimary;
@@ -2806,11 +2807,11 @@ class Parser
             case Lexer::T_IDENTIFIER:
                 $peek = $this->lexer->glimpse();
 
-                if ($peek['value'] == '(') {
+                if ($peek !== null && $peek['value'] == '(') {
                     return $this->FunctionDeclaration();
                 }
 
-                if ($peek['value'] == '.') {
+                if ($peek !== null && $peek['value'] == '.') {
                     return $this->SingleValuedPathExpression();
                 }
 
@@ -2826,7 +2827,7 @@ class Parser
             default:
                 $peek = $this->lexer->glimpse();
 
-                if ($peek['value'] == '(') {
+                if ($peek !== null && $peek['value'] == '(') {
                     if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
                         return $this->AggregateExpression();
                     }
@@ -3167,7 +3168,7 @@ class Parser
 
         $escapeChar = null;
 
-        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
+        if ($this->lexer->lookahead !== null && $this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
             $this->match(Lexer::T_ESCAPE);
             $this->match(Lexer::T_STRING);
 
--- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php
+++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php
@@ -48,11 +48,12 @@ class YamlExporter extends AbstractExporter
         } else {
             $array['type'] = 'entity';
         }
+        $metadataTable = isset($metadata->table) ? $metadata->table : ['name' => null];
 
-        $array['table'] = $metadata->table['name'];
+        $array['table'] = $metadataTable['name'];
 
-        if (isset($metadata->table['schema'])) {
-            $array['schema'] = $metadata->table['schema'];
+        if (isset($metadataTable['schema'])) {
+            $array['schema'] = $metadataTable['schema'];
         }
 
         $inheritanceType = $metadata->inheritanceType;
@@ -73,20 +74,20 @@ class YamlExporter extends AbstractExporter
             $array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
         }
 
-        if (isset($metadata->table['indexes'])) {
-            $array['indexes'] = $metadata->table['indexes'];
+        if (isset($metadataTable['indexes'])) {
+            $array['indexes'] = $metadataTable['indexes'];
         }
 
         if ($metadata->customRepositoryClassName) {
             $array['repositoryClass'] = $metadata->customRepositoryClassName;
         }
 
-        if (isset($metadata->table['uniqueConstraints'])) {
-            $array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
+        if (isset($metadataTable['uniqueConstraints'])) {
+            $array['uniqueConstraints'] = $metadataTable['uniqueConstraints'];
         }
 
-        if (isset($metadata->table['options'])) {
-            $array['options'] = $metadata->table['options'];
+        if (isset($metadataTable['options'])) {
+            $array['options'] = $metadataTable['options'];
         }
 
         $fieldMappings = $metadata->fieldMappings;