s-borchev/spicy-identifiers

一种轻松解析和操作标识符名称的方法,例如动态方法名称。

0.1.1 2023-08-03 14:23 UTC

This package is auto-updated.

Last update: 2024-09-03 17:06:42 UTC


README

Build Status

一种轻松解析和操作标识符名称的方法,例如动态方法名称。

此包提高了处理动态标识符名称(如动态访问的方法和变量/属性)的体验。它还简化了标识符大小写格式(如 camelCasesnake_case)之间的转换。

例如,假设我们有一个包含配置指令数组的配置文件和一个消费这些指令的类。如果我们使用 snake_case 编写配置键,但类使用 camelCase 定义相应的设置方法,我们可以使用此包轻松地在这两者之间建立联系。以下是一个示例。

注意:当前稳定版本仅包括处理动态方法和函数的支持。对类、变量和属性的支持正在开发中。

简单示例

开发者经常使用 PHP 的魔法方法来动态重载类方法,以获得更灵活的功能。此包可以简化处理动态方法调用所需的工作。

<?php

// Call a dynamic method:
$someClass->callSomeDynamicMethod('some argument');

// The dynamic method call is handled by the __call() magic method of a class.
// $methodCalled is the name of the dynamic method: "callSomeDynamicMethod"
public function __call($methodCalled, array $arguments)
{
    // Use Spicy Identifiers to work with the dynamic method
    $method = DynamicMethod::parse($methodCalled);

    // Check if the method name starts and ends with certain strings
    if ($method->startsWith('call') && $method->endsWith('Method')) {
        $method->replace(0, 'get')->replace(3, 'Variable');
        // The dynamic method name is now "getSomeDynamicVariable"
    }

    // Alert the developer if they called a method that doesn't exist
    $method->throwExceptionIfMissingOn($this);

    // Check that the method includes the word "Dynamic" in the name,
    // then call the method represented by the name and return that value
    if ($method->has(2) && $method[2] === 'Dynamic') {
        return $method->callOn($this, $arguments);
    }
}

考虑另一个示例,其中有一个消费存储为数组的配置指令的类。配置键以 snake_case 编写,但类使用 camelCase 定义相应的设置方法。此包可以轻松加载配置。

<?php

$config = [
    'config_directive_1' => 'some value',
    'config_directive_2' => 'some value',
    ...
];

class UsesConfig
{
    public function __construct(array $config)
    {
        foreach ($config as $option => $value) {
            DynamicMethod::parse($option)
                ->prepend('set')
                ->throwExceptionIfMissingOn($this, 'Invalid option: ' . $option)
                ->callOn($this, [ $value ]); // calls setConfigDirectiveN()
        }
    }

    public function setConfigDirective1($value) { ... }

    public function setConfigDirective2($value) { ... }

    ...
}

安装

$ composer require monospice/spicy-identifiers

我们需要确保导入我们想要使用的类

use Monospice\SpicyIdentifiers\DynamicVariable;
use Monospice\SpicyIdentifiers\DynamicMethod;
use Monospice\SpicyIdentifiers\DynamicFunction;
use Monospice\SpicyIdentifiers\DynamicClass;
use Monospice\SpicyIdentifiers\Tools\CaseFormat;

此包自动安装相关的 Spicy Identifier Tools 包,位于命名空间 Monospice\SpicyIdentifiers\Tools 中。

标识符类型

此包提供不同类型的类来处理各种类型的标识符

  • DynamicIdentifier:一个通用的类,用于操作标识符名称,但不提供任何额外的功能
  • DynamicMethod:提供方法和默认值,以加快处理类方法的过程
  • DynamicFunction:提供方法和默认值,以加快处理标准函数的过程
  • DynamicVariable:提供方法和默认值,以加快处理变量的过程
  • DynamicClass:提供方法和默认值,以加快处理类的过程

解析标识符名称

要开始处理标识符字符串,例如方法名称,请使用包的其中一个工厂方法将其解析成对象

$method = DynamicMethod::parse('aMethodName');
$function = DynamicFunction::parse('a_function_name');
$variable = DynamicVariable::parse('aVariableName');
$class = DynamicClass::parse('AClassName');

$method->parts(); // array('a', 'Method', 'Name')

::parse() 工厂方法使用包中每个类表示的标识符类型的默认大小写格式。要解析特定格式的标识符,请使用相应的解析方法

