src/Library/Helper/Code.php
<?php
/**
* This file is part of the Library package.
*
* Copyleft (ↄ) 2013-2016 Pierre Cassat <me@e-piwi.fr> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The source code of this package is available online at
* <http://github.com/atelierspierrot/library>.
*/
namespace Library\Helper;
use \Library\Helper\Text as TextHelper;
use \Library\Helper\Directory as DirectoryHelper;
/**
* Code helper
*
* As for all helpers, all methods are statics.
*
* For convenience, the best practice is to use:
*
* use Library\Helper\Code as CodeHelper;
*
* @author piwi <me@e-piwi.fr>
*/
class Code
{
/**
* Transforms a property name from CamelCase to underscored
*
* @param string $name The property name to transform
* @return string The transformed property name
* @see Library\Helper\Text::fromCamelCase()
*/
public static function getPropertyName($name)
{
return TextHelper::fromCamelCase( str_replace(' ', '_', $name) );
}
/**
* Transform a property name from underscored to CamelCase used in magic method names
*
* @param string $name The property name to transform
* @return string The transformed property name
* @see Library\Helper\Text::toCamelCase()
*/
public static function getPropertyMethodName($name)
{
return TextHelper::toCamelCase($name);
}
/**
* Check if a class implements a certain interface
*
* @param string|object $class_name The class name to test or a full object of this class
* @param string $interface_name The interface name to test
* @return bool
*/
public static function impelementsInterface($class_name, $interface_name)
{
trigger_error('DEPRECATED - Usage of the "impelementsInterface" method is deprecated and replaced by "implementsInterface"!', E_USER_WARNING);
return self::implementsInterface($class_name, $interface_name);
}
/**
* Check if a class implements a certain interface
*
* @param string|object $class_name The class name to test or a full object of this class
* @param string $interface_name The interface name to test
* @return bool
*/
public static function implementsInterface($class_name, $interface_name)
{
if (is_object($class_name)) {
$class_name = get_class($class_name);
}
if (class_exists($class_name)) {
$interfaces = class_implements($class_name);
return (bool) in_array($interface_name, $interfaces) || in_array(trim($interface_name, '\\'), $interfaces);
}
return false;
}
/**
* Check if a class extends a certain class
*
* @param string|object $class_name The class name to test or a full object of this class
* @param string $mother_name The class name to extend
*
* @return bool
*/
public static function extendsClass($class_name, $mother_name)
{
if (is_object($class_name)) {
$class_name = get_class($class_name);
}
if (class_exists($class_name)) {
return (bool) is_subclass_of($class_name, $mother_name);
}
return false;
}
/**
* Check if a an object is an instance of a class
*
* @param object $object
* @param string $class_name
*
* @return bool
*/
public static function isClassInstance($object, $class_name)
{
if (class_exists($class_name) && is_object($object)) {
return (bool) ($object instanceof $class_name);
}
return false;
}
/**
* @var string
*/
const NAMESPACE_SEPARATOR = '\\';
/**
* @var string
*/
const COMPOSER_AUTOLOADER_CLASSNAME = '\Composer\Autoload\ClassLoader';
/**
* @var string
*/
const COMPOSER_COMMON_NAMESPACES_AUTOLOADER = 'autoload_namespaces.php';
/**
* Test if a namespace can be found in declared classes or via Composer autoloader if so
*
* This method will search concerned namespace in PHP declared classes namespaces and, if
* found, in a Composer namespaces mapping usually stored in `vendor/composer/autoload_namespaces.php`,
* searching for a directory that should contains the nameapace following the
* [FIG standards](https://github.com/php-fig/fig-standards).
*
* @param string $namespace
*
* @return bool
*/
public static function namespaceExists($namespace)
{
$namespace = trim($namespace, self::NAMESPACE_SEPARATOR);
$namespace .= self::NAMESPACE_SEPARATOR;
foreach (get_declared_classes() as $name) {
if (strpos($name, $namespace) === 0) {
return true;
}
}
if (class_exists($_composer_loader = self::COMPOSER_AUTOLOADER_CLASSNAME)) {
$_composer_reflection = new \ReflectionClass($_composer_loader);
$_loader_filename = $_composer_reflection->getFilename();
$_classmap_filename = dirname($_loader_filename)
.DIRECTORY_SEPARATOR
.self::COMPOSER_COMMON_NAMESPACES_AUTOLOADER;
if (file_exists($_classmap_filename)) {
$namespaces_map = include $_classmap_filename;
foreach ($namespaces_map as $_ns=>$_dir) {
$_ns = trim($_ns, self::NAMESPACE_SEPARATOR);
if (strpos($_ns, $namespace) === 0) {
return true;
}
if (substr($namespace, 0, strlen($_ns))===$_ns) {
if (false !== $pos = strrpos($namespace, self::NAMESPACE_SEPARATOR)) {
// namespaced class name
$namespace_path = strtr(substr($namespace, 0, $pos), self::NAMESPACE_SEPARATOR, DIRECTORY_SEPARATOR);
$namespace_name = substr($namespace, $pos + 1);
} else {
// PEAR-like class name
$namespace_path = null;
$namespace_name = $namespace;
}
$namespace_path .= strtr($namespace_name, '_', DIRECTORY_SEPARATOR);
if (!is_array($_dir)) {
$_dir = array($_dir);
}
foreach ($_dir as $_testdir) {
$_d = DirectoryHelper::slashDirname($_testdir) . $namespace_path;
if (file_exists($_d) && is_dir($_d)) {
return true;
}
}
}
}
}
}
return false;
}
/**
* Launch a function or class's method fetching it arguments according to its declaration
*
* @param string $method_name The method name
* @param mixed $arguments A set of arguments to fetch
* @param string $class_name The class name
* @param array $logs Will be filled with indexes `miss` with missing required arguments
* and `rest` with unused `$arguments` - Passed by reference
* @return mixed
* @throws \InvalidArgumentException if the method is not callable
*/
public static function fetchArguments($method_name = null, $arguments = null, $class_name = null, &$logs = array())
{
$args_def = self::organizeArguments($method_name, $arguments, $class_name, $logs);
if (!empty($class_name)) {
if (is_callable(array($class_name, $method_name))) {
return call_user_func_array(array($class_name, $method_name), $args_def);
} else {
$_cls = is_object($class_name) ? get_class($class_name) : $class_name;
throw new \InvalidArgumentException(
sprintf('Method "%s" of class object "%s" is not callable!', $method_name, $_cls)
);
}
} else {
return call_user_func_array($method_name, $args_def);
}
}
/**
* Organize an array of arguments to pass to a function or class's method according to its declaration
*
* Undefined arguments will be fetched with their default value if available or `null` otherwise.
*
* If `$arguments` is not an array, the method will search for the first argument with
* no default value and define it on the `$arguments` value.
*
* @param string $method_name The method name
* @param mixed $arguments A set of arguments to fetch
* @param string $class_name The class name
* @param array $logs Will be filled with indexes `miss` with missing required arguments
* and `rest` with unused `$arguments` - Passed by reference
* @return mixed
*/
public static function organizeArguments($method_name = null, $arguments = null, $class_name = null, &$logs = array())
{
if (empty($method_name)) {
return;
}
$args_passed = $arguments;
$args_def = array();
if (!empty($args_passed)) {
if (!empty($class_name)) {
$method_reflect = new \ReflectionMethod($class_name, $method_name);
} else {
$method_reflect = new \ReflectionFunction($method_name);
}
if (!is_array($args_passed)) {
$tmp_index = -1;
foreach ($method_reflect->getParameters() as $_param) {
$arg_pos = $_param->getPosition();
if (!$_param->isDefaultValueAvailable() && $tmp_index===-1) {
$tmp_index = $_param->getPosition();
} elseif (!$_param->isDefaultValueAvailable() && $tmp_index!==-1) {
if (!isset($logs['miss'])) {
$logs['miss'] = array();
}
$logs['miss'][$arg_pos] = sprintf('Argument "%s" is missing and defined on NULL', $_param->getName());
}
}
if ($tmp_index===-1) {
$tmp_index = 0;
}
$args_passed = array( $tmp_index=>$args_passed );
}
foreach ($method_reflect->getParameters() as $_param) {
$arg_index = $_param->getName();
$arg_pos = $_param->getPosition();
$arg_class = $_param->getClass();
$arg_val = null;
if (!is_null($arg_class)) {
$cls_name = $arg_class->getName();
foreach ($args_passed as $ind=>$item) {
if (is_object($item) && (
($item instanceof $cls_name) ||
self::implementsInterface($item, $cls_name)
)) {
$arg_val = $item;
$args_def[$arg_pos] = $arg_val;
unset($args_passed[$ind]);
continue;
}
}
}
if (isset($args_passed[$arg_index])) {
$arg_val = $args_passed[$arg_index];
unset($args_passed[$arg_index]);
} elseif (isset($args_passed[$arg_pos])) {
$arg_val = $args_passed[$arg_pos];
unset($args_passed[$arg_pos]);
} elseif ($_param->isDefaultValueAvailable()) {
$arg_val = $_param->getDefaultValue();
} else {
if (!isset($logs['miss'])) {
$logs['miss'] = array();
}
$logs['miss'][$arg_pos] = sprintf('Argument "%s" is missing and defined on NULL', $arg_index);
}
$args_def[$arg_pos] = $arg_val;
}
}
if (!empty($args_passed)) {
$logs['rest'] = $args_passed;
}
return $args_def;
}
/**
* @see <http://www.metashock.de/2013/05/dump-source-code-of-closure-in-php/>
* @param callable $c
* @return string
*/
public static function dumpClosure(\Closure $c)
{
$str = 'function (';
$r = new \ReflectionFunction($c);
$params = array();
foreach($r->getParameters() as $p) {
$s = '';
if($p->isArray()) {
$s .= 'array ';
} else if($p->getClass()) {
$s .= $p->getClass()->name . ' ';
}
if($p->isPassedByReference()){
$s .= '&';
}
$s .= '$' . $p->name;
if($p->isOptional()) {
$s .= ' = ' . var_export($p->getDefaultValue(), TRUE);
}
$params []= $s;
}
$str .= implode(', ', $params);
$str .= '){' . PHP_EOL;
$lines = file($r->getFileName());
for($l = $r->getStartLine(); $l < $r->getEndLine(); $l++) {
$str .= $lines[$l];
}
return $str;
}
}