Overview

Namespaces

  • esperecyan
    • webidl
      • lib

Classes

  • Record
  • TypeError
  • TypeHinter

Interfaces

  • Error

Exceptions

  • ErrorClass
  • EvalError
  • RangeError
  • ReferenceError
  • URIError
  • Overview
  • Namespace
  • Class
  • Tree
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 
<?php
namespace esperecyan\webidl;

class TypeHinter
{
    use lib\Utility;

    /**
     * Converts a given value in accordance with a given IDL type.
     * @link https://github.com/esperecyan/webidl/blob/master/README.md#esperecyanwebidltypehintertotype-value-argnum-pseudotypes webidl/README.md at master · esperecyan/webidl
     * @param string $type The IDL type.
     * @param mixed $value The value being converted.
     * @param int $argNum The argument offset that received the value being converted.
     *      Arguments are counted starting from zero.  If the caller method is __set(), this argument are ignored.
     * @param (string|string[]|array)[] The associative array with the identifiers of
     *      callback interface types, enumeration types, callback function types or dictionary types
     *      (the strings passed in $type) as key.
     *      For the corresponding values, see the link.
     * @throws \InvalidArgumentException
     *      If $value is not castable. The exception includes a message with method name etc.
     * @throws \DomainException If $value is not castable. The exception includes a message with method name etc.
     * @return mixed
     */
    public static function to($type, $value, $argNum = 0, $pseudoTypes = [])
    {
        try {
            return lib\Type::to($type, $value, $pseudoTypes);
        } catch (\LogicException $exception) {
            $callerInfomation = self::getCallerInfomation();
            $errorMessage = $callerInfomation['function'] === '__set'
                ? sprintf(
                    'Value set to %s is not of the expected type',
                    $callerInfomation['class'] . '::' . $callerInfomation['args'][0]
                )
                : sprintf(
                    'Argument %d passed to %s is not of the expected type',
                    $argNum + 1,
                    $callerInfomation['class'] . '::' . $callerInfomation['function'] . '()'
                );
            if ($exception instanceof \DomainException) {
                throw new \DomainException($errorMessage, 0, $exception);
            } elseif ($exception instanceof \InvalidArgumentException) {
                throw new \InvalidArgumentException($errorMessage, 0, $exception);
            } else {
                throw $exception;
            }
        }
    }

    /**
     * Throws an exception with a message that represents a read-only property. Must call from __set() method.
     * @link https://github.com/esperecyan/webidl/blob/master/README.md#esperecyanwebidltypehinterthrowreadonlyexception webidl/README.md at master · esperecyan/webidl
     * @link https://www.w3.org/TR/WebIDL-1/#dfn-read-only WebIDL Level 1
     * @link https://wiki.php.net/rfc/propertygetsetsyntax#read-only_and_write-only_properties PHP: rfc:propertygetsetsyntax
     * @throws \BadMethodCallException 呼び出し元のメソッドが __set() 以外の場合。
     * @throws \LogicException An exception with a message that represents a read-only property.
     */
    public static function throwReadonlyException()
    {
        $callerInfomation = self::getCallerInfomation();
        if ($callerInfomation['function'] === '__set') {
            throw new \LogicException(sprintf(
                'Cannot write to readonly public property %s',
                $callerInfomation['class'] . '::' . $callerInfomation['args'][0]
            ));
        } else {
            throw new \BadMethodCallException(sprintf('%s must call inside a %s method', __METHOD__ . '()', '__set()'));
        }
    }