$method = DynamicMethod::parseFromUnderscore('a_method_name');
$method->parts(); // array('a', 'method', 'name')

除了 ::parse() 之外,我们还可以使用以下任何 DynamicIdentifier 子类的工厂方法来解析特定格式的标识符

  • parseFromCamelCase() - 标识符如:anIdentifierName
  • parseFromCamelCaseExtended() - 标识符如:änÏdentifierNáme
  • parseFromUnderscore() - 标识符如:an_identifier_name
  • parseFromSnakeCase() - parseFromUnderscore() 的别名
  • parseFromHyphen() - 标识符如:an-identifier-name
  • parseFromMixedCase() - 标识符如:aMixed_case-identifier

有关标识符格式、混合大小写或扩展ASCII标识符的更多信息,请参阅以下内容。

加载标识符

有时我们可能希望使用该包中类的动态功能,但又不需要将标识符字符串解析为其组成部分。在这种情况下,我们可以通过使用::from()工厂方法简单地创建一个标识符字符串的实例来避免调用解析器,以提高性能。

$method = DynamicMethod::from('aMethodName');
$method->parts(); // array('aMethodName')

$returnValue = $method->callOn($this);

同样,在某些情况下,我们可能已经知道或拥有我们希望使用的标识符部分集合。我们可以调用::fromParts()工厂方法来创建代表标识符的实例

$method = DynamicMethod::fromParts(['a', 'method', 'name']);
$method->parts(); // array('a', 'method', 'name')

$returnValue = $method->callOn($this);

标识符操作

解析标识符后,我们可以使用此包来操作这些部分。以下示例使用此DynamicIdentifier实例

$identifier = DynamicIdentifier::parse('anIdentifierName');

在任何时候,我们都可以获取当前的标识符名称

name() - 获取整个标识符名称的字符串表示形式

$identifier->name();                    // "anIdentifierName"

或者,我们可以将动态标识符实例转换为字符串

echo $identifier;                       // "anIdentifierName"

获取标识符部分数据

parts() - 获取标识符部分名称的数组

$identifier->parts();                   // ['an', 'Identifier', 'Name']
$identifier->toArray();                 // an alias for parts()

part() - 获取指定标识符部分字符串值

$identifer->part(1);                    // "Identifier"

或者,使用数组访问来获取值

$identifier[1];                         // "Identifier"

请注意,部分数组从零开始,因此第一部分对应于索引0

first() - 获取第一个标识符部分的值

$identifier->first();                   // "an"

last() - 获取最后一个标识符部分的值

$identifier->last();                    // "Name"

keys() - 获取标识符部分索引的数组

$identifier->keys();                    // [0, 1, 2]

我们可以向keys()方法传递一个字符串,以获取与值匹配的部分的索引数组

$identifier->keys('Name');              // [2]

请注意,默认情况下keys()执行不区分大小写的比较。要匹配确切的大小写,请将第二个参数设置为true

$identifier->keys('NAME', true);        // [ ]

getNumParts() - 获取标识符部分的数量

$identifier->getNumParts();             // 3
$identifier->count();                   // an alias for getNumParts()

或者,使用PHP的count()函数获取标识符部分的数量

count($identifier);                     // 3

检查标识符部分

has() - 检查标识符是否包含指定索引的部分

$identifier->has(1);                    // true

也可以使用数组访问进行上述操作

isset($identifier[1]);                  // true

startsWith() - 检查标识符是否以指定的字符串开头

$identifier->startsWith('an');          // true
$identifier->startsWith('identifier');  // false

endsWith() - 检查标识符是否以指定的字符串结尾

$identifier->endsWith('name');          // true
$identifier->endsWith('identifier');    // false

请注意,默认情况下startsWith()endsWith()执行不区分大小写的比较。要匹配确切的大小写,请将第二个参数设置为true

$identifier->endsWith('NAME', true);    // false

添加部分

append() - 将部分添加到标识符的末尾

$identifier->append('last');            // "anIdentifierNameLast"
$identifier->push('last');              // alias for append()

或者,使用数组访问将部分推送到标识符的末尾

$identifier[] = 'last';

prepend() - 将部分添加到标识符的开头

$identifier->prepend('first');          // "firstAnIdentifierName"

insert() - 在标识符的指定位置添加部分

$identifier->insert(1, 'insert');       // "anInsertIdentifierName"

移除部分

pop() - 从标识符的末尾移除部分

$identifier->pop();                     // "anIdentifier"

