/** * Finishes the classes. That means, inheritance will be performed and missing * constructors will be added. */ public function finalize() { foreach ($this->env->get_types()->get_classes() as $c) { if ($c->get_pid() != $this->env->get_options()->get_current_project()) { continue; } /* @var $c PC_Obj_Class */ $this->add_members($c, $c->get_name()); // add missing constructor if (!$c->is_interface() && $c->get_method('__construct') === null) { $method = new PC_Obj_Method($c->get_file(), -1, false); $method->set_name('__construct'); $method->set_visibility(PC_Obj_Visible::V_PUBLIC); $c->add_method($method); $this->env->get_storage()->create_function($method, $c->get_id()); } } }
/** * Determines the type of the given function-parameter * * @param PC_Obj_Method $func the function/method * @param string $varname the variable-name * @return PC_Obj_MultiType the type */ private function get_funcparam_type($func, $varname) { if ($func === null) { return null; } $param = $func->get_param($varname); if ($param === null) { return null; } return $param->get_mtype(); }
/** * Checks the parameters of the call against the given ones * * @param PC_Obj_Call $call the call * @param PC_Obj_Method $method the method */ private function check_params($call, $method) { $arguments = $call->get_arguments(); $nparams = $method->get_required_param_count(); $nmaxparams = $method->get_param_count(); if (count($arguments) < $nparams || $nmaxparams >= 0 && count($arguments) > $nmaxparams) { if ($nparams != $nmaxparams) { $reqparams = $nparams . ' to ' . ($nmaxparams == -1 ? '*' : $nmaxparams); } else { $reqparams = $nparams; } $this->report($call, 'The function/method called by "' . $this->get_call_link($call) . '" requires ' . $reqparams . ' arguments but you have given ' . count($arguments), PC_Obj_Error::E_S_WRONG_ARGUMENT_COUNT); } else { $i = 0; foreach ($method->get_params() as $param) { /* @var $param PC_Obj_Parameter */ $arg = isset($arguments[$i]) ? $arguments[$i] : null; // arg- or param-type unknown? if ($arg === null || $arg->is_unknown() || $param->get_mtype()->is_unknown()) { $i++; continue; } if (!$this->is_argument_ok($call, $arg, $param)) { $trequired = $param->get_mtype(); $tactual = $arg; $this->report($call, 'The parameter ' . ($i + 1) . ' in "' . $this->get_call_link($call) . '" requires ' . $this->get_article($trequired) . ' "' . $trequired . '" but you have given ' . $this->get_article($tactual) . ' "' . ($tactual === null ? "<i>NULL</i>" : $tactual) . '"', PC_Obj_Error::E_S_WRONG_ARGUMENT_TYPE); } $i++; } } }
/** * Parses the given method-phpdoc * * @param PC_Obj_Method $func the method to which the phpdoc belongs */ public function parse_method_doc($func) { if (isset($this->funcComments[$func->get_name()])) { $doc = $this->funcComments[$func->get_name()]; // look for params $matches = array(); preg_match_all('/\\@param\\s+([^\\s]+)\\s+(&)?\\s*([^\\s]+)/', $doc, $matches); foreach ($matches[1] as $k => $match) { $param = substr($matches[3][$k], 1); // does the param exist? if (($fp = $func->get_param($param)) !== null) { $mtype = PC_Obj_MultiType::get_type_by_name($match); $fptype = $fp->get_mtype(); $isref = $matches[2][$k] != ''; // don't report that if we only know the type from the default value if ($fp->is_reference() != $isref || !$fp->is_mtype_default() && !$fptype->is_unknown() && !$fptype->equals($mtype)) { $docstr = ($isref ? '&' : '') . $mtype; $this->report_error('PHPDoc (' . $docstr . ') does not match the parameter $' . $param . ' (' . $fp . ')', PC_Obj_Error::E_T_PARAM_DIFFERS_FROM_DOC, $func->get_line()); } $fp->set_mtype($mtype); $fp->set_has_doc(true); } else { $this->report_error('Found PHPDoc for parameter "' . $param . '" (' . $match . '),' . ' but the parameter does not exist', PC_Obj_Error::E_T_DOC_WITHOUT_PARAM, $func->get_line()); } } // look for throws preg_match_all('/\\@throws\\s+([^\\s]+)/', $doc, $matches); foreach ($matches[1] as $k => $match) { $func->add_throw($match, PC_Obj_Method::THROW_SELF); } // look for return-type if (preg_match('/\\@return\\s+&?\\s*([^\\s]+)/', $doc, $matches)) { $mtype = PC_Obj_MultiType::get_type_by_name($matches[1]); if ($mtype !== null) { $rettype = $func->get_return_type(); if ($rettype && !$rettype->is_unknown() && !$rettype->equals($mtype)) { $this->report_error('PHPDoc (' . $mtype . ') does not match the return type (' . $rettype . ')', PC_Obj_Error::E_T_RETURN_DIFFERS_FROM_DOC, $func->get_line()); } if (!$rettype || $rettype->is_unknown()) { $func->set_return_type($mtype); } $func->set_has_return_doc(true); } } unset($this->funcComments[$func->get_name()]); } }
/** * Checks if the given method is "magic". If so it adds automatically parameter-types (if not * already set). * * @param string $classname the class-name * @param PC_Obj_Method $method the method * @return bool true if handled */ private function handle_magic($classname, $method) { // the magic-methods: // public void __set ( string $name , mixed $value ) // public mixed __get ( string $name ) // public bool __isset ( string $name ) // public void __unset ( string $name ) // public mixed __call ( string $name , array $arguments ) // public mixed __callStatic ( string $name , array $arguments ) // * array __sleep( void ) // * void__wakeup( void ) // public string __toString( void ) // public void __invoke( ... ) // public static object __set_state( array $props ) // * void __clone( void ) $ismagic = true; $visibility = PC_Obj_Visible::V_PUBLIC; $static = false; $expected = null; switch (strtolower($method->get_name())) { case '__set': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string()), new PC_Obj_Parameter('value', new PC_Obj_MultiType())); break; case '__isset': case '__unset': case '__get': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string())); break; case '__call': case '__callstatic': $expected = array(new PC_Obj_Parameter('name', PC_Obj_MultiType::create_string()), new PC_Obj_Parameter('arguments', PC_Obj_MultiType::create_array())); break; case '__tostring': $expected = array(); break; case '__sleep': case '__wakeup': case '__clone': $visibility = null; // may be private or protected $expected = array(); break; case '__invoke': $static = true; break; case '__set_state': $expected = array(new PC_Obj_Parameter('props', PC_Obj_MultiType::create_array())); break; default: $ismagic = false; break; } if (!$ismagic) { return false; } if ($visibility !== null && $method->get_visibility() != $visibility) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should be public', PC_Obj_Error::E_T_MAGIC_NOT_PUBLIC); } if ($static && !$method->is_static()) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should be static', PC_Obj_Error::E_T_MAGIC_NOT_STATIC); } else { if (!$static && $method->is_static()) { $this->report($method, 'The magic method "#' . $classname . '#::' . $method->get_name() . '" should not be static', PC_Obj_Error::E_T_MAGIC_IS_STATIC); } } if ($expected !== null) { $changed = false; if ($this->check_params($classname, $method, $expected, $method->get_params())) { // set parameter-descriptions $i = 0; foreach ($method->get_params() as $param) { $param->set_mtype($expected[$i++]->get_mtype()); $param->set_has_doc(true); $changed = true; } } $return = new PC_Obj_MultiType(); switch (strtolower($method->get_name())) { case '__set': case '__unset': case '__wakeup': case '__clone': $return = PC_Obj_MultiType::create_void(); break; case '__set_state': $return = PC_Obj_MultiType::create_object(); break; case '__isset': $return = PC_Obj_MultiType::create_bool(); break; case '__sleep': $return = PC_Obj_MultiType::create_array(); break; case '__tostring': $return = PC_Obj_MultiType::create_string(); break; } if ($method->has_return_doc() && !$this->env->get_types()->is_type_conforming($method->get_return_type(), $return)) { // its ok to specify a more specific return-value if the expected one is "mixed" if (!$return->is_unknown()) { $this->report($method, 'The return-type of the magic-method "#' . $classname . '#::' . $method->get_name() . '" is invalid ' . '(expected="' . $return . '", found="' . $method->get_return_type() . '")', PC_Obj_Error::E_T_MAGIC_METHOD_RET_INVALID); } } if ($return !== null && !$method->has_return_doc()) { $method->set_return_type($return); $method->set_has_return_doc(true); $changed = true; } if ($changed) { $this->env->get_storage()->update_function($method, $method->get_class()); } } return true; }
/** * Builds a PC_Obj_Method from the given row * * @param array $row the row from db * @return PC_Obj_Method the method */ private function build_func($row) { $c = new PC_Obj_Method($row['file'], $row['line'], $row['class'] == 0, $row['id'], $row['class']); $c->set_name($row['name']); $c->set_visibility($row['visibility']); $c->set_abstract($row['abstract']); $c->set_static($row['static']); $c->set_anonymous($row['anonymous']); $c->set_final($row['final']); $c->get_version()->set(unserialize($row['min_version']), unserialize($row['max_version'])); foreach (unserialize($row['params']) as $param) { $c->put_param($param); } list($hasretdoc, $rettype) = unserialize($row['return_type']); $throws = unserialize($row['throws']); if (is_array($throws)) { foreach ($throws as $class => $type) { $c->add_throw($class, $type); } } $c->set_has_return_doc($hasretdoc); $c->set_return_type($rettype); return $c; }
/** * Parses a method-description into a PC_Obj_Method * * @param string $file the filename * @param string $desc the description * @return array an array of the class-name and the PC_Obj_Method * @throws PC_PHPRef_Exception if it failed */ public static function parse_method_desc($file, $desc) { $classname = ''; // find link to method if (preg_match('/<a href="(.*?)" class="methodname">/', $desc, $m)) { $file = dirname($file) . '/' . $m[1]; } // prepare description $desc = trim(strip_tags($desc)); $desc = FWS_StringHelper::htmlspecialchars_back($desc); // filter out modifier, return-type, name and params $match = array(); $res = preg_match('/^(?:(abstract|static|final|public|protected|private)\\s*)?' . '(?:(abstract|static|final|public|protected|private)\\s*)?' . '(?:(abstract|static|final|public|protected|private)\\s*)?' . '(?:(\\S+)\\s+)?' . '([a-zA-Z0-9_:\\-\\>]+)\\s*\\((.*?)\\)$/s', $desc, $match); if (!$res) { throw new PC_PHPRef_Exception('Unable to parse "' . $desc . '"'); } list(, $modifier1, $modifier2, $modifier3, $return, $name, $params) = $match; // detect class-names if (($pos = strpos($name, '::')) !== false || ($pos = strpos($name, '->')) !== false) { $classname = substr($name, 0, $pos); $name = substr($name, $pos + 2); } // build basic method $method = new PC_Obj_Method($file, 0, $classname != ''); if ($modifier1 == 'static' || $modifier2 == 'static' || $modifier3 == 'static') { $method->set_static(true); } if ($modifier1 == 'final' || $modifier2 == 'final' || $modifier3 == 'final') { $method->set_final(true); } if ($modifier1 == 'abstract' || $modifier2 == 'abstract' || $modifier3 == 'abstract') { $method->set_abstract(true); } if (in_array($modifier1, array('private', 'protected'))) { $method->set_visibility($modifier1); } else { if (in_array($modifier2, array('private', 'protected'))) { $method->set_visibility($modifier2); } else { if (in_array($modifier3, array('private', 'protected'))) { $method->set_visibility($modifier3); } } } if ($return) { $method->set_return_type(PC_Obj_MultiType::get_type_by_name($return)); // set this always for builtin types since it makes no sense to report errors for // inherited classes or similar $method->set_has_return_doc(true); } $method->set_name($name); // check what kind of params we have $optional = ''; $firstopt = strpos($params, '['); if ($firstopt !== false) { $required = substr($params, 0, $firstopt); $optional = substr($params, $firstopt + 1); $optional = str_replace(array('[', ']'), '', $optional); } else { $required = $params; } // add required ones $required = trim($required); if ($required && $required != 'void') { $reqparts = explode(', ', $required); foreach ($reqparts as $part) { list($type, $name) = explode(' ', trim($part)); $param = new PC_Obj_Parameter(); $param->set_name(trim($name)); $param->set_mtype(self::get_param_type($type)); $param->set_has_doc(true); $method->put_param($param); } } // add optional ones $optional = trim($optional); if ($optional) { $optparts = explode(', ', $optional); foreach ($optparts as $part) { $part = trim($part); if ($part == '') { continue; } $default = null; $param = new PC_Obj_Parameter(); $param->set_optional(true); // has it a known default-value? if (($pos = strpos($part, '=')) !== false) { $nametype = trim(substr($part, 0, $pos)); $default = trim(substr($part, $pos + 1)); $parts = preg_split('/\\s+/', $nametype); if (count($parts) != 2) { throw new PC_PHPRef_Exception('Parameter description has not 2 parts: "' . $nametype . '"'); } list($type, $name) = $parts; } else { // detect variable arguments if (strpos($part, '...') !== false) { $param->set_first_vararg(true); // sometimes there is a space bewteen $ and ... $part = preg_replace('/\\$\\s+\\.\\.\\./', '$...', $part); } $parts = preg_split('/\\s+/', $part); if (count($parts) != 2) { throw new PC_PHPRef_Exception('Parameter description has not 2 parts: "' . $part . '"'); } list($type, $name) = $parts; } // detect references if (substr($name, 0, 1) == '&') { $param->set_reference(true); $name = substr($name, 1); } $param->set_name(trim($name)); $param->set_mtype(self::get_param_type($type, $default)); $param->set_has_doc(true); $method->put_param($param); } } return array('func', $classname, $method); }