    /**
     * If a user tries setting to a private or protected property, it will trigger a fatal error.
     * If a user tries setting to a non-existing property, it will create a new public property.
     * Must call from __set() method.
     * @link https://github.com/esperecyan/webidl/blob/master/README.md#esperecyanwebidltypehinterthrowreadonlyexception webidl/README.md at master · esperecyan/webidl
     * @link https://www.w3.org/TR/WebIDL-1/#dfn-read-only WebIDL Level 1
     * @link https://wiki.php.net/rfc/propertygetsetsyntax#read-only_and_write-only_properties PHP: rfc:propertygetsetsyntax
     * @throws \BadMethodCallException 呼び出し元のメソッドが __set() 以外の場合。
     */
    public static function triggerVisibilityErrorOrDefineProperty()
    {
        $callerInfomation = self::getCallerInfomation();
        if ($callerInfomation['function'] === '__set') {
            if (!self::triggerVisibilityError($callerInfomation['class'], $callerInfomation['args'][0])) {
                \Closure::bind(function ($instance, $name, $value) {
                    $instance->{$name} = $value;
                }, null, $callerInfomation['object'])->__invoke(
                    $callerInfomation['object'],
                    $callerInfomation['args'][0],
                    $callerInfomation['args'][1]
                );
            }
        } else {
            throw new \BadMethodCallException(sprintf('%s must call inside a %s method', __METHOD__ . '()', '__set()'));
        }
    }

    /**
     * If a user tries setting to a private or protected property, it will trigger a fatal error.
     * If a user tries getting to a non-existing property, it will trigger a notice. Must call from __get() method.
     * @link https://github.com/esperecyan/webidl/blob/master/README.md#esperecyanwebidltypehintertriggervisibilityerrororundefinednotice webidl/README.md at master · esperecyan/webidl
     * @throws \BadMethodCallException 呼び出し元のメソッドが __get() 以外の場合。
     */
    public static function triggerVisibilityErrorOrUndefinedNotice()
    {
        $callerInfomation = self::getCallerInfomation();
        if ($callerInfomation['function'] === '__get') {
            if (!self::triggerVisibilityError($callerInfomation['class'], $callerInfomation['args'][0])) {
                trigger_error(sprintf(
                    'Undefined property: %s',
                    $callerInfomation['class'] . '::' . $callerInfomation['args'][0]
                ), E_USER_NOTICE);
            }
        } else {
            throw new \BadMethodCallException(sprintf('%s must call inside a %s method', __METHOD__ . '()', '__get()'));
        }
    }
    
    /**
     * アクセス権エラーを発生させる。
     * @param string $className 完全修飾クラス名。
     * @param string $propertyName プロパティ名 (クラス名を含まない)。
     * @return bool 成功した場合に真を、プロパティが存在しなかった場合に偽を返します。
     */
    private static function triggerVisibilityError($className, $propertyName)
    {
        $existed = property_exists($className, $propertyName);
        if ($existed) {
            trigger_error(sprintf(
                'Cannot access %s property %s',
                (new \ReflectionProperty($className, $propertyName))->isPrivate() ? 'private' : 'protected',
                $className . '::' . $propertyName
            ), E_USER_ERROR);
        }
        return $existed;
    }
    
    /**
     * 呼び出し元のメソッドから getCallerInfomation() を実行するまでに含まれうる最大のスタックフレーム数。
     * @internal
     */
    const STACK_FRAME_LIMIT = 5;
    
    /**
     * 呼び出し元のメソッドと引数の値を取得します。
     * @throws \BadMethodCallException クラスに属さない関数、または関数やメソッドの外から呼び出した場合。
     * @return (string|null|array|object)[]|null debug_backtrace() が返す要素と同じ。
     *      ただし function キーは、トレイトのエイリアス名ではなく元のメソッド名を返します。
     */
    private static function getCallerInfomation()
    {
        foreach (debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, self::STACK_FRAME_LIMIT) as $stack) {
            if (isset($stack['class']) && $stack['class'] !== __CLASS__) {
                $stack['function'] = (new \ReflectionMethod($stack['class'], $stack['function']))->name;
                return $stack;
            }
        }
        
        throw new \BadMethodCallException(sprintf('%s must use inside a method', __CLASS__));
    }
}
esperecyan/webidl documentation API documentation generated by ApiGen