6

我观察到,当我向 Play Framework Form-class 添加超过 18 个参数时,我得到了一个很长的(对我来说难以理解的)编译错误。

这是记录在案的限制吗?我需要在表单帖子中接收多达 29 个参数。当我从开放标准实现协议时,我不会决定参数的设计和数量。

我是这样映射的:

val registration = Form(mapping(
    "client_type" -> nonEmptyText,
    "client_id" -> optional(nonEmptyText),
    ... up to 29 args, all optional(nonEmptyText)
    ){ (clientType, clientId ...) => RegistrationRequest(clientType, clientId ...) }
     { req => None })

我的策略是以这种方式进行映射,而不是应用/取消应用并创建案例类的层次结构。原因是要解决 Case 类中的 22 个参数限制,这是我遇到的第一个看似任意的限制。最多 18 个 args 映射工作,之后我得到一个很长的编译错误。

可以在此处找到错误消息(太长而无法包含):https ://gist.github.com/2928297

我正在寻找有关如何解决此限制的建议。我知道在 Post 表单中发送 29 个参数是不好的设计,但它仍然应该是可能的。


破解/解决方法/解决方案

好的,这是我一起破解的解决方法(写这篇文章比实施花费的时间要长得多,我破解了大约 30 分钟)

我编写了预处理请求参数并添加组前缀以对某些参数进行分组的函数。然后我使用生成的 Map[String, String] 并继续处理表单类,像往常一样进行验证等。这允许我在映射中使用嵌套案例类并低于 18 个参数限制。

当心:前面有丑陋的代码!我可能不应该展示这样的早期 hacky 代码,但我希望它能帮助其他想要解决方法的人。

def preprocessFormParams(prefix:String, replace:String)(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( _.startsWith(prefix)).map( m => m._1.patch(0, replace, prefix.length)  -> m._2.head )).getOrElse(Map.empty)
def unprocessedFormParams(prefixes:Set[String])(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( !prefixes.contains(_) ).map( m => m._1 -> m._2.head )).getOrElse(Map.empty)

所以这些函数可能应该用于理解或拆分,但这里是:preprocessedFormParms 接受一个前缀并替换它:

val clientParams = preprocessFormParams("client_", "client.")
("client_id" -> "val1", "client_type" -> "val2") becomes ("client.id" -> "val1", "client.type" -> "val2")

当我有 group.key1, group.key2 形式的参数时,我可以像这样嵌套案例类