shift() - 从标识符的开头移除部分

$identifier->shift();                   // "identifierName"

remove() - 从标识符的指定位置移除部分

$identifier->remove(1);                 // "anName"

替换部分

replace() - 替换标识符指定位置的部分

$identifier->replace(2, 'String');      // "anIdentifierString"

或者,使用数组访问替换指定索引的部分

$identifier[2] = 'String';              // "anIdentifierString"

合并部分

合并部分不会更改输出字符串,但会合并内部数组的部分。这在其他操作中很有用。

mergeRange() - 合并指定位置之间的标识符部分

$identifier = DynamicIdentifier::parse('anIdentifierName');

echo $identifier->mergeRange(1, 2);     // "anIdentifierName"
$identifier->parts();                   // array(
                                        //     0 => "an",
                                        //     1 => "IdentifierName"
                                        // )

如果没有指定结束位置,则从起始位置之后的所有剩余部分都将合并。

$identifier->mergeRange(0)->parts();    // array(
                                        //     0 => "anIdentifierName"
                                        // )

动态方法

DynamicMethod类为与解析的标识符名称对应的底层类方法添加了功能。

$method = DynamicMethod::parse('someMethod');

existsOn() - 检查表示的方法是否在给定的类上下文中存在

$method->existsOn('Namespace\SomeClass');
$method->existsOn(SomeClass::class);
$method->existsOn($someInstance);
$method->existsOn($this);

callOn() - 在给定上下文中调用由解析的方法名称表示的方法

$returnValue = $method->callOn($someInstance);
$returnValue = $method->callOn($someInstance, ['arg1', 'arg2']);
$returnValue = $method->callOn($this, ['arg1', 'arg2']);

// Static Methods
$returnValue = $method->callOn('Namespace\SomeClass');
$returnValue = $method->callOn('Namespace\SomeClass', ['arg1']);

callFromScopeOn() - 从指定上下文的作用域中调用由解析的方法名表示的方法

此方法与 callOn() 类似,但它允许访问 DynamicMethod 实例无法直接调用的私有和受保护方法。callFromScopeOn() 适用于在 DynamicMethod 被用于可以正常直接访问其私有和受保护成员的类中的情况。在选择此方法之前应仔细考虑,并且始终使用 callOn() 调用公共方法。

$returnValue = $method->callFromScopeOn($someInstance);
$returnValue = $method->callFromScopeOn($this, ['arg1', 'arg2']);

// Static Methods
$returnValue = $method->callFromScopeOn('Namespace\SomeClass');
$returnValue = $method->callFromScopeOn('Namespace\SomeClass', ['arg1']);

forwardStaticCallTo() - 将调用转发到由解析的方法名表示的静态方法,以实现后期静态绑定

$returnValue = $method->forwardStaticCallTo('Namespace\SomeClass');
$returnValue = $method->forwardStaticCallTo('SomeClass', ['arg1', 'arg2']);

throwException() - 抛出 BadMethodCallException。默认异常消息假设异常是因为方法不存在而抛出的

$method->throwException();

可以在第一个参数中指定异常消息

$method->throwException('A custom exception message');

throwExceptionIfMissingOn() - 如果在指定上下文中不存在该方法,则抛出 BadMethodCallException

$method->throwExceptionIfMissingOn($someObject);
$method->throwExceptionIfMissingOn('Namespace\SomeClass');

可以在第二个参数中指定异常消息

$method->throwExceptionIfMissingOn($someObject, 'A custom exception message');

动态函数

DynamicFunction 类为处理与解析的标识符名称相对应的底层标准函数添加了功能。

$function = DynamicFunction::parse('some_function');

exists() - 检查表示的函数是否存在

$function->exists();

call() - 调用由解析的函数名表示的函数

$returnValue = $function->call();
$returnValue = $function->call(['arg1']);

throwException() - 抛出 BadFunctionCallException。默认异常消息假设异常是因为函数不存在而抛出的

$function->throwException();

可以在第一个参数中指定异常消息

$function->throwException('A custom exception message');

throwExceptionIfMissing() - 如果函数不存在,则抛出 BadFunctionCallException

$function->throwExceptionIfMissing();

可以在第一个参数中指定异常消息

$function->throwExceptionIfMissing('A custom exception message');

更改动态标识符类型

