有什么方法可以在 CFC 中强制执行 属性 的类型?
Any way to enforce a property's type in a CFC?
我在这里遇到了一些奇怪的类型转换问题,并非特定于 Lucee(也在 Railo 中)。可能是我在这里遗漏了一些关键点...
我有一个组件:
<cfcomponent output="false">
<cfproperty name="thisId" type="String" default="-1" />
<cfproperty name="thatId" type="String" default="-1" />
</cfcomponent>
这两个属性显然都是字符串类型。我希望当我尝试将对象或数字设置为其中之一时,代码会 return 出错。
但是,鉴于我现在已经习惯了 cfml 为我进行类型转换,所以我从来没有想过在这里设置一个数字完全没有问题。事实上,我假设我尝试在此处设置的所有数字都会为我转换为字符串。
但似乎并非如此。
在实现了一些包含序列化结构形式的这些组件的派生的 REST 调用之后,我注意到其中一些作为整数包含在内,而另一些作为字符串包含在内。当我注意到这一点时,我转储了组件本身并注意到设置了一个数字,其中一个字符串应该是 属性,输入已被覆盖为一个数字。
Railo / Lucee 仍然有效的事实在我看来毫无用处。要么验证严格类型并抛出错误/传递正确类型的变量,要么验证松散类型并转换为 CFC 期望的类型(如果可能)。
Railo / lucee 在这里实现了宽松的类型验证,但仍然决定以其原始类型传入变量,而不是 cfc 期望的类型。
鉴于我现在不想将每个数字都转换为字符串,这里是否有一个简单的疏忽可以挽救我的输入?
(我已经在 Lucee 邮件列表中发布了这个,但没有任何结果,只是人们确认我已经说过的/忽略这不是预期行为的可能性。)
更新(亚当问):
我看到的是以下内容(在我上面描述的 cfc 组件中):
<!--- setting a string returns a string afterwards, as expected since the property is a type string initialy --->
<cfset componentName.setThisId('1') />
<cfset local.thisIsStillAString = componentName.getThisId() />
<!--- setting a number returns a number, which means we can no longer assume the property is a string, as it was initially set up --->
<cfset componentName.setThatId(12345) />
<cfset local.thisIsNoLongerAString = componentName.getThatId() />
在这两种情况下,我都希望:
- 输入变量将被严格评估为字符串,这意味着第二个示例会引发错误,因为它实际上是一个数字
- 输入变量将被松散地评估为一个字符串,但在通过评估时将被转换为一个字符串,这意味着第二个示例将通过但最终将 return 一个字符串,不再是一个数字。
在任何情况下,我都希望 属性 的原始类型被保留,而不是它被更改为您尝试设置的任何类型,只要它通过当前的松散评估。
要将它们强制为字符串(不是数组或结构),请将 accessor=true
与您的 <cfproperty>
一起使用并使用设置器。
但是,如果您谈论 SerializeJSON()
将数字字符串视为整数,那不是 type="string"
可以执行的。它与 SerializeJSON()
函数的行为有关。如果你真的想将它们作为字符串强制执行,请尝试 https://github.com/bennadel/JsonSerializer.cfc
据我了解,内置访问器 setters 将进行自动类型验证,但只会在必要时进行强制转换 和 可能。
Numeric/string/date/boolean 值都被视为 "simple",这就是数字数据通过 "string" 类型验证的原因。因此,因为它通过了验证,所以跳过了转换。就个人而言,我更希望它能进行更严格的验证,但这对错误追踪器来说是个问题。
现在,如果您必须确保只有实际的字符串数据才能进入这些属性,您可以为[=53=覆盖生成的setter ] 进行更严格的类型转换 and/or 验证(我只在 Lucee 上测试过):
/** Example.cfc */
component accessors=true {
property type="string" name="thisId";
property type="string" name="thatId";
public function setThisId(required string newId) {
// convert numeric value to string value
if (isNumeric(newId)) {
newId = toString(newId);
// throw an exception for non-string/numeric values
// !isSimpleValue() is a catch-all btw, structs and arrays will
// be prevented by the "newId" argument's type hint
} else if (isBoolean(newId) || isDate(newId) || !isSimpleValue(newId)) {
throw(message="Invalid value specified for thisId");
}
variables.thisId = newId;
return this;
}
}
var example = new Example();
example.setThisId(54321);
example.setThatId(54321);
writeoutput(serializeJson(example)); //{"thisId":"54321","thatId":54321}
// throws exceptions:
example.setThisId(true);
example.setThisId({});
最后,回到"casting where necessary and possible"部分。对于给定的示例,如果您尝试将组件实例传递给 setThisId()
方法,它会在类型验证步骤中失败,这意味着类型转换对于该操作是 必需的 成功。因此,将检查该值是否存在 类型转换的可能性 。如果组件(这仅适用于 Railo/Lucee)定义了 _toString()
"magic method",则类型转换是 可能 。因为这是可能的,所以将组件转换为字符串,然后将结果传递给 setThisId()
。如果那个魔法方法没有在组件上定义,就不可能进行类型转换,并抛出异常。类似地,对于 structs/arrays,类型转换是 必要的 ,但不是 可能的 ,因为没有为这些类型定义自动序列化,因此导致在抛出异常时。
TL;DR
您可以覆盖 setter 访问器以执行更严格的 type-validation/type-casting。
我在这里遇到了一些奇怪的类型转换问题,并非特定于 Lucee(也在 Railo 中)。可能是我在这里遗漏了一些关键点...
我有一个组件:
<cfcomponent output="false">
<cfproperty name="thisId" type="String" default="-1" />
<cfproperty name="thatId" type="String" default="-1" />
</cfcomponent>
这两个属性显然都是字符串类型。我希望当我尝试将对象或数字设置为其中之一时,代码会 return 出错。 但是,鉴于我现在已经习惯了 cfml 为我进行类型转换,所以我从来没有想过在这里设置一个数字完全没有问题。事实上,我假设我尝试在此处设置的所有数字都会为我转换为字符串。
但似乎并非如此。 在实现了一些包含序列化结构形式的这些组件的派生的 REST 调用之后,我注意到其中一些作为整数包含在内,而另一些作为字符串包含在内。当我注意到这一点时,我转储了组件本身并注意到设置了一个数字,其中一个字符串应该是 属性,输入已被覆盖为一个数字。
Railo / Lucee 仍然有效的事实在我看来毫无用处。要么验证严格类型并抛出错误/传递正确类型的变量,要么验证松散类型并转换为 CFC 期望的类型(如果可能)。 Railo / lucee 在这里实现了宽松的类型验证,但仍然决定以其原始类型传入变量,而不是 cfc 期望的类型。
鉴于我现在不想将每个数字都转换为字符串,这里是否有一个简单的疏忽可以挽救我的输入?
(我已经在 Lucee 邮件列表中发布了这个,但没有任何结果,只是人们确认我已经说过的/忽略这不是预期行为的可能性。)
更新(亚当问): 我看到的是以下内容(在我上面描述的 cfc 组件中):
<!--- setting a string returns a string afterwards, as expected since the property is a type string initialy --->
<cfset componentName.setThisId('1') />
<cfset local.thisIsStillAString = componentName.getThisId() />
<!--- setting a number returns a number, which means we can no longer assume the property is a string, as it was initially set up --->
<cfset componentName.setThatId(12345) />
<cfset local.thisIsNoLongerAString = componentName.getThatId() />
在这两种情况下,我都希望: - 输入变量将被严格评估为字符串,这意味着第二个示例会引发错误,因为它实际上是一个数字 - 输入变量将被松散地评估为一个字符串,但在通过评估时将被转换为一个字符串,这意味着第二个示例将通过但最终将 return 一个字符串,不再是一个数字。
在任何情况下,我都希望 属性 的原始类型被保留,而不是它被更改为您尝试设置的任何类型,只要它通过当前的松散评估。
要将它们强制为字符串(不是数组或结构),请将 accessor=true
与您的 <cfproperty>
一起使用并使用设置器。
但是,如果您谈论 SerializeJSON()
将数字字符串视为整数,那不是 type="string"
可以执行的。它与 SerializeJSON()
函数的行为有关。如果你真的想将它们作为字符串强制执行,请尝试 https://github.com/bennadel/JsonSerializer.cfc
据我了解,内置访问器 setters 将进行自动类型验证,但只会在必要时进行强制转换 和 可能。
Numeric/string/date/boolean 值都被视为 "simple",这就是数字数据通过 "string" 类型验证的原因。因此,因为它通过了验证,所以跳过了转换。就个人而言,我更希望它能进行更严格的验证,但这对错误追踪器来说是个问题。
现在,如果您必须确保只有实际的字符串数据才能进入这些属性,您可以为[=53=覆盖生成的setter ] 进行更严格的类型转换 and/or 验证(我只在 Lucee 上测试过):
/** Example.cfc */
component accessors=true {
property type="string" name="thisId";
property type="string" name="thatId";
public function setThisId(required string newId) {
// convert numeric value to string value
if (isNumeric(newId)) {
newId = toString(newId);
// throw an exception for non-string/numeric values
// !isSimpleValue() is a catch-all btw, structs and arrays will
// be prevented by the "newId" argument's type hint
} else if (isBoolean(newId) || isDate(newId) || !isSimpleValue(newId)) {
throw(message="Invalid value specified for thisId");
}
variables.thisId = newId;
return this;
}
}
var example = new Example();
example.setThisId(54321);
example.setThatId(54321);
writeoutput(serializeJson(example)); //{"thisId":"54321","thatId":54321}
// throws exceptions:
example.setThisId(true);
example.setThisId({});
最后,回到"casting where necessary and possible"部分。对于给定的示例,如果您尝试将组件实例传递给 setThisId()
方法,它会在类型验证步骤中失败,这意味着类型转换对于该操作是 必需的 成功。因此,将检查该值是否存在 类型转换的可能性 。如果组件(这仅适用于 Railo/Lucee)定义了 _toString()
"magic method",则类型转换是 可能 。因为这是可能的,所以将组件转换为字符串,然后将结果传递给 setThisId()
。如果那个魔法方法没有在组件上定义,就不可能进行类型转换,并抛出异常。类似地,对于 structs/arrays,类型转换是 必要的 ,但不是 可能的 ,因为没有为这些类型定义自动序列化,因此导致在抛出异常时。
TL;DR
您可以覆盖 setter 访问器以执行更严格的 type-validation/type-casting。