Book Image

PHP 7 Programming Cookbook

By : Doug Bierer
Book Image

PHP 7 Programming Cookbook

By: Doug Bierer

Overview of this book

PHP 7 comes with a myriad of new features and great tools to optimize your code and make your code perform faster than in previous versions. Most importantly, it allows you to maintain high traffic on your websites with low-cost hardware and servers through a multithreading web server. This book demonstrates intermediate to advanced PHP techniques with a focus on PHP 7. Each recipe is designed to solve practical, real-world problems faced by PHP developers like yourself every day. We also cover new ways of writing PHP code made possible only in version 7. In addition, we discuss backward-compatibility breaks and give you plenty of guidance on when and where PHP 5 code needs to be changed to produce the correct results when running under PHP 7. This book also incorporates the latest PHP 7.x features. By the end of the book, you will be equipped with the tools and skills required to deliver efficient applications for your websites and enterprises.
Table of Contents (22 chapters)
PHP 7 Programming Cookbook
Credits
Foreword
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Implementing PSR-7 value object classes


In order to work with PSR-7 requests and responses, we first need to define a series of value objects. These are classes that represent logical objects used in web-based activities such as URIs, file uploads, and streaming request or response bodies.

Getting ready

The source code for the PSR-7 interfaces is available as a Composer package. It is considered a best practice to use Composer to manage external software, including PSR-7 interfaces.