Form(mapping("client" -> mapping("type" -> nonEmptyText
    "id" -> optional(nonEmptyText),
    "secret" -> optional(nonEmptyText))
    (RegisterClient.apply)(RegisterClient.unapply)
    ... more params ...)
    (RegisterRequest.apply)(RegisterRequest.unapply)

在我的行动中,我继续过滤掉我的每个组

implicit request =>
val clientParams = preprocessFormParams("client_", "client.")       
val applicationParams = preprocessFormParams("application_", "application.")
val unprocessedParams = unprocessedFormParams(Set("client_", "application_"))
val processedForm = clientParams ++ applicationParams ++ unprocessedParams

最后,我可以像平常一样应用我的表单,但现在我得到了嵌套结构,它减少了参数的数量,并希望使案例类更易于管理。

clientRegistrationForm.bind(processedForm).fold( ... )

使用这种方法,您可以减少参数的数量。如果您的参数没有像我的问题那样易于分组的相同前缀,那么您仍然可以使用相同的基本方法,但可以根据其他标准进行过滤。

4

3 回答 3

4

你使用的mapping方法不是单一的方法,而是重载的。对于单个参数,它有两个类型参数,一个用于结果类型,一个用于您正在使用的元素。它构造一个ObjectMapping1. 对于两个参数,它具有三个类型参数,并构造一个ObjectMapping2.

正如您所注意到的,这些ObjectMappingX类被定义为 。ObjectMapping18您可以在 Play 的源代码中找到它 play/api/data/Forms.scala

推荐的解决方案是避免这种大小的非嵌套形式。如果这是不可避免的,您可以使用与内置 Play 不同的库,或者您可以定义缺少的ObjectMappingX对象和相应的方法来自己构建它们。

于 2012-06-14T06:50:38.270 回答
3

几周前,我就这个问题开了一张票。

如果你投票给它,也许它会得到 Play 开发者的关注。

怀疑它在他们的优先级列表中很高(不幸的是,它或多或少只是复制粘贴到 19、20、21 和 22 映射[T])

如果你很绝望,你可以分叉 Play;否则,想出一个解决方法,例如,利用嵌套表单或将 > 22 个字段模型拆分为单独的表单。

于 2012-06-14T06:48:17.803 回答
2

前几天我不得不解决这个限制并且没有找到这个 SO 帖子并想出了一种不同的做事方法,尽管它看起来有点不稳定,但它似乎有效。

我们的表单组件

import play.api.data.Form
import play.api.data.Forms._

case class P1_18(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String)

case class P2_18(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String)

case class P36(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String,f19: String,f20: String,f21: String,f22: String,f23: String,f24: String,f25: String,f26: String,f27: String,f28: String,f29: String,f30: String,f31: String,f32: String,f33: String,f34: String,f35: String,f36: String)

P36 是你真正想要的对象,P1/P2 只是你用来在框架约束内构建它的类,我在我的实际应用程序中将这些私有到包装表单的对象。

然后我们有了表单定义,这就是神奇发生的地方:

val f = Form(
    mapping(
    "" -> mapping(
        "f1" -> text,
        "f2" -> text,
        "f3" -> text,
        "f4" -> text,
        "f5" -> text,
        "f6" -> text,
        "f7" -> text,
        "f8" -> text,
        "f9" -> text,
        "f10" -> text,
        "f11" -> text,
        "f12" -> text,
        "f13" -> text,
        "f14" -> text,
        "f15" -> text,
        "f16" -> text,
        "f17" -> text,
        "f18" -> text
    )(P1_18.apply)(P1_18.unapply),
    "" -> mapping(
        "f19" -> text,
        "f20" -> text,
        "f21" -> text,
        "f22" -> text,
        "f23" -> text,
        "f24" -> text,
        "f25" -> text,
        "f26" -> text,
        "f27" -> text,
        "f28" -> text,
        "f29" -> text,
        "f30" -> text,
        "f31" -> text,
        "f32" -> text,
        "f33" -> text,
        "f34" -> text,
        "f35" -> text,
        "f36" -> text
    )(P2_18.apply)(P2_18.unapply)
    )(
    (p1, p2) =>
        P36(
            f1 = p1.f1,
            f2 = p1.f2,
            f3 = p1.f3,
            f4 = p1.f4,
            f5 = p1.f5,
            f6 = p1.f6,
            f7 = p1.f7,
            f8 = p1.f8,
            f9 = p1.f9,
            f10 = p1.f10,
            f11 = p1.f11,
            f12 = p1.f12,
            f13 = p1.f13,
            f14 = p1.f14,
            f15 = p1.f15,
            f16 = p1.f16,
            f17 = p1.f17,
            f18 = p1.f18,
            f19 = p2.f1,
            f20 = p2.f2,
            f21 = p2.f3,
            f22 = p2.f4,
            f23 = p2.f5,
            f24 = p2.f6,
            f25 = p2.f7,
            f26 = p2.f8,
            f27 = p2.f9,
            f28 = p2.f10,
            f29 = p2.f11,
            f30 = p2.f12,
            f31 = p2.f13,
            f32 = p2.f14,
            f33 = p2.f15,
            f34 = p2.f16,
            f35 = p2.f17,
            f36 = p2.f18
        )
    )(
        p => {
            val p1 = P1_18(p.f1,p.f2,p.f3,p.f4,p.f5,p.f6,p.f7,p.f8,p.f9,p.f10,p.f11,p.f12,p.f13,p.f14,p.f15,p.f16,p.f17,p.f18)
            val p2 = P2_18(p.f19,p.f20,p.f21,p.f22,p.f23,p.f24,p.f25,p.f26,p.f27,p.f28,p.f29,p.f30,p.f31,p.f32,p.f33,p.f34,p.f35,p.f36)
            Option(
                (p1,p2)
            )
        }
    )
)

你可能会说:嗯。呃,对不起,你有一个空键绑定了两次。这怎么可能行得通?我说:

val dataSeq = for(i <- 1 to 36) yield s"f${i}" -> s"text no. #${i}"
val filledFormFromMap = f.bind(dataSeq.toMap)

filledFormFromMap.value

// res9: Option[P36] = Some(P36(text no. #1,text no. #2,text no. #3,text no. #4,text no. #5,text no. #6,text no. #7,text no. #8,text no. #9,text no. #10,text no. #11,text no. #12,text no. #13,text no. #14,text no. #15,text no. #16,text no. #17,text no. #18,text no. #19,text no. #20,text no. #21,text no. #22,text no. #23,text no. #24,text no. #25,text no. #26,text no. #27,text no. #28,text no. #29,text no. #30,text no. #31,text no. #32,text no. #33,text no. #34,text no. #35,text no. #36))

事实上,它确实可以毫无问题地工作。18 对象映射限制的问题不在于表单内部不能支持超过 18 个字段,而是绑定不支持它。但是,当我查看ObjectMapping 源时,我注意到默认情况下keyObjectMapping 是一个空字符串。并且字段绑定与给定的前缀绑定,然后还与所述前缀绑定:

val field1 = f1._2.withPrefix(f1._1).withPrefix(key)

这让我意识到表单的“顶部”只是一个空键。除了猖獗的好奇之外,我无缘无故地尝试了两个空键,因为您可以在 ObjectMapping 2中看到空键用于两个字段:

  val field1 = f1._2.withPrefix(f1._1).withPrefix(key)

  val field2 = f2._2.withPrefix(f2._1).withPrefix(key)

由于mappings字段中的字段Mapping只是一个Seq[Mapping],我认为在深层合并方法中的所有内容以及我们没有使用键会冲突的映射,而是它们以非破坏性方式组合,因为它们都分享这个顶级密钥,这就是(我相信)playfield.nested.thing如何根据您嵌套映射本身的方式生成您的映射。因此,总而言之,这意味着您可以对同一个键(或至少与空字符串)进行多个绑定,因此可以通过将其分解为更小的组件然后提供手册applyunapply组合事物的方法(反对尝试使用 P36.apply 和 P36.unapply ,因为我相信由于元组限制而这些不起作用)

于 2017-09-13T14:10:17.130 回答