1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\Error; 13 14use Twig\Source; 15use Twig\Template; 16 17/** 18 * Twig base exception. 19 * 20 * This exception class and its children must only be used when 21 * an error occurs during the loading of a template, when a syntax error 22 * is detected in a template, or when rendering a template. Other 23 * errors must use regular PHP exception classes (like when the template 24 * cache directory is not writable for instance). 25 * 26 * To help debugging template issues, this class tracks the original template 27 * name and line where the error occurred. 28 * 29 * Whenever possible, you must set these information (original template name 30 * and line number) yourself by passing them to the constructor. If some or all 31 * these information are not available from where you throw the exception, then 32 * this class will guess them automatically (when the line number is set to -1 33 * and/or the name is set to null). As this is a costly operation, this 34 * can be disabled by passing false for both the name and the line number 35 * when creating a new instance of this class. 36 * 37 * @author Fabien Potencier <fabien@symfony.com> 38 */ 39class Error extends \Exception 40{ 41 private $lineno; 42 private $name; 43 private $rawMessage; 44 private $sourcePath; 45 private $sourceCode; 46 47 /** 48 * Constructor. 49 * 50 * Set the line number to -1 to enable its automatic guessing. 51 * Set the name to null to enable its automatic guessing. 52 * 53 * @param string $message The error message 54 * @param int $lineno The template line where the error occurred 55 * @param Source|string|null $source The source context where the error occurred 56 * @param \Exception $previous The previous exception 57 */ 58 public function __construct(string $message, int $lineno = -1, $source = null, \Exception $previous = null) 59 { 60 parent::__construct('', 0, $previous); 61 62 if (null === $source) { 63 $name = null; 64 } elseif (!$source instanceof Source && !$source instanceof \Twig_Source) { 65 @trigger_error(sprintf('Passing a string as a source to %s is deprecated since Twig 2.6.1; pass a Twig\Source instance instead.', __CLASS__), \E_USER_DEPRECATED); 66 $name = $source; 67 } else { 68 $name = $source->getName(); 69 $this->sourceCode = $source->getCode(); 70 $this->sourcePath = $source->getPath(); 71 } 72 73 $this->lineno = $lineno; 74 $this->name = $name; 75 $this->rawMessage = $message; 76 $this->updateRepr(); 77 } 78 79 /** 80 * Gets the raw message. 81 * 82 * @return string The raw message 83 */ 84 public function getRawMessage() 85 { 86 return $this->rawMessage; 87 } 88 89 /** 90 * Gets the template line where the error occurred. 91 * 92 * @return int The template line 93 */ 94 public function getTemplateLine() 95 { 96 return $this->lineno; 97 } 98 99 /** 100 * Sets the template line where the error occurred. 101 * 102 * @param int $lineno The template line 103 */ 104 public function setTemplateLine($lineno) 105 { 106 $this->lineno = $lineno; 107 108 $this->updateRepr(); 109 } 110 111 /** 112 * Gets the source context of the Twig template where the error occurred. 113 * 114 * @return Source|null 115 */ 116 public function getSourceContext() 117 { 118 return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; 119 } 120 121 /** 122 * Sets the source context of the Twig template where the error occurred. 123 */ 124 public function setSourceContext(Source $source = null) 125 { 126 if (null === $source) { 127 $this->sourceCode = $this->name = $this->sourcePath = null; 128 } else { 129 $this->sourceCode = $source->getCode(); 130 $this->name = $source->getName(); 131 $this->sourcePath = $source->getPath(); 132 } 133 134 $this->updateRepr(); 135 } 136 137 public function guess() 138 { 139 $this->guessTemplateInfo(); 140 $this->updateRepr(); 141 } 142 143 public function appendMessage($rawMessage) 144 { 145 $this->rawMessage .= $rawMessage; 146 $this->updateRepr(); 147 } 148 149 private function updateRepr() 150 { 151 $this->message = $this->rawMessage; 152 153 if ($this->sourcePath && $this->lineno > 0) { 154 $this->file = $this->sourcePath; 155 $this->line = $this->lineno; 156 157 return; 158 } 159 160 $dot = false; 161 if ('.' === substr($this->message, -1)) { 162 $this->message = substr($this->message, 0, -1); 163 $dot = true; 164 } 165 166 $questionMark = false; 167 if ('?' === substr($this->message, -1)) { 168 $this->message = substr($this->message, 0, -1); 169 $questionMark = true; 170 } 171 172 if ($this->name) { 173 if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) { 174 $name = sprintf('"%s"', $this->name); 175 } else { 176 $name = json_encode($this->name); 177 } 178 $this->message .= sprintf(' in %s', $name); 179 } 180 181 if ($this->lineno && $this->lineno >= 0) { 182 $this->message .= sprintf(' at line %d', $this->lineno); 183 } 184 185 if ($dot) { 186 $this->message .= '.'; 187 } 188 189 if ($questionMark) { 190 $this->message .= '?'; 191 } 192 } 193 194 private function guessTemplateInfo() 195 { 196 $template = null; 197 $templateClass = null; 198 199 $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); 200 foreach ($backtrace as $trace) { 201 if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig\Template' !== \get_class($trace['object'])) { 202 $currentClass = \get_class($trace['object']); 203 $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); 204 if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { 205 $template = $trace['object']; 206 $templateClass = \get_class($trace['object']); 207 } 208 } 209 } 210 211 // update template name 212 if (null !== $template && null === $this->name) { 213 $this->name = $template->getTemplateName(); 214 } 215 216 // update template path if any 217 if (null !== $template && null === $this->sourcePath) { 218 $src = $template->getSourceContext(); 219 $this->sourceCode = $src->getCode(); 220 $this->sourcePath = $src->getPath(); 221 } 222 223 if (null === $template || $this->lineno > -1) { 224 return; 225 } 226 227 $r = new \ReflectionObject($template); 228 $file = $r->getFileName(); 229 230 $exceptions = [$e = $this]; 231 while ($e = $e->getPrevious()) { 232 $exceptions[] = $e; 233 } 234 235 while ($e = array_pop($exceptions)) { 236 $traces = $e->getTrace(); 237 array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); 238 239 while ($trace = array_shift($traces)) { 240 if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { 241 continue; 242 } 243 244 foreach ($template->getDebugInfo() as $codeLine => $templateLine) { 245 if ($codeLine <= $trace['line']) { 246 // update template line 247 $this->lineno = $templateLine; 248 249 return; 250 } 251 } 252 } 253 } 254 } 255} 256 257class_alias('Twig\Error\Error', 'Twig_Error'); 258