code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Mail;

use Nette;
use Nette\Utils\Strings;

/**
 * Mail provides functionality to compose and send both text and MIME-compliant multipart email messages.
 *
 * @property   string $subject
 * @property   mixed $htmlBody
 */
class Message extends MimePart
{
	/** Priority */
	const HIGH = 1,
		NORMAL = 3,
		LOW = 5;

	/** @var array */
	public static $defaultHeaders = array(
		'MIME-Version' => '1.0',
		'X-Mailer' => 'Nette Framework',
	);

	/** @var array */
	private $attachments = array();

	/** @var array */
	private $inlines = array();

	/** @var mixed */
	private $html;

	public function __construct()
	{
		foreach (static::$defaultHeaders as $name => $value) {
			$this->setHeader($name, $value);
		}
		$this->setHeader('Date', date('r'));
	}

	/**
	 * Sets the sender of the message.
	 * @param  string  email or format "John Doe" <doe@example.com>
	 * @param  string
	 * @return self
	 */
	public function setFrom($email, $name = NULL)
	{
		$this->setHeader('From', $this->formatEmail($email, $name));
		return $this;
	}

	/**
	 * Returns the sender of the message.
	 * @return array
	 */
	public function getFrom()
	{
		return $this->getHeader('From');
	}

	/**
	 * Adds the reply-to address.
	 * @param  string  email or format "John Doe" <doe@example.com>
	 * @param  string
	 * @return self
	 */
	public function addReplyTo($email, $name = NULL)
	{
		$this->setHeader('Reply-To', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	/**
	 * Sets the subject of the message.
	 * @param  string
	 * @return self
	 */
	public function setSubject($subject)
	{
		$this->setHeader('Subject', $subject);
		return $this;
	}

	/**
	 * Returns the subject of the message.
	 * @return string
	 */
	public function getSubject()
	{
		return $this->getHeader('Subject');
	}

	/**
	 * Adds email recipient.
	 * @param  string  email or format "John Doe" <doe@example.com>
	 * @param  string
	 * @return self
	 */
	public function addTo($email, $name = NULL) // addRecipient()
	{
		$this->setHeader('To', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	/**
	 * Adds carbon copy email recipient.
	 * @param  string  email or format "John Doe" <doe@example.com>
	 * @param  string
	 * @return self
	 */
	public function addCc($email, $name = NULL)
	{
		$this->setHeader('Cc', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	/**
	 * Adds blind carbon copy email recipient.
	 * @param  string  email or format "John Doe" <doe@example.com>
	 * @param  string
	 * @return self
	 */
	public function addBcc($email, $name = NULL)
	{
		$this->setHeader('Bcc', $this->formatEmail($email, $name), TRUE);
		return $this;
	}

	/**
	 * Formats recipient email.
	 * @param  string
	 * @param  string
	 * @return array
	 */
	private function formatEmail($email, $name)
	{
		if (!$name && preg_match('#^(.+) +<(.*)>\z#', $email, $matches)) {
			return array($matches[2] => $matches[1]);
		} else {
			return array($email => $name);
		}
	}

	/**
	 * Sets the Return-Path header of the message.
	 * @param  string  email
	 * @return self
	 */
	public function setReturnPath($email)
	{
		$this->setHeader('Return-Path', $email);
		return $this;
	}

	/**
	 * Returns the Return-Path header.
	 * @return string
	 */
	public function getReturnPath()
	{
		return $this->getHeader('Return-Path');
	}

	/**
	 * Sets email priority.
	 * @param  int
	 * @return self
	 */
	public function setPriority($priority)
	{
		$this->setHeader('X-Priority', (int) $priority);
		return $this;
	}

	/**
	 * Returns email priority.
	 * @return int
	 */
	public function getPriority()
	{
		return $this->getHeader('X-Priority');
	}

	/**
	 * Sets HTML body.
	 * @param  string
	 * @param  mixed base-path
	 * @return self
	 */
	public function setHtmlBody($html, $basePath = NULL)
	{
		if ($basePath === NULL && ($html instanceof Nette\Templating\IFileTemplate || $html instanceof Nette\Application\UI\ITemplate)) {
			$basePath = dirname($html->getFile());
			$bc = TRUE;
		}
		$html = (string) $html;

		if ($basePath) {
			$cids = array();
			$matches = Strings::matchAll(
				$html,
				'#(src\s*=\s*|background\s*=\s*|url\()(["\']?)(?![a-z]+:|[/\\#])([^"\')\s]+)#i',
				PREG_OFFSET_CAPTURE
			);
			if ($matches && isset($bc)) {
				trigger_error(__METHOD__ . '() missing second argument with image base path.', E_USER_WARNING);
			}
			foreach (array_reverse($matches) as $m) {
				$file = rtrim($basePath, '/\\') . '/' . urldecode($m[3][0]);
				if (!isset($cids[$file])) {
					$cids[$file] = substr($this->addEmbeddedFile($file)->getHeader('Content-ID'), 1, -1);
				}
				$html = substr_replace($html,
					"{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}",
					$m[0][1], strlen($m[0][0])
				);
			}
		}

		if ($this->getSubject() == NULL) { // intentionally ==
			$html = Strings::replace($html, '#<title>(.+?)</title>#is', function ($m) use (& $title) {
				$title = $m[1];
			});
			$this->setSubject(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
		}

		$this->html = ltrim(str_replace("\r", '', $html), "\n");

		if ($this->getBody() == NULL && $html != NULL) { // intentionally ==
			$this->setBody($this->buildText($html));
		}

		return $this;
	}

	/**
	 * Gets HTML body.
	 * @return mixed
	 */
	public function getHtmlBody()
	{
		return $this->html;
	}

	/**
	 * Adds embedded file.
	 * @param  string
	 * @param  string
	 * @param  string
	 * @return MimePart
	 */
	public function addEmbeddedFile($file, $content = NULL, $contentType = NULL)
	{
		return $this->inlines[$file] = $this->createAttachment($file, $content, $contentType, 'inline')
			->setHeader('Content-ID', $this->getRandomId());
	}

	/**
	 * Adds attachment.
	 * @param  string
	 * @param  string
	 * @param  string
	 * @return MimePart
	 */
	public function addAttachment($file, $content = NULL, $contentType = NULL)
	{
		return $this->attachments[] = $this->createAttachment($file, $content, $contentType, 'attachment');
	}

	/**
	 * Gets all email attachments.
	 * @return MimePart[]
	 */
	public function getAttachments()
	{
		return $this->attachments;
	}

	/**
	 * Creates file MIME part.
	 * @return MimePart
	 */
	private function createAttachment($file, $content, $contentType, $disposition)
	{
		$part = new MimePart;
		if ($content === NULL) {
			$content = @file_get_contents($file); // intentionally @
			if ($content === FALSE) {
				throw new Nette\FileNotFoundException("Unable to read file '$file'.");
			}
		} else {
			$content = (string) $content;
		}
		$part->setBody($content);
		$part->setContentType($contentType ? $contentType : finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content));
		$part->setEncoding(preg_match('#(multipart|message)/#A', $contentType) ? self::ENCODING_8BIT : self::ENCODING_BASE64);
		$part->setHeader('Content-Disposition', $disposition . '; filename="' . Strings::fixEncoding(basename($file)) . '"');
		return $part;
	}

	/********************* building and sending ****************d*g**/

	/**
	 * Returns encoded message.
	 * @return string
	 */
	public function generateMessage()
	{
		return $this->build()->getEncodedMessage();
	}

	/**
	 * Builds email. Does not modify itself, but returns a new object.
	 * @return self
	 */
	protected function build()
	{
		$mail = clone $this;
		$mail->setHeader('Message-ID', $this->getRandomId());

		$cursor = $mail;
		if ($mail->attachments) {
			$tmp = $cursor->setContentType('multipart/mixed');
			$cursor = $cursor->addPart();
			foreach ($mail->attachments as $value) {
				$tmp->addPart($value);
			}
		}

		if ($mail->html != NULL) { // intentionally ==
			$tmp = $cursor->setContentType('multipart/alternative');
			$cursor = $cursor->addPart();
			$alt = $tmp->addPart();
			if ($mail->inlines) {
				$tmp = $alt->setContentType('multipart/related');
				$alt = $alt->addPart();
				foreach ($mail->inlines as $value) {
					$tmp->addPart($value);
				}
			}
			$alt->setContentType('text/html', 'UTF-8')
				->setEncoding(preg_match('#[^\n]{990}#', $mail->html)
					? self::ENCODING_QUOTED_PRINTABLE
					: (preg_match('#[\x80-\xFF]#', $mail->html) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
				->setBody($mail->html);
		}

		$text = $mail->getBody();
		$mail->setBody(NULL);
		$cursor->setContentType('text/plain', 'UTF-8')
			->setEncoding(preg_match('#[^\n]{990}#', $text)
				? self::ENCODING_QUOTED_PRINTABLE
				: (preg_match('#[\x80-\xFF]#', $text) ? self::ENCODING_8BIT : self::ENCODING_7BIT))
			->setBody($text);

		return $mail;
	}

	/**
	 * Builds text content.
	 * @return string
	 */
	protected function buildText($html)
	{
		$text = Strings::replace($html, array(
			'#<(style|script|head).*</\\1>#Uis' => '',
			'#<t[dh][ >]#i' => ' $0',
			'#<a [^>]*href=(?|"([^"]+)"|\'([^\']+)\')[^>]*>(.*?)</a>#i' =>  '$2 <$1>',
			'#[\r\n]+#' => ' ',
			'#<(/?p|/?h\d|li|br|/tr)[ >/]#i' => "\n$0",
		));
		$text = html_entity_decode(strip_tags($text), ENT_QUOTES, 'UTF-8');
		$text = Strings::replace($text, '#[ \t]+#', ' ');
		return trim($text);
	}

	/** @return string */
	private function getRandomId()
	{
		return '<' . Nette\Utils\Random::generate() . '@'
			. preg_replace('#[^\w.-]+#', '', isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n'))
			. '>';
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Mail;

use Nette;
use Nette\Utils\Strings;

/**
 * MIME message part.
 *
 * @property   mixed $body
 */
class MimePart extends Nette\Object
{
	/** encoding */
	const ENCODING_BASE64 = 'base64',
		ENCODING_7BIT = '7bit',
		ENCODING_8BIT = '8bit',
		ENCODING_QUOTED_PRINTABLE = 'quoted-printable';

	/** @internal */
	const EOL = "\r\n";
	const LINE_LENGTH = 76;

	/** @var array */
	private $headers = array();

	/** @var array */
	private $parts = array();

	/** @var string */
	private $body;

	/**
	 * Sets a header.
	 * @param  string
	 * @param  string|array  value or pair email => name
	 * @param  bool
	 * @return self
	 */
	public function setHeader($name, $value, $append = FALSE)
	{
		if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
			throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
		}

		if ($value == NULL) { // intentionally ==
			if (!$append) {
				unset($this->headers[$name]);
			}

		} elseif (is_array($value)) { // email
			$tmp = & $this->headers[$name];
			if (!$append || !is_array($tmp)) {
				$tmp = array();
			}

			foreach ($value as $email => $recipient) {
				if ($recipient !== NULL && !Strings::checkEncoding($recipient)) {
					Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
				}
				if (preg_match('#[\r\n]#', $recipient)) {
					throw new Nette\InvalidArgumentException('Name must not contain line separator.');
				}
				Nette\Utils\Validators::assert($email, 'email', "header '$name'");
				$tmp[$email] = $recipient;
			}

		} else {
			$value = (string) $value;
			if (!Strings::checkEncoding($value)) {
				throw new Nette\InvalidArgumentException('Header is not valid UTF-8 string.');
			}
			$this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
		}
		return $this;
	}

	/**
	 * Returns a header.
	 * @param  string
	 * @return mixed
	 */
	public function getHeader($name)
	{
		return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
	}

	/**
	 * Removes a header.
	 * @param  string
	 * @return self
	 */
	public function clearHeader($name)
	{
		unset($this->headers[$name]);
		return $this;
	}

	/**
	 * Returns an encoded header.
	 * @param  string
	 * @param  string
	 * @return string
	 */
	public function getEncodedHeader($name)
	{
		$offset = strlen($name) + 2; // colon + space

		if (!isset($this->headers[$name])) {
			return NULL;

		} elseif (is_array($this->headers[$name])) {
			$s = '';
			foreach ($this->headers[$name] as $email => $name) {
				if ($name != NULL) { // intentionally ==
					$s .= self::encodeHeader($name, $offset, TRUE);
					$email = " <$email>";
				}
				$s .= self::append($email . ',', $offset);
			}
			return ltrim(substr($s, 0, -1)); // last comma

		} elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"\z#', $this->headers[$name], $m)) { // Content-Disposition
			$offset += strlen($m[1]);
			return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';

		} else {
			return ltrim(self::encodeHeader($this->headers[$name], $offset));
		}
	}

	/**
	 * Returns all headers.
	 * @return array
	 */
	public function getHeaders()
	{
		return $this->headers;
	}

	/**
	 * Sets Content-Type header.
	 * @param  string
	 * @param  string
	 * @return self
	 */
	public function setContentType($contentType, $charset = NULL)
	{
		$this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
		return $this;
	}

	/**
	 * Sets Content-Transfer-Encoding header.
	 * @param  string
	 * @return self
	 */
	public function setEncoding($encoding)
	{
		$this->setHeader('Content-Transfer-Encoding', $encoding);
		return $this;
	}

	/**
	 * Returns Content-Transfer-Encoding header.
	 * @return string
	 */
	public function getEncoding()
	{
		return $this->getHeader('Content-Transfer-Encoding');
	}

	/**
	 * Adds or creates new multipart.
	 * @return MimePart
	 */
	public function addPart(MimePart $part = NULL)
	{
		return $this->parts[] = $part === NULL ? new self : $part;
	}

	/**
	 * Sets textual body.
	 * @return self
	 */
	public function setBody($body)
	{
		$this->body = (string) $body;
		return $this;
	}

	/**
	 * Gets textual body.
	 * @return mixed
	 */
	public function getBody()
	{
		return $this->body;
	}

	/********************* building ****************d*g**/

	/**
	 * Returns encoded message.
	 * @return string
	 */
	public function getEncodedMessage()
	{
		$output = '';
		$boundary = '--------' . Nette\Utils\Random::generate();

		foreach ($this->headers as $name => $value) {
			$output .= $name . ': ' . $this->getEncodedHeader($name);
			if ($this->parts && $name === 'Content-Type') {
				$output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
			}
			$output .= self::EOL;
		}
		$output .= self::EOL;

		$body = (string) $this->body;
		if ($body !== '') {
			switch ($this->getEncoding()) {
				case self::ENCODING_QUOTED_PRINTABLE:
					$output .= quoted_printable_encode($body);
					break;

				case self::ENCODING_BASE64:
					$output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
					break;

				case self::ENCODING_7BIT:
					$body = preg_replace('#[\x80-\xFF]+#', '', $body);
					// break intentionally omitted

				case self::ENCODING_8BIT:
					$body = str_replace(array("\x00", "\r"), '', $body);
					$body = str_replace("\n", self::EOL, $body);
					$output .= $body;
					break;

				default:
					throw new Nette\InvalidStateException('Unknown encoding.');
			}
		}

		if ($this->parts) {
			if (substr($output, -strlen(self::EOL)) !== self::EOL) {
				$output .= self::EOL;
			}
			foreach ($this->parts as $part) {
				$output .= '--' . $boundary . self::EOL . $part->getEncodedMessage() . self::EOL;
			}
			$output .= '--' . $boundary.'--';
		}

		return $output;
	}

	/********************* QuotedPrintable helpers ****************d*g**/

	/**
	 * Converts a 8 bit header to a string.
	 * @param  string
	 * @param  int
	 * @param  bool
	 * @return string
	 */
	private static function encodeHeader($s, & $offset = 0, $quotes = FALSE)
	{
		if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}~=? _\r\n\t") === strlen($s)) {
			if ($quotes && preg_match('#[^ a-zA-Z0-9!\#$%&\'*+/?^_`{|}~-]#', $s)) { // RFC 2822 atext except =
				return self::append('"' . addcslashes($s, '"\\') . '"', $offset);
			}
			return self::append($s, $offset);
		}

		$o = '';
		if ($offset >= 55) { // maximum for iconv_mime_encode
			$o = self::EOL . "\t";
			$offset = 1;
		}

		$s = iconv_mime_encode(str_repeat(' ', $old = $offset), $s, array(
			'scheme' => 'B', // Q is broken
			'input-charset' => 'UTF-8',
			'output-charset' => 'UTF-8',
		));

		$offset = strlen($s) - strrpos($s, "\n");
		$s = str_replace("\n ", "\n\t", substr($s, $old + 2)); // adds ': '
		return $o . $s;
	}

	private static function append($s, & $offset = 0)
	{
		if ($offset + strlen($s) > self::LINE_LENGTH) {
			$offset = 1;
			$s = self::EOL . "\t" . $s;
		}
		$offset += strlen($s);
		return $s;
	}

}

repo_name: Cerx/ToDo

license: mit


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Mail;

use Nette;

/**
 * Sends emails via the PHP internal mail() function.
 */
class SendmailMailer extends Nette\Object implements IMailer
{
	/** @var string */
	public $commandArgs;

	/**
	 * Sends email.
	 * @return void
	 * @throws SendException
	 */
	public function send(Message $mail)
	{
		$tmp = clone $mail;
		$tmp->setHeader('Subject', NULL);
		$tmp->setHeader('To', NULL);

		$parts = explode(Message::EOL . Message::EOL, $tmp->generateMessage(), 2);

		$args = array(
			str_replace(Message::EOL, PHP_EOL, $mail->getEncodedHeader('To')),
			str_replace(Message::EOL, PHP_EOL, $mail->getEncodedHeader('Subject')),
			str_replace(Message::EOL, PHP_EOL, $parts[1]),
			str_replace(Message::EOL, PHP_EOL, $parts[0]),
		);
		if ($this->commandArgs) {
			$args[] = (string) $this->commandArgs;
		}
		$res = Nette\Utils\Callback::invokeSafe('mail', $args, function ($message) use (& $info) {
			$info = ": $message";
		});
		if ($res === FALSE) {
			throw new SendException("Unable to send email$info.");
		}
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Mail;

use Nette;

/**
 * Sends emails via the SMTP server.
 */
class SmtpMailer extends Nette\Object implements IMailer
{
	/** @var resource */
	private $connection;

	/** @var string */
	private $host;

	/** @var int */
	private $port;

	/** @var string */
	private $username;

	/** @var string */
	private $password;

	/** @var string ssl | tls | (empty) */
	private $secure;

	/** @var int */
	private $timeout;

	/** @var bool */
	private $persistent;

	public function __construct(array $options = array())
	{
		if (isset($options['host'])) {
			$this->host = $options['host'];
			$this->port = isset($options['port']) ? (int) $options['port'] : NULL;
		} else {
			$this->host = ini_get('SMTP');
			$this->port = (int) ini_get('smtp_port');
		}
		$this->username = isset($options['username']) ? $options['username'] : '';
		$this->password = isset($options['password']) ? $options['password'] : '';
		$this->secure = isset($options['secure']) ? $options['secure'] : '';
		$this->timeout = isset($options['timeout']) ? (int) $options['timeout'] : 20;
		if (!$this->port) {
			$this->port = $this->secure === 'ssl' ? 465 : 25;
		}
		$this->persistent = !empty($options['persistent']);
	}

	/**
	 * Sends email.
	 * @return void
	 * @throws SmtpException
	 */
	public function send(Message $mail)
	{
		$mail = clone $mail;

		try {
			if (!$this->connection) {
				$this->connect();
			}

			if (($from = $mail->getHeader('Return-Path'))
				|| ($from = key($mail->getHeader('From')))
			) {
				$this->write("MAIL FROM:<$from>", 250);
			}

			foreach (array_merge(
				(array) $mail->getHeader('To'),
				(array) $mail->getHeader('Cc'),
				(array) $mail->getHeader('Bcc')
			) as $email => $name) {
				$this->write("RCPT TO:<$email>", array(250, 251));
			}

			$mail->setHeader('Bcc', NULL);
			$data = $mail->generateMessage();
			$this->write('DATA', 354);
			$data = preg_replace('#^\.#m', '..', $data);
			$this->write($data);
			$this->write('.', 250);

			if (!$this->persistent) {
				$this->write('QUIT', 221);
				$this->disconnect();
			}
		} catch (SmtpException $e) {
			if ($this->connection) {
				$this->disconnect();
			}
			throw $e;
		}
	}

	/**
	 * Connects and authenticates to SMTP server.
	 * @return void
	 */
	protected function connect()
	{
		$this->connection = @stream_socket_client( // @ is escalated to exception
			($this->secure === 'ssl' ? 'ssl://' : '') . $this->host . ':' . $this->port,
			$errno, $error, $this->timeout
		);
		if (!$this->connection) {
			throw new SmtpException($error, $errno);
		}
		stream_set_timeout($this->connection, $this->timeout, 0);
		$this->read(); // greeting

		$self = isset($_SERVER['HTTP_HOST']) && preg_match('#^[\w.-]+\z#', $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
		$this->write("EHLO $self");
		if ((int) $this->read() !== 250) {
			$this->write("HELO $self", 250);
		}

		if ($this->secure === 'tls') {
			$this->write('STARTTLS', 220);
			if (!stream_socket_enable_crypto($this->connection, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
				throw new SmtpException('Unable to connect via TLS.');
			}
			$this->write("EHLO $self", 250);
		}

		if ($this->username != NULL && $this->password != NULL) {
			$this->write('AUTH LOGIN', 334);
			$this->write(base64_encode($this->username), 334, 'username');
			$this->write(base64_encode($this->password), 235, 'password');
		}
	}

	/**
	 * Disconnects from SMTP server.
	 * @return void
	 */
	protected function disconnect()
	{
		fclose($this->connection);
		$this->connection = NULL;
	}

	/**
	 * Writes data to server and checks response against expected code if some provided.
	 * @param  string
	 * @param  int   response code
	 * @param  string  error message
	 * @return void
	 */
	protected function write($line, $expectedCode = NULL, $message = NULL)
	{
		fwrite($this->connection, $line . Message::EOL);
		if ($expectedCode) {
			$response = $this->read();
			if (!in_array((int) $response, (array) $expectedCode, TRUE)) {
				throw new SmtpException('SMTP server did not accept ' . ($message ? $message : $line) . ' with error: ' . trim($response));
			}
		}
	}

	/**
	 * Reads response from server.
	 * @return string
	 */
	protected function read()
	{
		$s = '';
		while (($line = fgets($this->connection, 1e3)) != NULL) { // intentionally ==
			$s .= $line;
			if (substr($line, 3, 1) === ' ') {
				break;
			}
		}
		return $s;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Mail;

use Nette;

/**
 * Exception thrown when a mail sending error is encountered.
 */
class SendException extends Nette\InvalidStateException
{
}

/**
 * SMTP mailer exception.
 */
class SmtpException extends SendException
{
}


nette/neon

description: Nette NEON: parser & generator for Nette Object Notation

minimum-stability: dev


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Neon;

/**
 * Parser for Nette Object Notation.
 * @internal
 */
class Decoder
{
	/** @var array */
	public static $patterns = array(
		'
			\'[^\'\n]*\' |
			"(?: \\\\. | [^"\\\\\n] )*"
		', // string
		'
			(?: [^#"\',:=[\]{}()\x00-\x20!`-] | [:-][^"\',\]})\s] )
			(?:
				[^,:=\]})(\x00-\x20]+ |
				:(?! [\s,\]})] | $ ) |
				[\ \t]+ [^#,:=\]})(\x00-\x20]
			)*
		', // literal / boolean / integer / float
		'
			[,:=[\]{}()-]
		', // symbol
		'?:\#.*', // comment
		'\n[\t\ ]*', // new line + indent
		'?:[\t\ ]+', // whitespace
	);

	private static $brackets = array(
		'[' => ']',
		'{' => '}',
		'(' => ')',
	);

	/** @var string */
	private $input;

	/** @var array */
	private $tokens;

	/** @var int */
	private $pos;

	/**
	 * Decodes a NEON string.
	 * @param  string
	 * @return mixed
	 */
	public function decode($input)
	{
		if (!is_string($input)) {
			throw new \InvalidArgumentException(sprintf('Argument must be a string, %s given.', gettype($input)));

		} elseif (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM
			$input = substr($input, 3);
		}
		$this->input = "\n" . str_replace("\r", '', $input); // \n forces indent detection

		$pattern = '~(' . implode(')|(', self::$patterns) . ')~Amix';
		$this->tokens = preg_split($pattern, $this->input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE);

		$last = end($this->tokens);
		if ($this->tokens && !preg_match($pattern, $last[0])) {
			$this->pos = count($this->tokens) - 1;
			$this->error();
		}

		$this->pos = 0;
		$res = $this->parse(NULL);

		while (isset($this->tokens[$this->pos])) {
			if ($this->tokens[$this->pos][0][0] === "\n") {
				$this->pos++;
			} else {
				$this->error();
			}
		}
		return $res;
	}

	/**
	 * @param  string  indentation (for block-parser)
	 * @param  mixed
	 * @return array
	 */
	private function parse($indent, $result = NULL, $key = NULL, $hasKey = FALSE)
	{
		$inlineParser = $indent === FALSE;
		$value = NULL;
		$hasValue = FALSE;
		$tokens = $this->tokens;
		$n = & $this->pos;
		$count = count($tokens);
		$mainResult = & $result;

		for (; $n < $count; $n++) {
			$t = $tokens[$n][0];

			if ($t === ',') { // ArrayEntry separator
				if ((!$hasKey && !$hasValue) || !$inlineParser) {
					$this->error();
				}
				$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
				$hasKey = $hasValue = FALSE;

			} elseif ($t === ':' || $t === '=') { // KeyValuePair separator
				if ($hasValue && (is_array($value) || is_object($value))) {
					$this->error('Unacceptable key');

				} elseif ($hasKey && $key === NULL && $hasValue && !$inlineParser) {
					$n++;
					$result[] = $this->parse($indent . '  ', array(), $value, TRUE);
					$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
					if (strlen($newIndent) > strlen($indent)) {
						$n++;
						$this->error('Bad indentation');
					} elseif (strlen($newIndent) < strlen($indent)) {
						return $mainResult; // block parser exit point
					}
					$hasKey = $hasValue = FALSE;

				} elseif ($hasKey || !$hasValue) {
					$this->error();

				} else {
					$key = (string) $value;
					$hasKey = TRUE;
					$hasValue = FALSE;
					$result = & $mainResult;
				}

			} elseif ($t === '-') { // BlockArray bullet
				if ($hasKey || $hasValue || $inlineParser) {
					$this->error();
				}
				$key = NULL;
				$hasKey = TRUE;

			} elseif (isset(self::$brackets[$t])) { // Opening bracket [ ( {
				if ($hasValue) {
					if ($t !== '(') {
						$this->error();
					}
					$n++;
					if ($value instanceof Entity && $value->value === Neon::CHAIN) {
						end($value->attributes)->attributes = $this->parse(FALSE, array());
					} else {
						$value = new Entity($value, $this->parse(FALSE, array()));
					}
				} else {
					$n++;
					$value = $this->parse(FALSE, array());
				}
				$hasValue = TRUE;
				if (!isset($tokens[$n]) || $tokens[$n][0] !== self::$brackets[$t]) { // unexpected type of bracket or block-parser
					$this->error();
				}

			} elseif ($t === ']' || $t === '}' || $t === ')') { // Closing bracket ] ) }
				if (!$inlineParser) {
					$this->error();
				}
				break;

			} elseif ($t[0] === "\n") { // Indent
				if ($inlineParser) {
					if ($hasKey || $hasValue) {
						$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
						$hasKey = $hasValue = FALSE;
					}

				} else {
					while (isset($tokens[$n + 1]) && $tokens[$n + 1][0][0] === "\n") {
						$n++; // skip to last indent
					}
					if (!isset($tokens[$n + 1])) {
						break;
					}

					$newIndent = (string) substr($tokens[$n][0], 1);
					if ($indent === NULL) { // first iteration
						$indent = $newIndent;
					}
					$minlen = min(strlen($newIndent), strlen($indent));
					if ($minlen && (string) substr($newIndent, 0, $minlen) !== (string) substr($indent, 0, $minlen)) {
						$n++;
						$this->error('Invalid combination of tabs and spaces');
					}

					if (strlen($newIndent) > strlen($indent)) { // open new block-array or hash
						if ($hasValue || !$hasKey) {
							$n++;
							$this->error('Bad indentation');
						}
						$this->addValue($result, $key, $this->parse($newIndent));
						$newIndent = isset($tokens[$n], $tokens[$n + 1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last
						if (strlen($newIndent) > strlen($indent)) {
							$n++;
							$this->error('Bad indentation');
						}
						$hasKey = FALSE;

					} else {
						if ($hasValue && !$hasKey) { // block items must have "key"; NULL key means list item
							break;

						} elseif ($hasKey) {
							$this->addValue($result, $key, $hasValue ? $value : NULL);
							if ($key !== NULL && !$hasValue && $newIndent === $indent && isset($tokens[$n + 1]) && $tokens[$n + 1][0] === '-') {
								$result = & $result[$key];
							}
							$hasKey = $hasValue = FALSE;
						}
					}

					if (strlen($newIndent) < strlen($indent)) { // close block
						return $mainResult; // block parser exit point
					}
				}

			} else { // Value
				static $consts = array(
					'true' => TRUE, 'True' => TRUE, 'TRUE' => TRUE, 'yes' => TRUE, 'Yes' => TRUE, 'YES' => TRUE, 'on' => TRUE, 'On' => TRUE, 'ON' => TRUE,
					'false' => FALSE, 'False' => FALSE, 'FALSE' => FALSE, 'no' => FALSE, 'No' => FALSE, 'NO' => FALSE, 'off' => FALSE, 'Off' => FALSE, 'OFF' => FALSE,
					'null' => 0, 'Null' => 0, 'NULL' => 0,
				);
				if ($t[0] === '"') {
					$converted = preg_replace_callback('#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', array($this, 'cbString'), substr($t, 1, -1));
				} elseif ($t[0] === "'") {
					$converted = substr($t, 1, -1);
				} elseif (isset($consts[$t]) && (!isset($tokens[$n + 1][0]) || ($tokens[$n + 1][0] !== ':' && $tokens[$n + 1][0] !== '='))) {
					$converted = $consts[$t] === 0 ? NULL : $consts[$t];
				} elseif (is_numeric($t)) {
					$converted = $t * 1;
				} elseif (preg_match('#0x[0-9a-fA-F]+\z#A', $t)) {
					$converted = hexdec($t);
				} elseif (preg_match('#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::\d\d)?)?)?\z#A', $t)) {
					$converted = new \DateTime($t);
				} else { // literal
					$converted = $t;
				}
				if ($hasValue) {
					if ($value instanceof Entity) { // Entity chaining
						if ($value->value !== Neon::CHAIN) {
							$value = new Entity(Neon::CHAIN, array($value));
						}
						$value->attributes[] = new Entity($converted);
					} else {
						$this->error();
					}
				} else {
					$value = $converted;
					$hasValue = TRUE;
				}
			}
		}

		if ($inlineParser) {
			if ($hasKey || $hasValue) {
				$this->addValue($result, $hasKey ? $key : NULL, $hasValue ? $value : NULL);
			}
		} else {
			if ($hasValue && !$hasKey) { // block items must have "key"
				if ($result === NULL) {
					$result = $value; // simple value parser
				} else {
					$this->error();
				}
			} elseif ($hasKey) {
				$this->addValue($result, $key, $hasValue ? $value : NULL);
			}
		}
		return $mainResult;
	}

	private function addValue(& $result, $key, $value)
	{
		if ($key === NULL) {
			$result[] = $value;
		} elseif ($result && array_key_exists($key, $result)) {
			$this->error("Duplicated key '$key'");
		} else {
			$result[$key] = $value;
		}
	}

	private function cbString($m)
	{
		static $mapping = array('t' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\xc2\xa0");
		$sq = $m[0];
		if (isset($mapping[$sq[1]])) {
			return $mapping[$sq[1]];
		} elseif ($sq[1] === 'u' && strlen($sq) >= 6) {
			$lead = hexdec(substr($sq, 2, 4));
			$tail = hexdec(substr($sq, 8, 4));
			$code = $tail ? (0x2400 + (($lead - 0xD800) << 10) + $tail) : $lead;
			if ($code >= 0xD800 && $code <= 0xDFFF) {
				$this->error("Invalid UTF-8 (lone surrogate) $sq");
			}
			return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
		} elseif ($sq[1] === 'x' && strlen($sq) === 4) {
			return chr(hexdec(substr($sq, 2)));
		} else {
			$this->error("Invalid escaping sequence $sq");
		}
	}

	private function error($message = "Unexpected '%s'")
	{
		$last = isset($this->tokens[$this->pos]) ? $this->tokens[$this->pos] : NULL;
		$offset = $last ? $last[1] : strlen($this->input);
		$text = substr($this->input, 0, $offset);
		$line = substr_count($text, "\n");
		$col = $offset - strrpos("\n" . $text, "\n") + 1;
		$token = $last ? str_replace("\n", '<new line>', substr($last[0], 0, 40)) : 'end';
		throw new Exception(str_replace('%s', $token, $message) . " on line $line, column $col.");
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Neon;

/**
 * Simple generator for Nette Object Notation.
 */
class Encoder
{
	const BLOCK = 1;

	/**
	 * Returns the NEON representation of a value.
	 * @param  mixed
	 * @param  int
	 * @return string
	 */
	public function encode($var, $options = NULL)
	{
		if ($var instanceof \DateTime) {
			return $var->format('Y-m-d H:i:s O');

		} elseif ($var instanceof Entity) {
			if ($var->value === Neon::CHAIN) {
				return implode('', array_map(array($this, 'encode'), $var->attributes));
			}
			return $this->encode($var->value) . '('
				. (is_array($var->attributes) ? substr($this->encode($var->attributes), 1, -1) : '') . ')';
		}

		if (is_object($var)) {
			$obj = $var;
			$var = array();
			foreach ($obj as $k => $v) {
				$var[$k] = $v;
			}
		}

		if (is_array($var)) {
			$isList = !$var || array_keys($var) === range(0, count($var) - 1);
			$s = '';
			if ($options & self::BLOCK) {
				if (count($var) === 0) {
					return '[]';
				}
				foreach ($var as $k => $v) {
					$v = $this->encode($v, self::BLOCK);
					$s .= ($isList ? '-' : $this->encode($k) . ':')
						. (strpos($v, "\n") === FALSE
							? ' ' . $v . "\n"
							: "\n" . preg_replace('#^(?=.)#m', "\t", $v) . (substr($v, -2, 1) === "\n" ? '' : "\n"));
				}
				return $s;

			} else {
				foreach ($var as $k => $v) {
					$s .= ($isList ? '' : $this->encode($k) . ': ') . $this->encode($v) . ', ';
				}
				return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}');
			}

		} elseif (is_string($var) && !is_numeric($var)
			&& !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', $var)
			&& preg_match('~^' . Decoder::$patterns[1] . '\z~x', $var) // 1 = literals
		) {
			return $var;

		} elseif (is_float($var)) {
			$var = json_encode($var);
			return strpos($var, '.') === FALSE ? $var . '.0' : $var;

		} else {
			return json_encode($var, PHP_VERSION_ID >= 50400 ? JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES : 0);
		}
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Neon;

/**
 * Representation of 'foo(bar=1)' literal
 */
class Entity extends \stdClass
{
	/** @var mixed */
	public $value;

	/** @var array */
	public $attributes;

	public function __construct($value = NULL, array $attrs = NULL)
	{
		$this->value = $value;
		$this->attributes = (array) $attrs;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Neon;

/**
 * The exception that indicates error of NEON processing.
 */
class Exception extends \Exception
{
}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Neon;

/**
 * Simple parser & generator for Nette Object Notation.
 */
class Neon
{
	const BLOCK = Encoder::BLOCK;
	const CHAIN = '!!chain';

	/**
	 * Returns the NEON representation of a value.
	 * @param  mixed
	 * @param  int
	 * @return string
	 */
	public static function encode($var, $options = NULL)
	{
		$encoder = new Encoder;
		return $encoder->encode($var, $options);
	}

	/**
	 * Decodes a NEON string.
	 * @param  string
	 * @return mixed
	 */
	public static function decode($input)
	{
		$decoder = new Decoder;
		return $decoder->decode($input);
	}

}


code:
<?php

require __DIR__ . '/Neon/Decoder.php';
require __DIR__ . '/Neon/Encoder.php';
require __DIR__ . '/Neon/Entity.php';
require __DIR__ . '/Neon/Exception.php';
require __DIR__ . '/Neon/Neon.php';


nette/php-generator

description: Nette PHP Generator


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette;
use Nette\Utils\Strings;

/**
 * Class/Interface/Trait description.
 */
class ClassType extends Nette\Object
{
	const TYPE_CLASS = 'class';

	const TYPE_INTERFACE = 'interface';

	const TYPE_TRAIT = 'trait';

	/** @var PhpNamespace|NULL */
	private $namespace;

	/** @var string */
	private $name;

	/** @var string  class|interface|trait */
	private $type = 'class';

	/** @var bool */
	private $final = FALSE;

	/** @var bool */
	private $abstract = FALSE;

	/** @var string|string[] */
	private $extends = array();

	/** @var string[] */
	private $implements = array();

	/** @var string[] */
	private $traits = array();

	/** @var string[] */
	private $documents = array();

	/** @var array name => value */
	private $consts = array();

	/** @var Property[] name => Property */
	private $properties = array();

	/** @var Method[] name => Method */
	private $methods = array();

	/**
	 * @param  \ReflectionClass|string
	 * @return self
	 */
	public static function from($from)
	{
		$from = new \ReflectionClass($from instanceof \ReflectionClass ? $from->getName() : $from);
		if (PHP_VERSION_ID >= 70000 && $from->isAnonymous()) {
			$class = new static('anonymous');
		} else {
			$class = new static($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
		}
		$class->type = $from->isInterface() ? 'interface' : (PHP_VERSION_ID >= 50400 && $from->isTrait() ? 'trait' : 'class');
		$class->final = $from->isFinal() && $class->type === 'class';
		$class->abstract = $from->isAbstract() && $class->type === 'class';
		$class->implements = $from->getInterfaceNames();
		$class->documents = $from->getDocComment() ? array(preg_replace('#^\s*\* ?#m', '', trim($from->getDocComment(), "/* \r\n\t"))) : array();
		if ($from->getParentClass()) {
			$class->extends = $from->getParentClass()->getName();
			$class->implements = array_diff($class->implements, $from->getParentClass()->getInterfaceNames());
		}
		foreach ($from->getProperties() as $prop) {
			if ($prop->getDeclaringClass()->getName() === $from->getName()) {
				$class->properties[$prop->getName()] = Property::from($prop);
			}
		}
		foreach ($from->getMethods() as $method) {
			if ($method->getDeclaringClass()->getName() === $from->getName()) {
				$class->methods[$method->getName()] = Method::from($method)->setNamespace($class->namespace);
			}
		}
		return $class;
	}

	public function __construct($name = '', PhpNamespace $namespace = NULL)
	{
		$this->setName($name);
		$this->namespace = $namespace;
	}

	/**
	 * @return string  PHP code
	 */
	public function __toString()
	{
		$consts = array();
		foreach ($this->consts as $name => $value) {
			$consts[] = "const $name = " . Helpers::dump($value) . ";\n";
		}

		$properties = array();
		foreach ($this->properties as $property) {
			$doc = str_replace("\n", "\n * ", implode("\n", $property->getDocuments()));
			$properties[] = ($property->getDocuments() ? (strpos($doc, "\n") === FALSE ? "/** $doc */\n" : "/**\n * $doc\n */\n") : '')
				. $property->getVisibility() . ($property->isStatic() ? ' static' : '') . ' $' . $property->getName()
				. ($property->value === NULL ? '' : ' = ' . Helpers::dump($property->value))
				. ";\n";
		}

		$namespace = $this->namespace;
		$mapper = function (array $arr) use ($namespace) {
			return $namespace ? array_map(array($namespace, 'unresolveName'), $arr) : $arr;
		};

		return Strings::normalize(
			($this->documents ? str_replace("\n", "\n * ", "/**\n" . implode("\n", $this->documents)) . "\n */\n" : '')
			. ($this->abstract ? 'abstract ' : '')
			. ($this->final ? 'final ' : '')
			. $this->type . ' '
			. $this->name . ' '
			. ($this->extends ? 'extends ' . implode(', ', $mapper((array) $this->extends)) . ' ' : '')
			. ($this->implements ? 'implements ' . implode(', ', $mapper($this->implements)) . ' ' : '')
			. "\n{\n"
			. Strings::indent(
				($this->traits ? 'use ' . implode(";\nuse ", $mapper($this->traits)) . ";\n\n" : '')
				. ($this->consts ? implode('', $consts) . "\n" : '')
				. ($this->properties ? implode("\n", $properties) . "\n" : '')
				. ($this->methods ? "\n" . implode("\n\n\n", $this->methods) . "\n\n" : ''), 1)
			. '}'
		) . "\n";
	}

	/**
	 * @return PhpNamespace|NULL
	 */
	public function getNamespace()
	{
		return $this->namespace;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function setName($name)
	{
		$this->name = (string) $name;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function setType($type)
	{
		if (!in_array($type, array('class', 'interface', 'trait'), TRUE)) {
			throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.');
		}
		$this->type = $type;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getType()
	{
		return $this->type;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setFinal($state = TRUE)
	{
		$this->final = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isFinal()
	{
		return $this->final;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setAbstract($state = TRUE)
	{
		$this->abstract = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isAbstract()
	{
		return $this->abstract;
	}

	/**
	 * @param  string|string[]
	 * @return self
	 */
	public function setExtends($types)
	{
		if (!is_string($types) && !(is_array($types) && array_filter($types, 'is_string') === $types)) {
			throw new Nette\InvalidArgumentException('Argument must be string or string[].');
		}
		$this->extends = $types;
		return $this;
	}

	/**
	 * @return string|string[]
	 */
	public function getExtends()
	{
		return $this->extends;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addExtend($type)
	{
		$this->extends = (array) $this->extends;
		$this->extends[] = (string) $type;
		return $this;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setImplements(array $types)
	{
		$this->implements = $types;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getImplements()
	{
		return $this->implements;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addImplement($type)
	{
		$this->implements[] = (string) $type;
		return $this;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setTraits(array $traits)
	{
		$this->traits = $traits;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getTraits()
	{
		return $this->traits;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addTrait($trait)
	{
		$this->traits[] = (string) $trait;
		return $this;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setDocuments(array $s)
	{
		$this->documents = $s;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getDocuments()
	{
		return $this->documents;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addDocument($s)
	{
		$this->documents[] = (string) $s;
		return $this;
	}

	/**
	 * @return self
	 */
	public function setConsts(array $consts)
	{
		$this->consts = $consts;
		return $this;
	}

	/**
	 * @return array
	 */
	public function getConsts()
	{
		return $this->consts;
	}

	/**
	 * @param  string
	 * @param  mixed
	 * @return self
	 */
	public function addConst($name, $value)
	{
		$this->consts[$name] = $value;
		return $this;
	}

	/**
	 * @param  Property[]
	 * @return self
	 */
	public function setProperties(array $props)
	{
		$this->properties = array();
		foreach ($props as $v) {
			if (!$v instanceof Property) {
				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].');
			}
			$this->properties[$v->getName()] = $v;
		}
		return $this;
	}

	/**
	 * @return Property[]
	 */
	public function getProperties()
	{
		return $this->properties;
	}

	/**
	 * @return Property
	 */
	public function getProperty($name)
	{
		if (!isset($this->properties[$name])) {
			throw new Nette\InvalidArgumentException("Property '$name' not found.");
		}
		return $this->properties[$name];
	}

	/**
	 * @param  string  without $
	 * @param  mixed
	 * @return Property
	 */
	public function addProperty($name, $value = NULL)
	{
		$property = new Property($name);
		return $this->properties[$name] = $property->setValue($value);
	}

	/**
	 * @param  Method[]
	 * @return self
	 */
	public function setMethods(array $methods)
	{
		$this->methods = array();
		foreach ($methods as $v) {
			if (!$v instanceof Method) {
				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].');
			}
			$this->methods[$v->getName()] = $v->setNamespace($this->namespace);
		}
		return $this;
	}

	/**
	 * @return Method[]
	 */
	public function getMethods()
	{
		return $this->methods;
	}

	/**
	 * @return Method
	 */
	public function getMethod($name)
	{
		if (!isset($this->methods[$name])) {
			throw new Nette\InvalidArgumentException("Method '$name' not found.");
		}
		return $this->methods[$name];
	}

	/**
	 * @param  string
	 * @return Method
	 */
	public function addMethod($name)
	{
		$method = new Method($name);
		if ($this->type === 'interface') {
			$method->setVisibility(NULL)->setBody(FALSE);
		} else {
			$method->setVisibility('public');
		}
		return $this->methods[$name] = $method->setNamespace($this->namespace);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette;

/**
 * PHP code generator utils.
 */
class Helpers
{
	const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
	const MAX_DEPTH = 50;

	/**
	 * Returns a PHP representation of a variable.
	 * @return string
	 */
	public static function dump($var)
	{
		return self::_dump($var);
	}

	private static function _dump(& $var, $level = 0)
	{
		if ($var instanceof PhpLiteral) {
			return (string) $var;

		} elseif (is_float($var)) {
			$var = var_export($var, TRUE);
			return strpos($var, '.') === FALSE ? $var . '.0' : $var;

		} elseif (is_bool($var)) {
			return $var ? 'TRUE' : 'FALSE';

		} elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
			static $table;
			if ($table === NULL) {
				foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
					$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
				}
				$table['\\'] = '\\\\';
				$table["\r"] = '\r';
				$table["\n"] = '\n';
				$table["\t"] = '\t';
				$table['$'] = '\$';
				$table['"'] = '\"';
			}
			return '"' . strtr($var, $table) . '"';

		} elseif (is_string($var)) {
			return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|\z)#', '\\\\$0', $var) . "'";

		} elseif (is_array($var)) {
			$space = str_repeat("\t", $level);

			static $marker;
			if ($marker === NULL) {
				$marker = uniqid("\x00", TRUE);
			}
			if (empty($var)) {
				$out = '';

			} elseif ($level > self::MAX_DEPTH || isset($var[$marker])) {
				throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');

			} else {
				$out = '';
				$outAlt = "\n$space";
				$var[$marker] = TRUE;
				$counter = 0;
				foreach ($var as $k => & $v) {
					if ($k !== $marker) {
						$item = ($k === $counter ? '' : self::_dump($k, $level + 1) . ' => ') . self::_dump($v, $level + 1);
						$counter = is_int($k) ? max($k + 1, $counter) : $counter;
						$out .= ($out === '' ? '' : ', ') . $item;
						$outAlt .= "\t$item,\n$space";
					}
				}
				unset($var[$marker]);
			}
			return 'array(' . (strpos($out, "\n") === FALSE && strlen($out) < 40 ? $out : $outAlt) . ')';

		} elseif ($var instanceof \Serializable) {
			$var = serialize($var);
			return 'unserialize(' . self::_dump($var, $level) . ')';

		} elseif ($var instanceof \Closure) {
			throw new Nette\InvalidArgumentException('Cannot dump closure.');

		} elseif (is_object($var)) {
			if (PHP_VERSION_ID >= 70000 && ($rc = new \ReflectionObject($var)) && $rc->isAnonymous()) {
				throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
			}
			$arr = (array) $var;
			$space = str_repeat("\t", $level);
			$class = get_class($var);

			static $list = array();
			if ($level > self::MAX_DEPTH || in_array($var, $list, TRUE)) {
				throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');

			} else {
				$out = "\n";
				$list[] = $var;
				if (method_exists($var, '__sleep')) {
					foreach ($var->__sleep() as $v) {
						$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = TRUE;
					}
				}
				foreach ($arr as $k => & $v) {
					if (!isset($props) || isset($props[$k])) {
						$out .= "$space\t" . self::_dump($k, $level + 1) . ' => ' . self::_dump($v, $level + 1) . ",\n";
					}
				}
				array_pop($list);
				$out .= $space;
			}
			return $class === 'stdClass'
				? "(object) array($out)"
				: __CLASS__ . "::createObject('$class', array($out))";

		} elseif (is_resource($var)) {
			throw new Nette\InvalidArgumentException('Cannot dump resource.');

		} else {
			return var_export($var, TRUE);
		}
	}

	/**
	 * Generates PHP statement.
	 * @return string
	 */
	public static function format($statement)
	{
		$args = func_get_args();
		return self::formatArgs(array_shift($args), $args);
	}

	/**
	 * Generates PHP statement.
	 * @return string
	 */
	public static function formatArgs($statement, array $args)
	{
		$a = strpos($statement, '?');
		while ($a !== FALSE) {
			if (!$args) {
				throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
			}
			$arg = array_shift($args);
			if (substr($statement, $a + 1, 1) === '*') { // ?*
				if (!is_array($arg)) {
					throw new Nette\InvalidArgumentException('Argument must be an array.');
				}
				$s = substr($statement, 0, $a);
				$sep = '';
				foreach ($arg as $tmp) {
					$s .= $sep . self::dump($tmp);
					$sep = strlen($s) - strrpos($s, "\n") > 100 ? ",\n\t" : ', ';
				}
				$statement = $s . substr($statement, $a + 2);
				$a = strlen($s);

			} else {
				$arg = substr($statement, $a - 1, 1) === '$' || in_array(substr($statement, $a - 2, 2), array('->', '::'), TRUE)
					? self::formatMember($arg) : self::_dump($arg);
				$statement = substr_replace($statement, $arg, $a, 1);
				$a += strlen($arg);
			}
			$a = strpos($statement, '?', $a);
		}
		return $statement;
	}

	/**
	 * Returns a PHP representation of a object member.
	 * @return string
	 */
	public static function formatMember($name)
	{
		return $name instanceof PhpLiteral || !self::isIdentifier($name)
			? '{' . self::_dump($name) . '}'
			: $name;
	}

	/**
	 * @return bool
	 */
	public static function isIdentifier($value)
	{
		return is_string($value) && preg_match('#^' . self::PHP_IDENT . '\z#', $value);
	}

	/** @internal */
	public static function createObject($class, array $props)
	{
		return unserialize('O' . substr(serialize((string) $class), 1, -1) . substr(serialize($props), 1));
	}

	/**
	 * @param  string
	 * @return string
	 */
	public static function extractNamespace($name)
	{
		return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
	}

	/**
	 * @param  string
	 * @return string
	 */
	public static function extractShortName($name)
	{
		return ($pos = strrpos($name, '\\')) === FALSE ? $name : substr($name, $pos + 1);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette;

/**
 * Method or function description.
 */
class Method extends Nette\Object
{
	/** @var string|NULL */
	private $name;

	/** @var array of name => Parameter */
	private $parameters = array();

	/** @var array of name => bool */
	private $uses = array();

	/** @var string|FALSE */
	private $body = '';

	/** @var bool */
	private $static = FALSE;

	/** @var string|NULL  public|protected|private */
	private $visibility;

	/** @var bool */
	private $final = FALSE;

	/** @var bool */
	private $abstract = FALSE;

	/** @var bool */
	private $returnReference = FALSE;

	/** @var bool */
	private $variadic = FALSE;

	/** @var string[] */
	private $documents = array();

	/** @var PhpNamespace|NULL */
	private $namespace;

	/** @var string|NULL */
	private $returnType;

	/**
	 * @return self
	 */
	public static function from($from)
	{
		if (is_string($from) && strpos($from, '::')) {
			$from = new \ReflectionMethod($from);
		} elseif (is_array($from)) {
			$from = new \ReflectionMethod($from[0], $from[1]);
		} elseif (!$from instanceof \ReflectionFunctionAbstract) {
			$from = new \ReflectionFunction($from);
		}

		$method = new static($from->isClosure() ? NULL : $from->getName());
		foreach ($from->getParameters() as $param) {
			$method->parameters[$param->getName()] = Parameter::from($param);
		}
		if ($from instanceof \ReflectionMethod) {
			$method->static = $from->isStatic();
			$method->visibility = $from->isPrivate() ? 'private' : ($from->isProtected() ? 'protected' : NULL);
			$method->final = $from->isFinal();
			$method->abstract = $from->isAbstract() && !$from->getDeclaringClass()->isInterface();
			$method->body = $from->isAbstract() ? FALSE : '';
		}
		$method->returnReference = $from->returnsReference();
		$method->variadic = PHP_VERSION_ID >= 50600 && $from->isVariadic();
		$method->documents = $from->getDocComment() ? array(preg_replace('#^\s*\* ?#m', '', trim($from->getDocComment(), "/* \r\n\t"))) : array();
		if (PHP_VERSION_ID >= 70000 && $from->hasReturnType()) {
			$method->returnType = (string) $from->getReturnType();
		}
		return $method;
	}

	/**
	 * @param  string|NULL
	 */
	public function __construct($name = NULL)
	{
		$this->setName($name);
	}

	/**
	 * @return string  PHP code
	 */
	public function __toString()
	{
		$parameters = array();
		foreach ($this->parameters as $param) {
			$variadic = $this->variadic && $param === end($this->parameters);
			$hint = $param->getTypeHint();
			$parameters[] = ($hint ? ($this->namespace ? $this->namespace->unresolveName($hint) : $hint) . ' ' : '')
				. ($param->isReference() ? '&' : '')
				. ($variadic ? '...' : '')
				. '$' . $param->getName()
				. ($param->isOptional() && !$variadic ? ' = ' . Helpers::dump($param->defaultValue) : '');
		}
		$uses = array();
		foreach ($this->uses as $param) {
			$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
		}

		return ($this->documents ? str_replace("\n", "\n * ", "/**\n" . implode("\n", $this->documents)) . "\n */\n" : '')
			. ($this->abstract ? 'abstract ' : '')
			. ($this->final ? 'final ' : '')
			. ($this->visibility ? $this->visibility . ' ' : '')
			. ($this->static ? 'static ' : '')
			. 'function'
			. ($this->returnReference ? ' &' : '')
			. ' ' . $this->name
			. '(' . implode(', ', $parameters) . ')'
			. ($this->uses ? ' use (' . implode(', ', $uses) . ')' : '')
			. ($this->returnType ? ': ' . ($this->namespace ? $this->namespace->unresolveName($this->returnType) : $this->returnType) : '')
			. ($this->abstract || $this->body === FALSE ? ';'
				: ($this->name ? "\n" : ' ') . "{\n" . Nette\Utils\Strings::indent(ltrim(rtrim($this->body) . "\n"), 1) . '}');
	}

	/**
	 * @param  string|NULL
	 * @return self
	 */
	public function setName($name)
	{
		$this->name = $name ? (string) $name : NULL;
		return $this;
	}

	/**
	 * @return string|NULL
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @param  Parameter[]
	 * @return self
	 */
	public function setParameters(array $val)
	{
		$this->parameters = array();
		foreach ($val as $v) {
			if (!$v instanceof Parameter) {
				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].');
			}
			$this->parameters[$v->getName()] = $v;
		}
		return $this;
	}

	/**
	 * @return Parameter[]
	 */
	public function getParameters()
	{
		return $this->parameters;
	}

	/**
	 * @param  string  without $
	 * @return Parameter
	 */
	public function addParameter($name, $defaultValue = NULL)
	{
		$param = new Parameter($name);
		if (func_num_args() > 1) {
			$param->setOptional(TRUE)->setDefaultValue($defaultValue);
		}
		return $this->parameters[$name] = $param;
	}

	/**
	 * @return self
	 */
	public function setUses(array $val)
	{
		$this->uses = $val;
		return $this;
	}

	/**
	 * @return array
	 */
	public function getUses()
	{
		return $this->uses;
	}

	/**
	 * @return Parameter
	 */
	public function addUse($name)
	{
		return $this->uses[] = new Parameter($name);
	}

	/**
	 * @return self
	 */
	public function setBody($statement, array $args = NULL)
	{
		$this->body = func_num_args() > 1 ? Helpers::formatArgs($statement, $args) : $statement;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getBody()
	{
		return $this->body;
	}

	/**
	 * @return self
	 */
	public function addBody($statement, array $args = NULL)
	{
		$this->body .= (func_num_args() > 1 ? Helpers::formatArgs($statement, $args) : $statement) . "\n";
		return $this;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setStatic($val)
	{
		$this->static = (bool) $val;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isStatic()
	{
		return $this->static;
	}

	/**
	 * @param  string|NULL  public|protected|private
	 * @return self
	 */
	public function setVisibility($val)
	{
		if (!in_array($val, array('public', 'protected', 'private', NULL), TRUE)) {
			throw new Nette\InvalidArgumentException('Argument must be public|protected|private|NULL.');
		}
		$this->visibility = $val ? (string) $val : NULL;
		return $this;
	}

	/**
	 * @return string|NULL
	 */
	public function getVisibility()
	{
		return $this->visibility;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setFinal($val)
	{
		$this->final = (bool) $val;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isFinal()
	{
		return $this->final;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setAbstract($val)
	{
		$this->abstract = (bool) $val;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isAbstract()
	{
		return $this->abstract;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setReturnReference($val)
	{
		$this->returnReference = (bool) $val;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function getReturnReference()
	{
		return $this->returnReference;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setVariadic($val)
	{
		$this->variadic = (bool) $val;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isVariadic()
	{
		return $this->variadic;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setDocuments(array $val)
	{
		$this->documents = $val;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getDocuments()
	{
		return $this->documents;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addDocument($val)
	{
		$this->documents[] = (string) $val;
		return $this;
	}

	/**
	 * @return self
	 */
	public function setNamespace(PhpNamespace $val = NULL)
	{
		$this->namespace = $val;
		return $this;
	}

	/**
	 * @param  string|NULL
	 * @return self
	 */
	public function setReturnType($val)
	{
		$this->returnType = $val ? (string) $val : NULL;
		return $this;
	}

	/**
	 * @return string|NULL
	 */
	public function getReturnType()
	{
		return $this->returnType;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette;

/**
 * Method parameter description.
 */
class Parameter extends Nette\Object
{
	/** @var string */
	private $name = '';

	/** @var bool */
	private $reference = FALSE;

	/** @var string|NULL */
	private $typeHint;

	/** @var bool */
	private $optional = FALSE;

	/** @var mixed */
	public $defaultValue;

	/**
	 * @return self
	 */
	public static function from(\ReflectionParameter $from)
	{
		$param = new static($from->getName());
		$param->reference = $from->isPassedByReference();
		if (PHP_VERSION_ID >= 70000) {
			$param->typeHint = $from->hasType() ? (string) $from->getType() : NULL;
		} elseif ($from->isArray()) {
			$param->typeHint = 'array';
		} elseif (PHP_VERSION_ID >= 50400 && $from->isCallable()) {
			$param->typeHint = 'callable';
		} else {
			try {
				$param->typeHint = $from->getClass() ? $from->getClass()->getName() : NULL;
			} catch (\ReflectionException $e) {
				if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
					$param->typeHint = $m[1];
				} else {
					throw $e;
				}
			}
		}
		$param->optional = PHP_VERSION_ID < 50407 ? $from->isOptional() || ($param->typeHint && $from->allowsNull()) : $from->isDefaultValueAvailable();
		$param->defaultValue = (PHP_VERSION_ID === 50316 ? $from->isOptional() : $from->isDefaultValueAvailable()) ? $from->getDefaultValue() : NULL;
		return $param;
	}

	/**
	 * @param  string  without $
	 */
	public function __construct($name = '')
	{
		$this->setName($name);
	}

	/**
	 * @param  string  without $
	 * @return self
	 */
	public function setName($name)
	{
		$this->name = (string) $name;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setReference($state = TRUE)
	{
		$this->reference = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isReference()
	{
		return $this->reference;
	}

	/**
	 * @param  string|NULL
	 * @return self
	 */
	public function setTypeHint($hint)
	{
		$this->typeHint = $hint ? (string) $hint : NULL;
		return $this;
	}

	/**
	 * @return string|NULL
	 */
	public function getTypeHint()
	{
		return $this->typeHint;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setOptional($state = TRUE)
	{
		$this->optional = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isOptional()
	{
		return $this->optional;
	}

	/**
	 * @return self
	 */
	public function setDefaultValue($val)
	{
		$this->defaultValue = $val;
		return $this;
	}

	/**
	 * @return mixed
	 */
	public function getDefaultValue()
	{
		return $this->defaultValue;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette\Object;
use Nette\Utils\Strings;

/**
 * Instance of PHP file.
 *
 * Generates:
 * - opening tag (<?php)
 * - doc comments
 * - one or more namespaces
 */
class PhpFile extends Object
{
	/** @var string[] */
	private $documents = array();

	/** @var PhpNamespace[] */
	private $namespaces = array();

	/**
	 * @return string[]
	 */
	public function getDocuments()
	{
		return $this->documents;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setDocuments(array $documents)
	{
		$this->documents = $documents;
		return $this;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addDocument($document)
	{
		$this->documents[] = $document;
		return $this;
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addClass($name)
	{
		return $this
			->addNamespace(Helpers::extractNamespace($name))
			->addClass(Helpers::extractShortName($name));
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addInterface($name)
	{
		return $this
			->addNamespace(Helpers::extractNamespace($name))
			->addInterface(Helpers::extractShortName($name));
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addTrait($name)
	{
		return $this
			->addNamespace(Helpers::extractNamespace($name))
			->addTrait(Helpers::extractShortName($name));
	}

	/**
	 * @param  string NULL means global namespace
	 * @return PhpNamespace
	 */
	public function addNamespace($name)
	{
		if (!isset($this->namespaces[$name])) {
			$this->namespaces[$name] = new PhpNamespace($name);
		}
		return $this->namespaces[$name];
	}

	/**
	 * @return string PHP code
	 */
	public function __toString()
	{
		foreach ($this->namespaces as $namespace) {
			$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces[NULL]));
		}

		return Strings::normalize(
			"<?php\n"
			. ($this->documents ? "\n" . str_replace("\n", "\n * ", "/**\n" . implode("\n", $this->documents)) . "\n */\n\n" : '')
			. implode("\n\n", $this->namespaces)
		) . "\n";
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

/**
 * PHP literal value.
 */
class PhpLiteral
{
	/** @var string */
	private $value;

	public function __construct($value)
	{
		$this->value = (string) $value;
	}

	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->value;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette\InvalidStateException;
use Nette\Object;
use Nette\Utils\Strings;

/**
 * Namespaced part of a PHP file.
 *
 * Generates:
 * - namespace statement
 * - variable amount of use statements
 * - one or more class declarations
 */
class PhpNamespace extends Object
{
	/** @var string */
	private $name;

	/** @var bool */
	private $bracketedSyntax = FALSE;

	/** @var string[] */
	private $uses = array();

	/** @var ClassType[] */
	private $classes = array();

	public function __construct($name = NULL)
	{
		$this->setName($name);
	}

	/**
	 * @param  string|NULL
	 * @return self
	 */
	public function setName($name)
	{
		$this->name = (string) $name;
		return $this;
	}

	/**
	 * @return string|NULL
	 */
	public function getName()
	{
		return $this->name ?: NULL;
	}

	/**
	 * @param  bool
	 * @return self
	 * @internal
	 */
	public function setBracketedSyntax($state = TRUE)
	{
		$this->bracketedSyntax = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function getBracketedSyntax()
	{
		return $this->bracketedSyntax;
	}

	/**
	 * @param  string
	 * @param  string
	 * @param  string
	 * @throws InvalidStateException
	 * @return self
	 */
	public function addUse($name, $alias = NULL, &$aliasOut = NULL)
	{
		$name = ltrim($name, '\\');
		if ($alias === NULL && $this->name === Helpers::extractNamespace($name)) {
			$alias = Helpers::extractShortName($name);
		}
		if ($alias === NULL) {
			$path = explode('\\', $name);
			$counter = NULL;
			do {
				if (empty($path)) {
					$counter++;
				} else {
					$alias = array_pop($path) . $alias;
				}
			} while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name);
			$alias .= $counter;

		} elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) {
			throw new InvalidStateException(
				"Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'."
			);
		}

		$aliasOut = $alias;
		$this->uses[$alias] = $name;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getUses()
	{
		return $this->uses;
	}

	/**
	 * @param  string
	 * @return string
	 */
	public function unresolveName($name)
	{
		if (in_array(strtolower($name), array('self', 'parent', 'array', 'callable', 'string', 'bool', 'float', 'int', ''), TRUE)) {
			return $name;
		}
		$name = ltrim($name, '\\');
		$res = NULL;
		$lower = strtolower($name);
		foreach ($this->uses as $alias => $for) {
			if (Strings::startsWith($lower . '\\', strtolower($for) . '\\')) {
				$short = $alias . substr($name, strlen($for));
				if (!isset($res) || strlen($res) > strlen($short)) {
					$res = $short;
				}
			}
		}

		if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) {
			return substr($name, strlen($this->name) + 1);
		} else {
			return $res ?: ($this->name ? '\\' : '') . $name;
		}
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addClass($name)
	{
		if (!isset($this->classes[$name])) {
			$this->addUse($this->name . '\\' . $name);
			$this->classes[$name] = new ClassType($name, $this);
		}
		return $this->classes[$name];
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addInterface($name)
	{
		return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
	}

	/**
	 * @param  string
	 * @return ClassType
	 */
	public function addTrait($name)
	{
		return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
	}

	/**
	 * @return ClassType[]
	 */
	public function getClasses()
	{
		return $this->classes;
	}

	/**
	 * @return string PHP code
	 */
	public function __toString()
	{
		$uses = array();
		asort($this->uses);
		foreach ($this->uses as $alias => $name) {
			$useNamespace = Helpers::extractNamespace($name);

			if ($this->name !== $useNamespace) {
				if ($alias === $name || substr($name, -(strlen($alias) + 1)) === '\\' . $alias) {
					$uses[] = "use {$name};";
				} else {
					$uses[] = "use {$name} as {$alias};";
				}
			}
		}

		$body = ($uses ? implode("\n", $uses) . "\n\n" : '')
			. implode("\n", $this->classes);

		if ($this->bracketedSyntax) {
			return 'namespace' . ($this->name ? ' ' . $this->name : '') . " {\n\n"
				. Strings::indent($body)
				. "\n}\n";

		} else {
			return ($this->name ? "namespace {$this->name};\n\n" : '')
				. $body;
		}
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\PhpGenerator;

use Nette;

/**
 * Class property description.
 */
class Property extends Nette\Object
{
	/** @var string */
	private $name = '';

	/** @var mixed */
	public $value;

	/** @var bool */
	private $static = FALSE;

	/** @var string  public|protected|private */
	private $visibility = 'public';

	/** @var string[] */
	private $documents = array();

	/**
	 * @return self
	 */
	public static function from(\ReflectionProperty $from)
	{
		$prop = new static($from->getName());
		$defaults = $from->getDeclaringClass()->getDefaultProperties();
		$prop->value = isset($defaults[$prop->name]) ? $defaults[$prop->name] : NULL;
		$prop->static = $from->isStatic();
		$prop->visibility = $from->isPrivate() ? 'private' : ($from->isProtected() ? 'protected' : 'public');
		$prop->documents = $from->getDocComment() ? array(preg_replace('#^\s*\* ?#m', '', trim($from->getDocComment(), "/* \r\n\t"))) : array();
		return $prop;
	}

	/**
	 * @param  string  without $
	 */
	public function __construct($name = '')
	{
		$this->setName($name);
	}

	/**
	 * @param  string  without $
	 * @return self
	 */
	public function setName($name)
	{
		$this->name = (string) $name;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @return self
	 */
	public function setValue($val)
	{
		$this->value = $val;
		return $this;
	}

	/**
	 * @return mixed
	 */
	public function getValue()
	{
		return $this->value;
	}

	/**
	 * @param  bool
	 * @return self
	 */
	public function setStatic($state = TRUE)
	{
		$this->static = (bool) $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isStatic()
	{
		return $this->static;
	}

	/**
	 * @param  string  public|protected|private
	 * @return self
	 */
	public function setVisibility($val)
	{
		if (!in_array($val, array('public', 'protected', 'private'), TRUE)) {
			throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
		}
		$this->visibility = (string) $val;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getVisibility()
	{
		return $this->visibility;
	}

	/**
	 * @param  string[]
	 * @return self
	 */
	public function setDocuments(array $s)
	{
		$this->documents = $s;
		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getDocuments()
	{
		return $this->documents;
	}

	/**
	 * @param  string
	 * @return self
	 */
	public function addDocument($s)
	{
		$this->documents[] = (string) $s;
		return $this;
	}

}


nette/reflection

description: Nette PHP Reflection Component


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Bridges\ReflectionDI;

use Nette;

/**
 * Extension for Nette DI.
 */
class ReflectionExtension extends Nette\DI\CompilerExtension
{
	/** @var bool */
	private $debugMode;

	public function __construct($debugMode = FALSE)
	{
		$this->debugMode = $debugMode;
	}

	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$class->getMethod('initialize')
			->addBody('Nette\Reflection\AnnotationsParser::setCacheStorage($this->getByType("Nette\Caching\IStorage"));')
			->addBody('Nette\Reflection\AnnotationsParser::$autoRefresh = ?;', array($this->debugMode));
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;

/**
 * Basic annotation implementation.
 */
class Annotation extends Nette\Object implements IAnnotation
{

	public function __construct(array $values)
	{
		foreach ($values as $k => $v) {
			$this->$k = $v;
		}
	}

	/**
	 * Returns default annotation.
	 * @return string
	 */
	public function __toString()
	{
		return $this->value;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\Strings;

/**
 * Annotations support for PHP.
 * @Annotation
 */
class AnnotationsParser
{
	/** @internal single & double quoted PHP string */
	const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';

	/** @internal identifier */
	const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-\\\]*';

	/** @var bool */
	public static $useReflection;

	/** @var bool */
	public static $autoRefresh = TRUE;

	/** @var array */
	public static $inherited = array('description', 'param', 'return');

	/** @var array */
	private static $cache;

	/** @var array */
	private static $timestamps;

	/** @var Nette\Caching\IStorage */
	private static $cacheStorage;

	/**
	 * Static class - cannot be instantiated.
	 */
	final public function __construct()
	{
		throw new Nette\StaticClassException;
	}

	/**
	 * Returns annotations.
	 * @param  \ReflectionClass|\ReflectionMethod|\ReflectionProperty
	 * @return array
	 */
	public static function getAll(\Reflector $r)
	{
		if ($r instanceof \ReflectionClass) {
			$type = $r->getName();
			$member = 'class';
			$file = $r->getFileName();

		} elseif ($r instanceof \ReflectionMethod) {
			$type = $r->getDeclaringClass()->getName();
			$member = $r->getName();
			$file = $r->getFileName();

		} elseif ($r instanceof \ReflectionFunction) {
			$type = NULL;
			$member = $r->getName();
			$file = $r->getFileName();

		} else {
			$type = $r->getDeclaringClass()->getName();
			$member = '$' . $r->getName();
			$file = $r->getDeclaringClass()->getFileName();
		}

		if (!self::$useReflection) { // auto-expire cache
			if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
				unset(self::$cache[$type]);
			}
			unset(self::$timestamps[$file]);
		}

		if (isset(self::$cache[$type][$member])) { // is value cached?
			return self::$cache[$type][$member];
		}

		if (self::$useReflection === NULL) { // detects whether is reflection available
			self::$useReflection = (bool) ClassType::from(__CLASS__)->getDocComment();
		}

		if (self::$useReflection) {
			$annotations = self::parseComment($r->getDocComment());

		} else {
			$outerCache = self::getCache();

			if (self::$cache === NULL) {
				self::$cache = (array) $outerCache->load('list');
				self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
			}

			if (!isset(self::$cache[$type]) && $file) {
				self::$cache['*'][$file] = filemtime($file);
				foreach (static::parsePhp(file_get_contents($file)) as $class => $info) {
					foreach ($info as $prop => $comment) {
						if ($prop !== 'use') {
							self::$cache[$class][$prop] = self::parseComment($comment);
						}
					}
				}
				$outerCache->save('list', self::$cache);
			}

			if (isset(self::$cache[$type][$member])) {
				$annotations = self::$cache[$type][$member];
			} else {
				$annotations = array();
			}
		}

		if ($r instanceof \ReflectionMethod && !$r->isPrivate()
			&& (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
		) {
			try {
				$inherited = self::getAll(new \ReflectionMethod(get_parent_class($type), $member));
			} catch (\ReflectionException $e) {
				try {
					$inherited = self::getAll($r->getPrototype());
				} catch (\ReflectionException $e) {
					$inherited = array();
				}
			}
			$annotations += array_intersect_key($inherited, array_flip(self::$inherited));
		}

		return self::$cache[$type][$member] = $annotations;
	}

	/**
	 * Expands class name into FQN.
	 * @param  string
	 * @return string  fully qualified class name
	 * @throws Nette\InvalidArgumentException
	 */
	public static function expandClassName($name, \ReflectionClass $reflector)
	{
		if (empty($name)) {
			throw new Nette\InvalidArgumentException('Class name must not be empty.');

		} elseif ($name === 'self') {
			return $reflector->getName();

		} elseif ($name[0] === '\\') { // already fully qualified
			return ltrim($name, '\\');
		}

		$filename = $reflector->getFileName();
		$parsed = static::getCache()->load($filename, function (& $dp) use ($filename) {
			if (AnnotationsParser::$autoRefresh) {
				$dp[Nette\Caching\Cache::FILES] = $filename;
			}
			return AnnotationsParser::parsePhp(file_get_contents($filename));
		});
		$uses = array_change_key_case((array) $tmp = & $parsed[$reflector->getName()]['use']);
		$parts = explode('\\', $name, 2);
		$parts[0] = strtolower($parts[0]);
		if (isset($uses[$parts[0]])) {
			$parts[0] = $uses[$parts[0]];
			return implode('\\', $parts);

		} elseif ($reflector->inNamespace()) {
			return $reflector->getNamespaceName() . '\\' . $name;

		} else {
			return $name;
		}
	}

	/**
	 * Parses phpDoc comment.
	 * @param  string
	 * @return array
	 */
	private static function parseComment($comment)
	{
		static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);

		$res = array();
		$comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
		$parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);

		$description = trim($parts[0]);
		if ($description !== '') {
			$res['description'] = array($description);
		}

		$matches = Strings::matchAll(
			isset($parts[1]) ? $parts[1] : '',
			'~
				(?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]*      ##  annotation
				(
					\((?>'.self::RE_STRING.'|[^\'")@]+)+\)|  ##  (value)
					[^(@\r\n][^@\r\n]*|)                     ##  value
			~xi'
		);

		foreach ($matches as $match) {
			list(, $name, $value) = $match;

			if (substr($value, 0, 1) === '(') {
				$items = array();
				$key = '';
				$val = TRUE;
				$value[0] = ',';
				while ($m = Strings::match(
					$value,
					'#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
				) {
					$value = substr($value, strlen($m[0]));
					list(, $key, $val) = $m;
					$val = rtrim($val);
					if ($val[0] === "'" || $val[0] === '"') {
						$val = substr($val, 1, -1);

					} elseif (is_numeric($val)) {
						$val = 1 * $val;

					} else {
						$lval = strtolower($val);
						$val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
					}

					if ($key === '') {
						$items[] = $val;

					} else {
						$items[$key] = $val;
					}
				}

				$value = count($items) < 2 && $key === '' ? $val : $items;

			} else {
				$value = trim($value);
				if (is_numeric($value)) {
					$value = 1 * $value;

				} else {
					$lval = strtolower($value);
					$value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
				}
			}

			$res[$name][] = is_array($value) ? Nette\Utils\ArrayHash::from($value) : $value;
		}

		return $res;
	}

	/**
	 * Parses PHP file.
	 * @param  string
	 * @return array [class => [prop => comment (or 'use' => [alias => class])]
	 * @internal
	 */
	public static function parsePhp($code)
	{
		if (Strings::match($code, '#//nette'.'loader=(\S*)#')) {
			return;
		}

		$tokens = @token_get_all($code);
		$namespace = $class = $classLevel = $level = $docComment = NULL;
		$res = $uses = array();

		while (list(, $token) = each($tokens)) {
			switch (is_array($token) ? $token[0] : $token) {
				case T_DOC_COMMENT:
					$docComment = $token[1];
					break;

				case T_NAMESPACE:
					$namespace = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)) . '\\';
					$uses = array();
					break;

				case T_CLASS:
				case T_INTERFACE:
				case PHP_VERSION_ID < 50400 ? -1 : T_TRAIT:
					if ($name = self::fetch($tokens, T_STRING)) {
						$class = $namespace . $name;
						$classLevel = $level + 1;
						$res[$class] = array();
						if ($docComment) {
							$res[$class]['class'] = $docComment;
						}
						if ($uses) {
							$res[$class]['use'] = $uses;
						}
					}
					break;

				case T_FUNCTION:
					self::fetch($tokens, '&');
					if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_STRING))) {
						$res[$class][$name] = $docComment;
					}
					break;

				case T_VAR:
				case T_PUBLIC:
				case T_PROTECTED:
					self::fetch($tokens, T_STATIC);
					if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_VARIABLE))) {
						$res[$class][$name] = $docComment;
					}
					break;

				case T_USE:
					while (!$class && ($name = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)))) {
						if (self::fetch($tokens, T_AS)) {
							$uses[self::fetch($tokens, T_STRING)] = ltrim($name, '\\');
						} else {
							$tmp = explode('\\', $name);
							$uses[end($tmp)] = $name;
						}
						if (!self::fetch($tokens, ',')) {
							break;
						}
					}
					break;

				case T_CURLY_OPEN:
				case T_DOLLAR_OPEN_CURLY_BRACES:
				case '{':
					$level++;
					break;

				case '}':
					if ($level === $classLevel) {
						$class = $classLevel = NULL;
					}
					$level--;
					// break omitted
				case ';':
					$docComment = NULL;
			}
		}

		return $res;
	}

	private static function fetch(& $tokens, $take)
	{
		$res = NULL;
		while ($token = current($tokens)) {
			list($token, $s) = is_array($token) ? $token : array($token, $token);
			if (in_array($token, (array) $take, TRUE)) {
				$res .= $s;
			} elseif (!in_array($token, array(T_DOC_COMMENT, T_WHITESPACE, T_COMMENT), TRUE)) {
				break;
			}
			next($tokens);
		}
		return $res;
	}

	/********************* backend ****************d*g**/

	/**
	 * @return void
	 */
	public static function setCacheStorage(Nette\Caching\IStorage $storage)
	{
		self::$cacheStorage = $storage;
	}

	/**
	 * @return Nette\Caching\IStorage
	 */
	public static function getCacheStorage()
	{
		if (!self::$cacheStorage) {
			self::$cacheStorage = new Nette\Caching\Storages\MemoryStorage();
		}
		return self::$cacheStorage;
	}

	/**
	 * @return Nette\Caching\Cache
	 */
	private static function getCache()
	{
		return new Nette\Caching\Cache(static::getCacheStorage(), 'Nette.Reflection.Annotations');
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a class.
 * @property-read Method $constructor
 * @property-read Extension $extension
 * @property-read ClassType[] $interfaces
 * @property-read Method[] $methods
 * @property-read ClassType $parentClass
 * @property-read Property[] $properties
 * @property-read IAnnotation[][] $annotations
 * @property-read string $description
 * @property-read string $name
 * @property-read bool $internal
 * @property-read bool $userDefined
 * @property-read bool $instantiable
 * @property-read string $fileName
 * @property-read int $startLine
 * @property-read int $endLine
 * @property-read string $docComment
 * @property-read mixed[] $constants
 * @property-read string[] $interfaceNames
 * @property-read bool $interface
 * @property-read bool $abstract
 * @property-read bool $final
 * @property-read int $modifiers
 * @property-read array $staticProperties
 * @property-read array $defaultProperties
 * @property-read bool $iterateable
 * @property-read string $extensionName
 * @property-read string $namespaceName
 * @property-read string $shortName
 */
class ClassType extends \ReflectionClass
{

	/**
	 * @param  string|object
	 * @return self
	 */
	public static function from($class)
	{
		return new static($class);
	}

	public function __toString()
	{
		return $this->getName();
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function is($type)
	{
		return $this->isSubclassOf($type) || strcasecmp($this->getName(), ltrim($type, '\\')) === 0;
	}

	/********************* Reflection layer ****************d*g**/

	/**
	 * @return Method|NULL
	 */
	public function getConstructor()
	{
		return ($ref = parent::getConstructor()) ? Method::from($this->getName(), $ref->getName()) : NULL;
	}

	/**
	 * @return Extension|NULL
	 */
	public function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new Extension($name) : NULL;
	}

	/**
	 * @return self[]
	 */
	public function getInterfaces()
	{
		$res = array();
		foreach (parent::getInterfaceNames() as $val) {
			$res[$val] = new static($val);
		}
		return $res;
	}

	/**
	 * @return Method
	 */
	public function getMethod($name)
	{
		return new Method($this->getName(), $name);
	}

	/**
	 * @return Method[]
	 */
	public function getMethods($filter = -1)
	{
		foreach ($res = parent::getMethods($filter) as $key => $val) {
			$res[$key] = new Method($this->getName(), $val->getName());
		}
		return $res;
	}

	/**
	 * @return self|NULL
	 */
	public function getParentClass()
	{
		return ($ref = parent::getParentClass()) ? new static($ref->getName()) : NULL;
	}

	/**
	 * @return Property[]
	 */
	public function getProperties($filter = -1)
	{
		foreach ($res = parent::getProperties($filter) as $key => $val) {
			$res[$key] = new Property($this->getName(), $val->getName());
		}
		return $res;
	}

	/**
	 * @return Property
	 */
	public function getProperty($name)
	{
		return new Property($this->getName(), $name);
	}

	/********************* Nette\Annotations support ****************d*g**/

	/**
	 * Has class specified annotation?
	 * @param  string
	 * @return bool
	 */
	public function hasAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	/**
	 * Returns an annotation value.
	 * @param  string
	 * @return IAnnotation
	 */
	public function getAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	/**
	 * Returns all annotations.
	 * @return IAnnotation[][]
	 */
	public function getAnnotations()
	{
		return AnnotationsParser::getAll($this);
	}

	/**
	 * Returns value of annotation 'description'.
	 * @return string
	 */
	public function getDescription()
	{
		return $this->getAnnotation('description');
	}

	/********************* Nette\Object behaviour ****************d*g**/

	public function __call($name, $args)
	{
		return ObjectMixin::call($this, $name, $args);
	}

	public function &__get($name)
	{
		return ObjectMixin::get($this, $name);
	}

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

	public function __isset($name)
	{
		return ObjectMixin::has($this, $name);
	}

	public function __unset($name)
	{
		ObjectMixin::remove($this, $name);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a extension.
 */
class Extension extends \ReflectionExtension
{

	public function __toString()
	{
		return $this->getName();
	}

	/********************* Reflection layer ****************d*g**/

	public function getClasses()
	{
		$res = array();
		foreach (parent::getClassNames() as $val) {
			$res[$val] = new ClassType($val);
		}
		return $res;
	}

	public function getFunctions()
	{
		foreach ($res = parent::getFunctions() as $key => $val) {
			$res[$key] = new GlobalFunction($key);
		}
		return $res;
	}

	/********************* Nette\Object behaviour ****************d*g**/

	public function __call($name, $args)
	{
		return ObjectMixin::call($this, $name, $args);
	}

	public function &__get($name)
	{
		return ObjectMixin::get($this, $name);
	}

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

	public function __isset($name)
	{
		return ObjectMixin::has($this, $name);
	}

	public function __unset($name)
	{
		ObjectMixin::remove($this, $name);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a function.
 * @property-read array $defaultParameters
 * @property-read bool $closure
 * @property-read Extension $extension
 * @property-read Parameter[] $parameters
 * @property-read bool $disabled
 * @property-read bool $deprecated
 * @property-read bool $internal
 * @property-read bool $userDefined
 * @property-read string $docComment
 * @property-read int $endLine
 * @property-read string $extensionName
 * @property-read string $fileName
 * @property-read string $name
 * @property-read string $namespaceName
 * @property-read int $numberOfParameters
 * @property-read int $numberOfRequiredParameters
 * @property-read string $shortName
 * @property-read int $startLine
 * @property-read array $staticVariables
 */
class GlobalFunction extends \ReflectionFunction
{
	/** @var string|\Closure */
	private $value;

	public function __construct($name)
	{
		parent::__construct($this->value = $name);
	}

	/**
	 * @deprecated
	 */
	public function toCallback()
	{
		return new Nette\Callback($this->value);
	}

	public function __toString()
	{
		return $this->getName() . '()';
	}

	public function getClosure()
	{
		return PHP_VERSION_ID < 50400
			? Nette\Utils\Callback::closure($this->value)
			: parent::getClosure();
	}

	/********************* Reflection layer ****************d*g**/

	/**
	 * @return Extension
	 */
	public function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new Extension($name) : NULL;
	}

	/**
	 * @return Parameter[]
	 */
	public function getParameters()
	{
		foreach ($res = parent::getParameters() as $key => $val) {
			$res[$key] = new Parameter($this->value, $val->getName());
		}
		return $res;
	}

	/********************* Nette\Annotations support ****************d*g**/

	/**
	 * Has method specified annotation?
	 * @param  string
	 * @return bool
	 */
	public function hasAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	/**
	 * Returns an annotation value.
	 * @param  string
	 * @return IAnnotation
	 */
	public function getAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	/**
	 * Returns all annotations.
	 * @return IAnnotation[][]
	 */
	public function getAnnotations()
	{
		return AnnotationsParser::getAll($this);
	}

	/**
	 * Returns value of annotation 'description'.
	 * @return string
	 */
	public function getDescription()
	{
		return $this->getAnnotation('description');
	}

	/********************* Nette\Object behaviour ****************d*g**/

	public function __call($name, $args)
	{
		return ObjectMixin::call($this, $name, $args);
	}

	public function &__get($name)
	{
		return ObjectMixin::get($this, $name);
	}

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

	public function __isset($name)
	{
		return ObjectMixin::has($this, $name);
	}

	public function __unset($name)
	{
		ObjectMixin::remove($this, $name);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

class Helpers
{

	/**
	 * Returns declaring class or trait.
	 * @return \ReflectionClass
	 * @internal
	 */
	public static function getDeclaringClass(\ReflectionProperty $prop)
	{
		if (PHP_VERSION_ID >= 50400) {
			foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
				if ($trait->hasProperty($prop->getName())) {
					return self::getDeclaringClass($trait->getProperty($prop->getName()));
				}
			}
		}
		return $prop->getDeclaringClass();
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

/**
 * Code annotation.
 */
interface IAnnotation
{

	function __construct(array $values);

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Reflection;

use Nette;
use Nette\Utils\ObjectMixin;

/**
 * Reports information about a method.
 * @property-read array $defaultParameters
 * @property-read ClassType $declaringClass
 * @property-read Method $prototype
 * @property-read Extension $extension
 * @property-read Parameter[] $parameters
 * @property-read IAnnotation[][] $annotations
 * @property-read string $description
 * @property-read bool $public
 * @property-read bool $private
 * @property-read bool $protected
 * @property-read bool $abstract
 * @property-read bool $final
 * @property-read bool $static
 * @property-read bool $constructor
 * @property-read bool $destructor
 * @property-read int $modifiers
 * @property-write bool $accessible
 * @property-read bool $closure
 * @property-read bool $deprecated
 * @property-read bool $internal
 * @property-read bool $userDefined
 * @property-read string $docComment
 * @property-read int $endLine
 * @property-read string $extensionName
 * @property-read string $fileName
 * @property-read string $name
 * @property-read string $namespaceName
 * @property-read int $numberOfParameters
 * @property-read int $numberOfRequiredParameters
 * @property-read string $shortName
 * @property-read int $startLine
 * @property-read array $staticVariables
 */
class Method extends \ReflectionMethod
{

	/**
	 * @param  string|object
	 * @param  string
	 * @return self
	 */
	public static function from($class, $method)
	{
		return new static(is_object($class) ? get_class($class) : $class, $method);
	}

	/**
	 * @deprecated
	 */
	public function toCallback()
	{
		return new Nette\Callback(parent::getDeclaringClass()->getName(), $this->getName());
	}

	public function __toString()
	{
		return parent::getDeclaringClass()->getName() . '::' . $this->getName() . '()';
	}

	public function getClosure($object = NULL)
	{
		return PHP_VERSION_ID < 50400
			? Nette\Utils\Callback::closure($object ?: parent::getDeclaringClass()->getName(), $this->getName())
			: parent::getClosure($object);
	}

	/********************* Reflection layer ****************d*g**/

	/**
	 * @return ClassType
	 */
	public function getDeclaringClass()
	{
		return new ClassType(parent::getDeclaringClass()->getName());
	}

	/**
	 * @return self
	 */
	public function getPrototype()
	{
		$prototype = parent::getPrototype();
		return new static($prototype->getDeclaringClass()->getName(), $prototype->getName());
	}

	/**
	 * @return Extension
	 */
	public function getExtension()
	{
		return ($name = $this->getExtensionName()) ? new Extension($name) : NULL;
	}

	/**
	 * @return Parameter[]
	 */
	public function getParameters()
	{
		$me = array(parent::getDeclaringClass()->getName(), $this->getName());
		foreach ($res = parent::getParameters() as $key => $val) {
			$res[$key] = new Parameter($me, $val->getName());
		}
		return $res;
	}

	/********************* Nette\Annotations support ****************d*g**/

	/**
	 * Has method specified annotation?
	 * @param  string
	 * @return bool
	 */
	public function hasAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return !empty($res[$name]);
	}

	/**
	 * Returns an annotation value.
	 * @param  string
	 * @return IAnnotation
	 */
	public function getAnnotation($name)
	{
		$res = AnnotationsParser::getAll($this);
		return isset($res[$name]) ? end($res[$name]) : NULL;
	}

	/**
	 * Returns all annotations.
	 * @return IAnnotation[][]
	 */
	public function getAnnotations()
	{
		return AnnotationsParser::getAll($this);
	}

	/**
	 * Returns value of annotation 'description'.
	 * @return string
	 */
	public function getDescription()
	{
		return $this->getAnnotation('description');
	}

	/********************* Nette\Object behaviour ****************d*g**/

	public function __call($name, $args)
	{
		return ObjectMixin::call($this, $name, $args);
	}

	public function &__get($name)
	{
		return ObjectMixin::get($this, $name);
	}

	public function __set($name, $value)
	{
		ObjectMixin::set($this, $name, $value);
	}

	public function __isset($name)
	{
		return ObjectMixin::has($this, $name);
	}

	public function __unset($name)
	{
		ObjectMixin::remove($this, $name);
	}

}