From d5a38a0cd6ed5d69f4d2f88b446a0a0e04f1880c Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 18 Dec 2017 00:16:10 +0200 Subject: [PATCH] more checks about creating new instance of controller on each request - this time if all bindings are static then set them to the initial-devpassed controller and if the total number of lengths are equal with these static dependencies then we ignore the injector and use the initial controller on each request - maximize the performance when simple controller is used - need more cleanup before new release but I hope until Christmas iris developers will be amazed Former-commit-id: 32ed69368d1df2c25cdb712bb7f0cf47b2e36c05 --- _examples/mvc/hello-world/main.go | 14 +- .../src/web/controllers/todo_controller.go | 4 +- context/context.go | 2 + context/gzip_response_writer.go | 2 +- context/response_recorder.go | 2 +- context/response_writer.go | 2 +- mvc/README.md | 4 +- mvc/controller.go | 134 ++++++++++++------ mvc/controller_handle_test.go | 10 +- mvc/controller_test.go | 39 ++--- mvc/di/di.go | 16 +-- mvc/di/reflect.go | 6 + mvc/di/struct.go | 11 ++ mvc/di/values.go | 30 +++- mvc/engine.go | 22 +-- mvc/func_result_test.go | 4 +- mvc/ideas/1/main.go | 14 ++ mvc/mvc.go | 25 ++-- mvc/session_controller.go | 8 +- 19 files changed, 232 insertions(+), 117 deletions(-) diff --git a/_examples/mvc/hello-world/main.go b/_examples/mvc/hello-world/main.go index ca8550be..fdac99cb 100644 --- a/_examples/mvc/hello-world/main.go +++ b/_examples/mvc/hello-world/main.go @@ -80,22 +80,22 @@ func (c *ExampleController) GetHello() interface{} { return map[string]string{"message": "Hello Iris!"} } -// BeforeActivate called once, before the controller adapted to the main application +// BeforeActivation called once, before the controller adapted to the main application // and of course before the server ran. // After version 9 you can also add custom routes for a specific controller's methods. // Here you can register custom method's handlers // use the standard router with `ca.Router` to do something that you can do without mvc as well, // and add dependencies that will be binded to a controller's fields or method function's input arguments. -func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) { +func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) { anyMiddlewareHere := func(ctx iris.Context) { ctx.Application().Logger().Warnf("Inside /custom_path") ctx.Next() } - ca.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere) + b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere) // or even add a global middleware based on this controller's router, // which in this example is the root "/": - // ca.Router.Use(myMiddleware) + // b.Router().Use(myMiddleware) } // CustomHandlerWithoutFollowingTheNamingGuide serves @@ -140,11 +140,13 @@ func (c *ExampleController) Any() {} -func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) { +func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) { // 1 -> the HTTP Method // 2 -> the route's path // 3 -> this controller's method name that should be handler for that route. - ca.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...) + b.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...) } +func (c *ExampleController) AfterActivation(a mvc.AfterActivation) + */ diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go index 5536e0fd..744eb522 100644 --- a/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go +++ b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go @@ -16,11 +16,11 @@ type TodoController struct { Session *sessions.Session } -// BeforeActivate called once before the server ran, and before +// BeforeActivation called once before the server ran, and before // the routes and dependency binder builded. // You can bind custom things to the controller, add new methods, add middleware, // add dependencies to the struct or the method(s) and more. -func (c *TodoController) BeforeActivate(ca *mvc.ControllerActivator) { +func (c *TodoController) BeforeActivation(ca *mvc.ControllerActivator) { // this could be binded to a controller's function input argument // if any, or struct field if any: ca.Dependencies.Add(func(ctx iris.Context) todo.Item { diff --git a/context/context.go b/context/context.go index c9794d09..498ae57c 100644 --- a/context/context.go +++ b/context/context.go @@ -821,6 +821,8 @@ type Context interface { String() string } +var _ Context = (*context)(nil) + // Next calls all the next handler from the handlers chain, // it should be used inside a middleware. func Next(ctx Context) { diff --git a/context/gzip_response_writer.go b/context/gzip_response_writer.go index f9aa2341..05ee0c65 100644 --- a/context/gzip_response_writer.go +++ b/context/gzip_response_writer.go @@ -83,7 +83,7 @@ type GzipResponseWriter struct { disabled bool } -var _ ResponseWriter = &GzipResponseWriter{} +var _ ResponseWriter = (*GzipResponseWriter)(nil) // BeginGzipResponse accepts a ResponseWriter // and prepares the new gzip response writer. diff --git a/context/response_recorder.go b/context/response_recorder.go index 76e7b777..c469dc25 100644 --- a/context/response_recorder.go +++ b/context/response_recorder.go @@ -39,7 +39,7 @@ type ResponseRecorder struct { headers http.Header } -var _ ResponseWriter = &ResponseRecorder{} +var _ ResponseWriter = (*ResponseRecorder)(nil) // Naive returns the simple, underline and original http.ResponseWriter // that backends this response writer. diff --git a/context/response_writer.go b/context/response_writer.go index 04875723..baab5d22 100644 --- a/context/response_writer.go +++ b/context/response_writer.go @@ -115,7 +115,7 @@ type responseWriter struct { beforeFlush func() } -var _ ResponseWriter = &responseWriter{} +var _ ResponseWriter = (*responseWriter)(nil) const ( defaultStatusCode = http.StatusOK diff --git a/mvc/README.md b/mvc/README.md index 51de01be..06001bef 100644 --- a/mvc/README.md +++ b/mvc/README.md @@ -114,7 +114,7 @@ m.Controller(sub, &myController{service: myService}) ``` ```go -NewEngine().Controller(sub.Party("/subsub"), new(myController), func(ca *ControllerActivator) { - ca.Dependencies.Add(myService) +NewEngine().Controller(sub.Party("/subsub"), new(myController), func(b mvc.BeforeActivation) { + b.Dependencies().Add(myService) }) ``` \ No newline at end of file diff --git a/mvc/controller.go b/mvc/controller.go index cba69d78..d36b4032 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -21,29 +21,50 @@ type BaseController interface { EndRequest(context.Context) } +type shared interface { + Name() string + Router() router.Party + Handle(method, path, funcName string, middleware ...context.Handler) *router.Route +} + +type BeforeActivation interface { + shared + Dependencies() *di.Values +} + +type AfterActivation interface { + shared + DependenciesReadOnly() di.ValuesReadOnly + IsRequestScoped() bool +} + +var ( + _ BeforeActivation = (*ControllerActivator)(nil) + _ AfterActivation = (*ControllerActivator)(nil) +) + // ControllerActivator returns a new controller type info description. // Its functionality can be overriden by the end-dev. type ControllerActivator struct { - // the router is used on the `Activate` and can be used by end-dev on the `BeforeActivate` - // to register any custom controller's functions as handlers but we will need it here - // in order to not create a new type like `ActivationPayload` for the `BeforeActivate`. - Router router.Party + // the router is used on the `Activate` and can be used by end-dev on the `BeforeActivation` + // to register any custom controller's methods as handlers. + router router.Party // initRef BaseController // the BaseController as it's passed from the end-dev. Value reflect.Value // the BaseController's Value. Type reflect.Type // raw type of the BaseController (initRef). // FullName it's the last package path segment + "." + the Name. // i.e: if login-example/user/controller.go, the FullName is "user.Controller". - FullName string + fullName string // the methods names that is already binded to a handler, - // the BeginRequest, EndRequest and BeforeActivate are reserved by the internal implementation. + // the BeginRequest, EndRequest and BeforeActivation are reserved by the internal implementation. reservedMethods []string // the bindings that comes from the Engine and the controller's filled fields if any. // Can be binded to the the new controller's fields and method that is fired // on incoming requests. - Dependencies *di.D + dependencies di.Values // on activate. injector *di.StructInjector @@ -59,7 +80,7 @@ func getNameOf(typ reflect.Type) string { return fullname } -func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator { +func newControllerActivator(router router.Party, controller interface{}, dependencies di.Values) *ControllerActivator { var ( val = reflect.ValueOf(controller) typ = val.Type() @@ -68,13 +89,17 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D fullName = getNameOf(typ) ) + // add the manual filled fields to the dependencies. + filledFieldValues := di.LookupNonZeroFieldsValues(val) + dependencies.AddValue(filledFieldValues...) + c := &ControllerActivator{ // give access to the Router to the end-devs if they need it for some reason, // i.e register done handlers. - Router: router, + router: router, Value: val, Type: typ, - FullName: fullName, + fullName: fullName, // set some methods that end-dev cann't use accidentally // to register a route via the `Handle`, // all available exported and compatible methods @@ -85,27 +110,14 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D // TODO: now that BaseController is totally optionally // we have to check if BeginRequest and EndRequest should be here. reservedMethods: whatReservedMethods(typ), - Dependencies: d, - } - - filledFieldValues := di.LookupNonZeroFieldsValues(val) - c.Dependencies.AddValue(filledFieldValues...) - - if len(filledFieldValues) == di.IndirectType(typ).NumField() { - // all fields are filled by the end-developer, - // the controller doesn't contain any other field, not any dynamic binding as well. - // Therefore we don't need to create a new controller each time. - // Set the c.injector now instead on the first `Handle` and set it to invalid state - // in order to `buildControllerHandler` ignore - // creating new controller value on each incoming request. - c.injector = &di.StructInjector{Valid: false} + dependencies: dependencies, } return c } func whatReservedMethods(typ reflect.Type) []string { - methods := []string{"BeforeActivate"} + methods := []string{"BeforeActivation", "AfterActivation"} if isBaseController(typ) { methods = append(methods, "BeginRequest", "EndRequest") } @@ -113,6 +125,22 @@ func whatReservedMethods(typ reflect.Type) []string { return methods } +func (c *ControllerActivator) Dependencies() *di.Values { + return &c.dependencies +} + +func (c *ControllerActivator) DependenciesReadOnly() di.ValuesReadOnly { + return c.dependencies +} + +func (c *ControllerActivator) Name() string { + return c.fullName +} + +func (c *ControllerActivator) Router() router.Party { + return c.router +} + // IsRequestScoped returns new if each request has its own instance // of the controller and it contains dependencies that are not manually // filled by the struct initialization from the caller. @@ -150,8 +178,8 @@ func (c *ControllerActivator) parseMethod(m reflect.Method) { httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod) if err != nil { if err != errSkip { - err = fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.FullName, m.Name, err) - c.Router.GetReporter().AddErr(err) + err = fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.fullName, m.Name, err) + c.router.GetReporter().AddErr(err) } return @@ -197,16 +225,16 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . m, ok := c.Type.MethodByName(funcName) if !ok { err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", - funcName, c.FullName) - c.Router.GetReporter().AddErr(err) + funcName, c.fullName) + c.router.GetReporter().AddErr(err) return nil } // parse a route template which contains the parameters organised. - tmpl, err := macro.Parse(path, c.Router.Macros()) + tmpl, err := macro.Parse(path, c.router.Macros()) if err != nil { - err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.FullName, funcName, err) - c.Router.GetReporter().AddErr(err) + err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.fullName, funcName, err) + c.router.GetReporter().AddErr(err) return nil } @@ -222,11 +250,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // end-dev's controller pointer. pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...) // get the function's input arguments' bindings. - funcDependencies := c.Dependencies.Clone() + funcDependencies := c.dependencies.Clone() funcDependencies.AddValue(pathParams...) - // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies.Values) - funcInjector := funcDependencies.Func(m.Func) + // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) + funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) // the element value, not the pointer, wil lbe used to create a @@ -237,7 +265,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // hasStructInjector = c.injector != nil && c.injector.Valid // hasFuncInjector = funcInjector != nil && funcInjector.Valid // because - // the `Handle` can be called from `BeforeActivate` callbacks + // the `Handle` can be called from `BeforeActivation` callbacks // and before activation, the c.injector is nil because // we may not have the dependencies binded yet. But if `c.injector.Valid` // inside the Handelr works because it's set on the `activate()` method. @@ -246,24 +274,48 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . // so the user should bind the dependencies needed before the `Handle` // this is a logical flow, so we will choose that one -> if c.injector == nil { - c.injector = c.Dependencies.Struct(c.Value) + // check if manually filled + any dependencies are only static, if so + // and the total struct's fields are equal these static dependencies length + // then we don't need to create a new struct on each request. + // + // We use our custom NumFields here because the std "reflect" package + // checks only for the current struct and not for embedded's exported fields. + totalFieldsLength := di.NumFields(di.IndirectType(c.Type)) + + // first, set these bindings to the passed controller, they will be useless + // if the struct contains any dynamic value because this controller will + // be never fired as it's but we make that in order to get the length of the static + // matched dependencies of the struct. + c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...) + matchedStaticDependenciesLength := c.injector.InjectElemStaticOnly(di.IndirectValue(c.Value)) + if c.injector.Valid { - golog.Debugf("MVC dependencies of '%s':\n%s", c.FullName, c.injector.String()) + golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String()) + } + + if matchedStaticDependenciesLength == totalFieldsLength { + // all fields are filled by the end-developer or via static dependencies (if context is there then it will be filled by the MakeStructInjector so we don't worry about it), + // the controller doesn't contain any other field neither any dynamic binding as well. + // Therefore we don't need to create a new controller each time. + // Set the c.injector now instead on the first `Handle` and set it to invalid state + // in order to `buildControllerHandler` ignore the + // creation of a new controller value on each incoming request. + c.injector = &di.StructInjector{Valid: false} } } if funcInjector.Valid { - golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.FullName, funcName, funcInjector.String()) + golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, funcName, funcInjector.String()) } handler := buildControllerHandler(m, c.Type, c.Value, c.injector, funcInjector, funcIn) // register the handler now. - route := c.Router.Handle(method, path, append(middleware, handler)...) + route := c.router.Handle(method, path, append(middleware, handler)...) if route != nil { // change the main handler's name in order to respect the controller's and give // a proper debug message. - route.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) + route.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName) } return route diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index 9be5dd29..a373f18c 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -17,11 +17,11 @@ type testControllerHandle struct { reqField string } -func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) { - ca.Handle("GET", "/histatic", "HiStatic") - ca.Handle("GET", "/hiservice", "HiService") - ca.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") - ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") +func (c *testControllerHandle) BeforeActivation(b BeforeActivation) { // BeforeActivation(t *mvc.TController) { + b.Handle("GET", "/histatic", "HiStatic") + b.Handle("GET", "/hiservice", "HiService") + b.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") + b.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") } func (c *testControllerHandle) BeginRequest(ctx iris.Context) { diff --git a/mvc/controller_test.go b/mvc/controller_test.go index b2c7d75e..24ed310b 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -275,7 +275,7 @@ func (t *testControllerBindDeep) Get() { t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other) } -func TestControllerBind(t *testing.T) { +func TestControllerDependencies(t *testing.T) { app := iris.New() // app.Logger().SetLevel("debug") @@ -421,8 +421,8 @@ type testControllerActivateListener struct { TitlePointer *testBindType } -func (c *testControllerActivateListener) BeforeActivate(ca *ControllerActivator) { - ca.Dependencies.AddOnce(&testBindType{title: "default title"}) +func (c *testControllerActivateListener) BeforeActivation(b BeforeActivation) { + b.Dependencies().AddOnce(&testBindType{title: "default title"}) } func (c *testControllerActivateListener) Get() string { @@ -454,9 +454,27 @@ func TestControllerActivateListener(t *testing.T) { } type testControllerNotCreateNewDueManuallySettingAllFields struct { + T *testing.T + TitlePointer *testBindType } +func (c *testControllerNotCreateNewDueManuallySettingAllFields) AfterActivation(a AfterActivation) { + if n := a.DependenciesReadOnly().Len(); n != 2 { + c.T.Fatalf(`expecting 2 dependency, the 'T' and the 'TitlePointer' that we manually insert + and the fields total length is 2 so it will not create a new controller on each request + however the dependencies are available here + although the struct injector is being ignored when + creating the controller's handlers because we set it to invalidate state at "newControllerActivator" + -- got dependencies length: %d`, n) + } + + if a.IsRequestScoped() { + c.T.Fatalf(`this controller shouldn't be tagged used as request scoped(create new instances on each request), + it doesn't contain any dynamic value or dependencies that should be binded via the iris mvc engine`) + } +} + func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string { return c.TitlePointer.title } @@ -464,23 +482,12 @@ func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string { func TestControllerNotCreateNewDueManuallySettingAllFields(t *testing.T) { app := iris.New() NewEngine().Controller(app, &testControllerNotCreateNewDueManuallySettingAllFields{ + T: t, TitlePointer: &testBindType{ title: "my title", }, - }, func(ca *ControllerActivator) { - if n := len(ca.Dependencies.Values); n != 1 { - t.Fatalf(`expecting 1 dependency, the 'TitlePointer' which we manually insert - and the fields length is 1 so it will not create a new controller on each request - however the dependencies are available here - although the struct injector is being ignored when - creating the controller's handlers because we set it to invalidate state at "newControllerActivator" - -- got dependencies length: %d`, n) - } + }, func(b BeforeActivation) { - if ca.IsRequestScoped() { - t.Fatalf(`this controller shouldn't be tagged used as request scoped(create new instances on each request), - it doesn't contain any dynamic value or dependencies that should be binded via the iris mvc engine`) - } }) e := httptest.New(t, app) diff --git a/mvc/di/di.go b/mvc/di/di.go index d56bed86..adc069ac 100644 --- a/mvc/di/di.go +++ b/mvc/di/di.go @@ -44,19 +44,11 @@ func (d *D) GoodFunc(fn TypeChecker) *D { // Clone returns a new Dependency Injection container, it adopts the // parent's (current "D") hijacker, good func type checker and all dependencies values. func (d *D) Clone() *D { - clone := New() - clone.hijacker = d.hijacker - clone.goodFunc = d.goodFunc - - // copy the current dynamic bindings (func binders) - // and static struct bindings (services) to this new child. - if n := len(d.Values); n > 0 { - values := make(Values, n, n) - copy(values, d.Values) - clone.Values = values + return &D{ + Values: d.Values.Clone(), + hijacker: d.hijacker, + goodFunc: d.goodFunc, } - - return clone } // Struct is being used to return a new injector based on diff --git a/mvc/di/reflect.go b/mvc/di/reflect.go index 0c28bf02..3b03e6c0 100644 --- a/mvc/di/reflect.go +++ b/mvc/di/reflect.go @@ -125,6 +125,12 @@ type field struct { AnyValue reflect.Value } +// NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported, +// it will check for its exported fields. +func NumFields(elemTyp reflect.Type) int { + return len(lookupFields(elemTyp, nil)) +} + func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { if elemTyp.Kind() != reflect.Struct { return diff --git a/mvc/di/struct.go b/mvc/di/struct.go index 2ee2c07a..aaa88f89 100644 --- a/mvc/di/struct.go +++ b/mvc/di/struct.go @@ -97,6 +97,17 @@ func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value } } +func (s *StructInjector) InjectElemStaticOnly(destElem reflect.Value) (n int) { + for _, f := range s.fields { + if f.Object.BindType != Static { + continue + } + destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value) + n++ + } + return +} + func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value { dest := reflect.New(s.elemType) s.InjectElem(dest, ctx...) diff --git a/mvc/di/values.go b/mvc/di/values.go index 64301aee..da9342bb 100644 --- a/mvc/di/values.go +++ b/mvc/di/values.go @@ -2,12 +2,35 @@ package di import "reflect" +type ValuesReadOnly interface { + // Has returns true if a binder responsible to + // bind and return a type of "typ" is already registered to this controller. + Has(value interface{}) bool + // Len returns the length of the values. + Len() int +} + type Values []reflect.Value func NewValues() Values { return Values{} } +// Clone returns a copy of the current values. +func (bv Values) Clone() Values { + if n := len(bv); n > 0 { + values := make(Values, n, n) + copy(values, bv) + return values + } + + return NewValues() +} + +func (bv Values) Len() int { + return len(bv) +} + // Add binds values to this controller, if you want to share // binding values between controllers use the Engine's `Bind` function instead. func (bv *Values) Add(values ...interface{}) { @@ -57,13 +80,12 @@ func (bv *Values) remove(typ reflect.Type, n int) (ok bool) { // Has returns true if a binder responsible to // bind and return a type of "typ" is already registered to this controller. -func (bv *Values) Has(value interface{}) bool { +func (bv Values) Has(value interface{}) bool { return bv.valueTypeExists(reflect.TypeOf(value)) } -func (bv *Values) valueTypeExists(typ reflect.Type) bool { - input := *bv - for _, in := range input { +func (bv Values) valueTypeExists(typ reflect.Type) bool { + for _, in := range bv { if equalTypes(in.Type(), typ) { return true } diff --git a/mvc/engine.go b/mvc/engine.go index e835fa4a..269e2032 100644 --- a/mvc/engine.go +++ b/mvc/engine.go @@ -20,7 +20,7 @@ import ( // // For a more high-level structure please take a look at the "mvc.go#Application". type Engine struct { - Dependencies *di.D + Dependencies di.Values } // NewEngine returns a new engine, a container for dependencies and a factory @@ -28,7 +28,7 @@ type Engine struct { // Please take a look at the structure's documentation for more information. func NewEngine() *Engine { return &Engine{ - Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker), + Dependencies: di.NewValues(), } } @@ -46,7 +46,7 @@ func (e *Engine) Clone() *Engine { // It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application, // as middleware or as simple route handler or subdomain's handler. func (e *Engine) Handler(handler interface{}) context.Handler { - h, err := MakeHandler(handler, e.Dependencies.Values...) + h, err := MakeHandler(handler, e.Dependencies.Clone()...) if err != nil { golog.Errorf("mvc handler: %v", err) } @@ -84,8 +84,8 @@ func (e *Engine) Handler(handler interface{}) context.Handler { // where Get is an HTTP Method func. // // Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc. -func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) { - ca := newControllerActivator(router, controller, e.Dependencies) +func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(BeforeActivation)) { + ca := newControllerActivator(router, controller, e.Dependencies.Clone()) // give a priority to the "beforeActivate" // callbacks, if any. @@ -93,13 +93,19 @@ func (e *Engine) Controller(router router.Party, controller interface{}, beforeA cb(ca) } - // check if controller has an "BeforeActivate" function + // check if controller has an "BeforeActivation" function // which accepts the controller activator and call it. if activateListener, ok := controller.(interface { - BeforeActivate(*ControllerActivator) + BeforeActivation(BeforeActivation) }); ok { - activateListener.BeforeActivate(ca) + activateListener.BeforeActivation(ca) } ca.activate() + + if afterActivateListener, ok := controller.(interface { + AfterActivation(AfterActivation) + }); ok { + afterActivateListener.AfterActivation(ca) + } } diff --git a/mvc/func_result_test.go b/mvc/func_result_test.go index 7de6c688..424f6734 100644 --- a/mvc/func_result_test.go +++ b/mvc/func_result_test.go @@ -264,8 +264,8 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result { func TestControllerViewResultRespectCtxViewData(t *testing.T) { app := iris.New() - NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) { - ca.Dependencies.Add(t) + NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(b BeforeActivation) { + b.Dependencies().Add(t) }) e := httptest.New(t, app) diff --git a/mvc/ideas/1/main.go b/mvc/ideas/1/main.go index 88c120d1..93e31a1e 100644 --- a/mvc/ideas/1/main.go +++ b/mvc/ideas/1/main.go @@ -69,6 +69,16 @@ type TodoController struct { Session *sessions.Session } +func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) { + b.Handle("GET", "/custom", "Custom") +} + +func (c *TodoController) AfterActivation(b mvc.BeforeActivation) { + if !b.IsRequestScoped() { + panic("TodoController should be request scoped, we have a 'Session' which depends on the context.") + } +} + func (c *TodoController) Get() string { count := c.Session.Increment("count", 1) @@ -77,6 +87,10 @@ func (c *TodoController) Get() string { return body } +func (c *TodoController) Custom() string { + return "custom" +} + type TodoSubController struct { Session *sessions.Session } diff --git a/mvc/mvc.go b/mvc/mvc.go index 98241d57..08744f15 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -25,13 +25,13 @@ func newApp(engine *Engine, subRouter router.Party) *Application { } } -// New returns a new mvc Application based on a "subRouter". +// New returns a new mvc Application based on a "party". // Application creates a new engine which is responsible for binding the dependencies // and creating and activating the app's controller(s). // -// Example: `New(app.Party("/todo"))`. -func New(subRouter router.Party) *Application { - return newApp(NewEngine(), subRouter) +// Example: `New(app.Party("/todo"))` or `New(app)` as it's the same as `New(app.Party("/"))`. +func New(party router.Party) *Application { + return newApp(NewEngine(), party) } // Configure can be used to pass one or more functions that accept this @@ -53,7 +53,7 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat // controller's methods, if matching. // // The dependencies can be changed per-controller as well via a `beforeActivate` -// on the `Register` method or when the controller has the `BeforeActivate(c *ControllerActivator)` +// on the `Register` method or when the controller has the `BeforeActivation(c *ControllerActivator)` // method defined. // // It returns this Application. @@ -75,16 +75,17 @@ func (app *Application) AddDependencies(values ...interface{}) *Application { // It returns this Application. // // Example: `.Register(new(TodoController))`. -func (app *Application) Register(controller interface{}, beforeActivate ...func(*ControllerActivator)) *Application { +func (app *Application) Register(controller interface{}, beforeActivate ...func(BeforeActivation)) *Application { app.Engine.Controller(app.Router, controller, beforeActivate...) return app } -// NewChild creates and returns a new Application which will be adapted -// to the "subRouter", it adopts -// the dependencies bindings from the parent(current) one. +// NewChild creates and returns a new MVC Application which will be adapted +// to the "party", it adopts +// the parent's (current) dependencies, the "party" may be +// a totally new router or a child path one via the parent's `.Router.Party`. // -// Example: `.NewChild(irisApp.Party("/sub")).Register(new(TodoSubController))`. -func (app *Application) NewChild(subRouter router.Party) *Application { - return newApp(app.Engine.Clone(), subRouter) +// Example: `.NewChild(irisApp.Party("/path")).Register(new(TodoSubController))`. +func (app *Application) NewChild(party router.Party) *Application { + return newApp(app.Engine.Clone(), party) } diff --git a/mvc/session_controller.go b/mvc/session_controller.go index 39bc9b0a..e5670a00 100644 --- a/mvc/session_controller.go +++ b/mvc/session_controller.go @@ -15,13 +15,13 @@ type SessionController struct { Session *sessions.Session } -// BeforeActivate called, once per application lifecycle NOT request, +// BeforeActivation called, once per application lifecycle NOT request, // every single time the dev registers a specific SessionController-based controller. // It makes sure that its "Manager" field is filled // even if the caller didn't provide any sessions manager via the `app.Controller` function. -func (s *SessionController) BeforeActivate(ca *ControllerActivator) { - if didntBindManually := ca.Dependencies.AddOnce(defaultSessionManager); didntBindManually { - ca.Router.GetReporter().Add( +func (s *SessionController) BeforeActivation(b BeforeActivation) { + if didntBindManually := b.Dependencies().AddOnce(defaultSessionManager); didntBindManually { + b.Router().GetReporter().Add( `MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, therefore this controller is using the default sessions manager instead. Please refer to the documentation to learn how you can provide the session manager`)