How to do it...

  1. First of all, go to the following URL to obtain the latest versions of the PSR-7 interface definitions: https://github.com/php-fig/http-message. The source code is also available. At the time of writing, the following definitions are available:

    Interface

    Extends

    Notes

    What the methods handle

    MessageInterface

     

    Defines methods common to HTTP messages

    Headers, message body (that is, content), and protocol

    RequestInterface

    MessageInterface

    Represents requests generated by a client

    The URI, HTTP method, and the request target

    ServerRequestInterface

    RequestInterface

    Represents a request coming to a server from a client

    Server and query parameters, cookies, uploaded files, and the parsed body

    ResponseInterface

    MessageInterface

    Represents a response from the server to client

    HTTP status code and reason

    StreamInterface

     

    Represents the data stream

    Streaming behavior such as seek, tell, read, write, and so on

    UriInterface

     

    Represents the URI

    Scheme (that is, HTTP, HTTPS), host, port, username, password (that is, for FTP), query parameters, path, and fragment

    UploadedFileInterface

     

    Deals with uploaded files

    File size, media type, moving the file, and filename

  2. Unfortunately, we will need to create concrete classes that implement these interfaces in order to utilize PSR-7. Fortunately, the interface classes are extensively documented internally through a series of comments. We will start with a separate class that contains useful constants:

    Tip

    Note that we take advantage of a new feature introduced in PHP 7 that allows us to define a constant as an array.

    namespace Application\MiddleWare;
    class Constants
    {
      const HEADER_HOST   = 'Host';     // host header
      const HEADER_CONTENT_TYPE = 'Content-Type';
      const HEADER_CONTENT_LENGTH = 'Content-Length';
    
      const METHOD_GET    = 'get';
      const METHOD_POST   = 'post';
      const METHOD_PUT    = 'put';
      const METHOD_DELETE = 'delete';
      const HTTP_METHODS  = ['get','put','post','delete'];
    
      const STANDARD_PORTS = [
        'ftp' => 21, 'ssh' => 22, 'http' => 80, 'https' => 443
      ];
    
      const CONTENT_TYPE_FORM_ENCODED = 
        'application/x-www-form-urlencoded';
      const CONTENT_TYPE_MULTI_FORM   = 'multipart/form-data';
      const CONTENT_TYPE_JSON         = 'application/json';
      const CONTENT_TYPE_HAL_JSON     = 'application/hal+json';
    
      const DEFAULT_STATUS_CODE    = 200;
      const DEFAULT_BODY_STREAM    = 'php://input';
      const DEFAULT_REQUEST_TARGET = '/';
    
      const MODE_READ = 'r';
      const MODE_WRITE = 'w';
    
      // NOTE: not all error constants are shown to conserve space
      const ERROR_BAD = 'ERROR: ';
      const ERROR_UNKNOWN = 'ERROR: unknown';
    
      // NOTE: not all status codes are shown here!
      const STATUS_CODES = [
        200 => 'OK',
        301 => 'Moved Permanently',
        302 => 'Found',
        401 => 'Unauthorized',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        418 => 'I_m A Teapot',
        500 => 'Internal Server Error',
      ];
    }

    Note

    A complete list of HTTP status codes can be found here: https://tools.ietf.org/html/rfc7231#section-6.1.

  3. Next, we will tackle classes that represent value objects used by other PSR-7 classes. For a start, here is the class that represents a URI. In the constructor, we accept a URI string as an argument, and break it down into its component parts using the parse_url() function:

    namespace Application\MiddleWare;
    use InvalidArgumentException;
    use Psr\Http\Message\UriInterface;
    class Uri implements UriInterface
    {
      protected $uriString;
      protected $uriParts = array();
    
      public function __construct($uriString)
      {
        $this->uriParts = parse_url($uriString);
        if (!$this->uriParts) {
          throw new InvalidArgumentException(
            Constants::ERROR_INVALID_URI);
        }
        $this->uriString = $uriString;
      }

    Note

    URI stands for Uniform Resource Indicator. This is what you would see at the top of your browser when making a request. For more information on what comprises a URI, have a look at http://tools.ietf.org/html/rfc3986.

  4. Following the constructor, we define methods to access the component parts of the URI. The scheme represents a PHP wrapper (that is, HTTP, FTP, and so on):

    public function getScheme()
    {
      return strtolower($this->uriParts['scheme']) ?? '';
    }
  5. The authority represents the username (if present), the host, and optionally the port number:

    public function getAuthority()
    {
      $val = '';
      if (!empty($this->getUserInfo()))
      $val .= $this->getUserInfo() . '@';
      $val .= $this->uriParts['host'] ?? '';
      if (!empty($this->uriParts['port']))
      $val .= ':' . $this->uriParts['port'];
      return $val;
    }
  6. User info represents the username (if present) and optionally the password. An example of when a password is used is when accessing an FTP website such as ftp://username:[email protected]:/path:

    public function getUserInfo()
    {
      if (empty($this->uriParts['user'])) {
        return '';
      }
      $val = $this->uriParts['user'];
      if (!empty($this->uriParts['pass']))
        $val .= ':' . $this->uriParts['pass'];
      return $val;
    }
  7. Host is the DNS address included in the URI:

    public function getHost()
    {
      if (empty($this->uriParts['host'])) {
        return '';
      }
      return strtolower($this->uriParts['host']);
    }
  8. Port is the HTTP port, if present. You will note if a port is listed in our STANDARD_PORTS constant, the return value is NULL, according to the requirements of PSR-7:

    public function getPort()
    {
      if (empty($this->uriParts['port'])) {
          return NULL;
      } else {
          if ($this->getScheme()) {
              if ($this->uriParts['port'] == 
                  Constants::STANDARD_PORTS[$this->getScheme()]) {
                  return NULL;
              }
          }
          return (int) $this->uriParts['port'];
      }
    }
  9. Path is the part of the URI that follows the DNS address. According to PSR-7, this must be encoded. We use the rawurlencode() PHP function as it is compliant with RFC 3986. We cannot just encode the entire path, however, as the path separator (that is, /) would also get encoded! Accordingly, we need to first break it up using explode(), encode the parts, and then reassemble it:

    public function getPath()
    {
      if (empty($this->urlParts['path'])) {
        return '';
      }
      return implode('/', array_map("rawurlencode", explode('/', $this->urlParts['path'])));
    }
  10. Next, we define a method to retrieve the query string (that is, from $_GET). These too must be URL-encoded. First, we define getQueryParams(), which breaks the query string into an associative array. You will note the reset option in case we wish to refresh the query parameters. We then define getQuery(), which takes the array and produces a proper URL-encoded string:

    public function getQueryParams($reset = FALSE)
    {
      if ($this->queryParams && !$reset) {
        return $this->queryParams;
      }
      $this->queryParams = [];
      if (!empty($this->uriParts['query'])) {
        foreach (explode('&', $this->uriParts['query']) as $keyPair) {
          list($param,$value) = explode('=',$keyPair);
          $this->queryParams[$param] = $value;
        }
      }
      return $this->queryParams;
    }
    
    public function getQuery()
    {
      if (!$this->getQueryParams()) {
        return '';
      }
      $output = '';
      foreach ($this->getQueryParams() as $key => $value) {
        $output .= rawurlencode($key) . '=' 
        . rawurlencode($value) . '&';
      }
      return substr($output, 0, -1);
    }
  11. After that, we provide a method to return the fragment (that is, a # in the URI), and any part following it:

    public function getFragment()
    {
      if (empty($this->urlParts['fragment'])) {
        return '';
      }
      return rawurlencode($this->urlParts['fragment']);
    }
  12. Next, we define a series of withXXX() methods, which match the getXXX() methods described above. These methods are designed to add, replace, or remove properties associated with the request class (scheme, authority, user info, and so on). In addition, these methods return the current instance that allows us to use these methods in a series of successive calls (often referred to as the fluent interface). We start with withScheme():

    Note

    You will note that an empty argument, according to PSR-7, signals the removal of that property. You will also note that we do not allow a scheme that does not match what is defined in our Constants::STANDARD_PORTS array.

    public function withScheme($scheme)
    {
      if (empty($scheme) && $this->getScheme()) {
          unset($this->uriParts['scheme']);
      } else {
          if (isset(STANDARD_PORTS[strtolower($scheme)])) {
              $this->uriParts['scheme'] = $scheme;
          } else {
              throw new InvalidArgumentException(Constants::ERROR_BAD . __METHOD__);
          }
      }
      return $this;
    }
  13. We then apply similar logic to methods that overwrite, add, or replace the user info, host, port, path, query, and fragment. Note that the withQuery() method resets the query parameters array. withHost(), withPort(), withPath(), and withFragment() use the same logic, but are not shown to conserve space:

    public function withUserInfo($user, $password = null)
    {
      if (empty($user) && $this->getUserInfo()) {
          unset($this->uriParts['user']);
      } else {
          $this->urlParts['user'] = $user;
          if ($password) {
              $this->urlParts['pass'] = $password;
          }
      }
      return $this;
    }
    // Not shown: withHost(),withPort(),withPath(),withFragment()
    
    public function withQuery($query)
    {
      if (empty($query) && $this->getQuery()) {
          unset($this->uriParts['query']);
      } else {
          $this->uriParts['query'] = $query;
      }
      // reset query params array
      $this->getQueryParams(TRUE);
      return $this;
    }
  14. Finally, we wrap up the Application\MiddleWare\Uri class with __toString(), which, when the object is used in a string context, returns a proper URI, assembled from $uriParts. We also define a convenience method, getUriString(), that simply calls __toString():

    public function __toString()
    {
        $uri = ($this->getScheme())
          ? $this->getScheme() . '://' : '';
  15. If the authority URI part is present, we add it. authority includes the user information, host, and port. Otherwise, we just append host and port:

    if ($this->getAuthority()) {
        $uri .= $this->getAuthority();
    } else {
        $uri .= ($this->getHost()) ? $this->getHost() : '';
        $uri .= ($this->getPort())
          ? ':' . $this->getPort() : '';
    }
  16. Before adding path, we first check whether the first character is /. If not, we need to add this separator. We then add query and fragment, if present:

    $path = $this->getPath();
    if ($path) {
        if ($path[0] != '/') {
            $uri .= '/' . $path;
        } else {
            $uri .= $path;
        }
    }
    $uri .= ($this->getQuery())
      ? '?' . $this->getQuery() : '';
    $uri .= ($this->getFragment())
      ? '#' . $this->getFragment() : '';
    return $uri;
    }
    
    public function getUriString()
    {
      return $this->__toString();
    }
    
    }

    Note

    Note the use of string dereferencing (that is, $path[0]), now part of PHP 7.

  17. Next, we turn our attention to a class that represents the body of the message. As it is not known how large the body might be, PSR-7 recommends that the body should be treated as a stream. A stream is a resource that allows access to input and output sources in a linear fashion. In PHP, all file commands operate on top of the Streams sub-system, so this is a natural fit. PSR-7 formalizes this by way of Psr\Http\Message\StreamInterface that defines such methods as read(), write(), seek(), and so on. We now present Application\MiddleWare\Stream that we can use to represent the body of incoming or outgoing requests and/or responses:

    namespace Application\MiddleWare;
    use SplFileInfo;
    use Throwable;
    use RuntimeException;
    use Psr\Http\Message\StreamInterface;
    class Stream implements StreamInterface
    {
      protected $stream;
      protected $metadata;
      protected $info;
  18. In the constructor, we open the stream using a simple fopen() command. We then use stream_get_meta_data() to get information on the stream. For other details, we create an SplFileInfo instance:

    public function __construct($input, $mode = self::MODE_READ)
    {
      $this->stream = fopen($input, $mode);
      $this->metadata = stream_get_meta_data($this->stream);
      $this->info = new SplFileInfo($input);
    }

    Note

    The reason why we chose fopen() over the more modern SplFileObject is that the latter does not allow direct access to the inner file resource object, and is therefore useless for this application.

  19. We include two convenience methods that provide access to the resource, as well as access to the SplFileInfo instance:

    public function getStream()
    {
      return $this->stream;
    }
    
    public function getInfo()
    {
      return $this->info;
    }
  20. Next, we define low-level core streaming methods:

    public function read($length)
    {
      if (!fread($this->stream, $length)) {
          throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
    }
    public function write($string)
    {
      if (!fwrite($this->stream, $string)) {
          throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
    }
    public function rewind()
    {
      if (!rewind($this->stream)) {
          throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
    }
    public function eof()
    {
      return eof($this->stream);
    }
    public function tell()
    {
      try {
          return ftell($this->stream);
      } catch (Throwable $e) {
          throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
    }
    public function seek($offset, $whence = SEEK_SET)
    {
      try {
          fseek($this->stream, $offset, $whence);
      } catch (Throwable $e) {
          throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
    }
    public function close()
    {
      if ($this->stream) {
        fclose($this->stream);
      }
    }
    public function detach()
    {
      return $this->close();
    }
  21. We also need to define informational methods that tell us about the stream:

    public function getMetadata($key = null)
    {
      if ($key) {
          return $this->metadata[$key] ?? NULL;
      } else {
          return $this->metadata;
      }
    }
    public function getSize()
    {
      return $this->info->getSize();
    }
    public function isSeekable()
    {
      return boolval($this->metadata['seekable']);
    }
    public function isWritable()
    {
      return $this->stream->isWritable();
    }
    public function isReadable()
    {
      return $this->info->isReadable();
    }
  22. Following PSR-7 guidelines, we then define getContents() and __toString() in order to dump the contents of the stream:

    public function __toString()
    {
      $this->rewind();
      return $this->getContents();
    }
    
    public function getContents()
    {
      ob_start();
      if (!fpassthru($this->stream)) {
        throw new RuntimeException(self::ERROR_BAD . __METHOD__);
      }
      return ob_get_clean();
    }
    }
  23. An important variation of the Stream class shown previously is TextStream that is designed for situations where the body is a string (that is, an array encoded as JSON) rather than a file. As we need to make absolutely certain that the incoming $input value is of the string data type, we invoke PHP 7 strict types just after the opening tag. We also identify a $pos property (that is, position) that will emulate a file pointer, but instead point to a position within the string:

    <?php
    declare(strict_types=1);
    namespace Application\MiddleWare;
    use Throwable;
    use RuntimeException;
    use SplFileInfo;
    use Psr\Http\Message\StreamInterface;
    
    class TextStream implements StreamInterface
    {
      protected $stream;
      protected $pos = 0;
  24. Most of the methods are quite simple and self-explanatory. The $stream property is the input string:

    public function __construct(string $input)
    {
      $this->stream = $input;
    }
    public function getStream()
    {
      return $this->stream;
    }
      public function getInfo()
    {
      return NULL;
    }
    public function getContents()
    {
      return $this->stream;
    }
    public function __toString()
    {
      return $this->getContents();
    }
    public function getSize()
    {
      return strlen($this->stream);
    }
    public function close()
    {
      // do nothing: how can you "close" string???
    }
    public function detach()
    {
      return $this->close();  // that is, do nothing!
    }
  25. To emulate streaming behavior, tell(), eof(), seek(), and so on, work with $pos:

    public function tell()
    {
      return $this->pos;
    }
    public function eof()
    {
      return ($this->pos == strlen($this->stream));
    }
    public function isSeekable()
    {
      return TRUE;
    }
    public function seek($offset, $whence = NULL)
    {
      if ($offset < $this->getSize()) {
          $this->pos = $offset;
      } else {
          throw new RuntimeException(
            Constants::ERROR_BAD . __METHOD__);
      }
    }
    public function rewind()
    {
      $this->pos = 0;
    }
    public function isWritable()
    {
      return TRUE;
    }
  26. The read() and write() methods work with $pos and substrings:

    public function write($string)
    {
      $temp = substr($this->stream, 0, $this->pos);
      $this->stream = $temp . $string;
      $this->pos = strlen($this->stream);
    }
    
    public function isReadable()
    {
      return TRUE;
    }
    public function read($length)
    {
      return substr($this->stream, $this->pos, $length);
    }
    public function getMetadata($key = null)
    {
      return NULL;
    }
    
    }
  27. The last of the value objects to be presented is Application\MiddleWare\UploadedFile. As with the other classes, we first define properties that represent aspects of a file upload:

    namespace Application\MiddleWare;
    use RuntimeException;
    use InvalidArgumentException;
    use Psr\Http\Message\UploadedFileInterface;
    class UploadedFile implements UploadedFileInterface
    {
    
      protected $field;   // original name of file upload field
      protected $info;    // $_FILES[$field]
      protected $randomize;
      protected $movedName = '';
  28. In the constructor, we allow the definition of the name attribute of the file upload form field, as well as the corresponding array in $_FILES. We add the last parameter to signal whether or not we want the class to generate a new random filename once the uploaded file is confirmed:

    public function __construct($field, array $info, $randomize = FALSE)
    {
      $this->field = $field;
      $this->info = $info;
      $this->randomize = $randomize;
    }
  29. Next, we create a Stream class instance for the temporary or moved file:

    public function getStream()
    {
      if (!$this->stream) {
          if ($this->movedName) {
              $this->stream = new Stream($this->movedName);
          } else {
              $this->stream = new Stream($info['tmp_name']);
          }
      }
      return $this->stream;
    }
  30. The moveTo() method performs the actual file movement. Note the extensive series of safety checks to help prevent an injection attack. If randomize is not enabled, we use the original user-supplied filename:

    public function moveTo($targetPath)
    {
      if ($this->moved) {
          throw new Exception(Constants::ERROR_MOVE_DONE);
      }
      if (!file_exists($targetPath)) {
          throw new InvalidArgumentException(Constants::ERROR_BAD_DIR);
      }
      $tempFile = $this->info['tmp_name'] ?? FALSE;
      if (!$tempFile || !file_exists($tempFile)) {
          throw new Exception(Constants::ERROR_BAD_FILE);
      }
      if (!is_uploaded_file($tempFile)) {
          throw new Exception(Constants::ERROR_FILE_NOT);
      }
      if ($this->randomize) {
          $final = bin2hex(random_bytes(8)) . '.txt';
      } else {
          $final = $this->info['name'];
      }
      $final = $targetPath . '/' . $final;
      $final = str_replace('//', '/', $final);
      if (!move_uploaded_file($tempFile, $final)) {
          throw new RuntimeException(Constants::ERROR_MOVE_UNABLE);
      }
      $this->movedName = $final;
      return TRUE;
    }
  31. We then provide access to the other parameters returned in $_FILES from the $info property. Please note that the return values from getClientFilename() and getClientMediaType() should be considered untrusted, as they originate from the outside. We also add a method to return the moved filename:

    public function getMovedName()
    {
      return $this->movedName ?? NULL;
    }
    public function getSize()
    {
      return $this->info['size'] ?? NULL;
    }
    public function getError()
    {
      if (!$this->moved) {
          return UPLOAD_ERR_OK;
      }
      return $this->info['error'];
    }
    public function getClientFilename()
    {
      return $this->info['name'] ?? NULL;
    }
    public function getClientMediaType()
    {
      return $this->info['type'] ?? NULL;
    }
    
    }

How it works...

First of all, go to https://github.com/php-fig/http-message/tree/master/src, the GitHub repository for the PSR-7 interfaces, and download them. Create a directory called Psr/Http/Message in /path/to/source and places the files there. Alternatively, you can visit https://packagist.org/packages/psr/http-message and install the source code using Composer. (For instructions on how to obtain and use Composer, you can visit https://getcomposer.org/.)

Then, go ahead and define the classes discussed previously, summarized in this table:

Class

Steps discussed in

Application\MiddleWare\Constants

2

Application\MiddleWare\Uri

3 to 16

Application\MiddleWare\Stream

17 to 22

Application\MiddleWare\TextStream

23 to 26

Application\MiddleWare\UploadedFile

27 to 31

Next, define a chap_09_middleware_value_objects_uri.php calling program that implements autoloading and uses the appropriate classes. Please note that if you use Composer, unless otherwise instructed, it will create a folder called vendor. Composer also adds its own autoloader, which you are free to use here:

<?php
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\MiddleWare\Uri;

You can then create a Uri instance and use the with methods to add parameters. You can then echo the Uri instance directly as __toString() is defined:

$uri = new Uri();
$uri->withScheme('https')
    ->withHost('localhost')
    ->withPort('8080')
    ->withPath('chap_09_middleware_value_objects_uri.php')
    ->withQuery('param=TEST');

echo $uri;

Here is the expected result:

Next, create a directory called uploads from /path/to/source/for/this/chapter. Go ahead and define another calling program, chap_09_middleware_value_objects_file_upload.php, that sets up autoloading and uses the appropriate classes:

<?php
define('TARGET_DIR', __DIR__ . '/uploads');
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\MiddleWare\UploadedFile;

Inside a try...catch block, check to see whether any files were uploaded. If so, loop through $_FILES and create UploadedFile instances where tmp_name is set. You can then use the moveTo() method to move the files to TARGET_DIR:

try {
    $message = '';
    $uploadedFiles = array();
    if (isset($_FILES)) {
        foreach ($_FILES as $key => $info) {
          if ($info['tmp_name']) {
              $uploadedFiles[$key] = new UploadedFile($key, $info, TRUE);
              $uploadedFiles[$key]->moveTo(TARGET_DIR);
          }
        }
    }
} catch (Throwable $e) {
    $message =  $e->getMessage();
}
?>

In the view logic, display a simple file upload form. You could also use phpinfo() to display information about what was uploaded:

<form name="search" method="post" enctype="<?= Constants::CONTENT_TYPE_MULTI_FORM ?>">
<table class="display" cellspacing="0" width="100%">
    <tr><th>Upload 1</th><td><input type="file" name="upload_1" /></td></tr>
    <tr><th>Upload 2</th><td><input type="file" name="upload_2" /></td></tr>
    <tr><th>Upload 3</th><td><input type="file" name="upload_3" /></td></tr>
    <tr><th>&nbsp;</th><td><input type="submit" /></td></tr>
</table>
</form>
<?= ($message) ? '<h1>' . $message . '</h1>' : ''; ?>

Next, if there were any uploaded files, you can display information on each one. You can also use getStream() followed by getContents() to display each file (assuming you're using short text files):

<?php if ($uploadedFiles) : ?>
<table class="display" cellspacing="0" width="100%">
    <tr>
        <th>Filename</th><th>Size</th>
      <th>Moved Filename</th><th>Text</th>
    </tr>
    <?php foreach ($uploadedFiles as $obj) : ?>
        <?php if ($obj->getMovedName()) : ?>
        <tr>
            <td><?= htmlspecialchars($obj->getClientFilename()) ?></td>
            <td><?= $obj->getSize() ?></td>
            <td><?= $obj->getMovedName() ?></td>
            <td><?= $obj->getStream()->getContents() ?></td>
        </tr>
        <?php endif; ?>
    <?php endforeach; ?>
</table>
<?php endif; ?>
<?php phpinfo(INFO_VARIABLES); ?>

Here is how the output might appear:

See also