我们可以从本包中的任何动态标识符类中获得特定的标识符类型。例如,开发者可以使用具有相同标识符名称但具有特定于变量的方法的 DynamicVariable 表示 DynamicMethod

$method = DynamicMethod::parse('anIdentifierName');
$method->name();                   // 'anIdentifierName'

$variable = $method->toVariable();
get_class($variable);              // Monospice\SpicyIdentifiers\DynamicVariable
$variable->name();                 // 'anIdentifierName'
$variable->value();                // the value of the corresponding variable

请注意,此功能不会将原始对象转换为类型,而是返回相应类的新的实例。因此,如果您计划稍后在代码中使用转换后的实例,请务必将返回的对象分配给变量。这种设计鼓励为每种类型使用适当的变量名。

可用的转换有

$method = $identifier->toMethod();      // DynamicMethod
$variable = $identifier->toVariable();  // DynamicVariable
$class = $identifier->toClass();        // DynamicClass
$function = $identifier->toFunction();  // DynamicFunction

方法链式调用

不返回输出值的方法可以进行链式调用

$returnValue = DynamicMethod::parse('aDynamicMethod')
    ->append('last')
    ->mergeRange(1, 2)
    ->callOn($this);

标识符大小写格式

每个类使用默认的大小写格式来解析和输出标识符。这些格式是在 Tools\CaseFormat 类上设置的常量。

有关支持的格式化格式的更多信息,请参阅此包自动包含的 Spicy Identifier Tools 包。

要覆盖此默认格式化,请使用专用方法之一解析标识符,并/或显式设置输出格式

// parse and output with the default case format (camel case)
$identifier = DynamicIdentifier::parse('identifierName');

// parse with an explicit case format, output with the default format
$identifier = DynamicIdentifier::parseFromUnderscore('identifier_name');

// parse with an explicit format, and set an explicit output format
$identifier = DynamicIdentifier::parseFromUnderscore('identifier_name')
    ->setOutputFormat(CaseFormat::UPPER_CAMEL_CASE);

标识符名称中的缩写

有时标识符名称包含缩写,例如 JavaScript 的 XMLHttpRequest 中的 XML

$method = DynamicMethod::parse('XMLHttpRequest')
$method->parts();   // array('XML', 'Http', 'Request');

但是,除非我们设置一个保留缩写的输出格式,否则输出方法不会保留这些缩写

$method->name();    // "xmlHttpRequest"

$method
    ->setOutputFormat(CaseFormat::CAMEL_CASE_WITH_ACRONYMS)
    ->name();       // "XMLHttpRequest"

此行为在转换或规范化标识符名称时提供了灵活性。

混合大小写格式的标识符

尽管在实践中不建议使用混合大小写标识符,但可以使用 ::parseFromMixedCase() 方法来解析包含多个大小写的标识符。

// parseFromMixedCase($identiferString, $arrayOfCaseFormatsToParse);

DynamicIdentifier::parseFromMixedCase('aMixed_case-identifier', [
    CaseFormat::CAMEL_CASE,
    CaseFormat::UNDERSCORE,
    CaseFormat::HYPHEN,
])
    ->parts(); // array('a', 'Mixed', 'case', 'identifier');

该包不提供在混合格式中输出标识符的支持。任何输出方法都将使用默认格式对输出字符串进行格式化,除非显式指定(见前面的部分)。

扩展 ASCII 标识符(实验性)

PHP 支持在标识符名称中使用扩展 ASCII 字符。例如,字符 ä

// From the php.net manual:
$täyte = 'mansikka';    // valid; 'ä' is (Extended) ASCII 228.

当通过下划线或连字符解析标识符时,这些字符没有作用。然而,驼峰式标识符可能包含由扩展ASCII字符分隔的单词,例如 änÏdentifierNáme

Spicy Identifiers 包提供了一个 实验性 方法来解析这些标识符

DynamicIdentifier::parseFromCamelCaseExtended('änÏdentifierNáme')
    ->parts(); // array('än', 'Ïdentifier', 'Náme');

此方法的连贯性取决于源文件的字符编码以及环境语言和编码设置。作为一个最佳实践,应避免在标识符名称中使用扩展ASCII字符。

更多信息,请访问 PHP 手册: https://php.ac.cn/manual/en/language.variables.basics.php

测试

Spicy Identifiers 包使用 PHPSpec 来测试对象行为。

$ vendor/bin/phpspec run

许可证

MIT 许可证 (MIT)。更多信息,请参阅 LICENSE 文件