create one generic package for dependency injection which can be used outside of Iris too - worked but unfished

Former-commit-id: a9d600321c07d7c9f39105416f14ae91528a16a3
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-14 23:04:42 +02:00
parent a5fac270cf
commit 4e15f4ea88
11 changed files with 73 additions and 419 deletions

View File

@ -1,8 +1,10 @@
package controllers
// import "github.com/kataras/iris/mvc2"
// ValuesController is the equivalent
// `ValuesController` of the .net core 2.0 mvc application.
type ValuesController struct{}
type ValuesController struct{} //{ mvc2.C }
/* on windows tests(older) the Get was:
func (vc *ValuesController) Get() {

View File

@ -46,7 +46,7 @@ func main() {
// using an io.Writer for automatic buffer management (i.e. hero built-in buffer pool),
// iris context implements the io.Writer by its ResponseWriter
// which is an enhanced version of the standar http.ResponseWriter
// which is an enhanced version of the standard http.ResponseWriter
// but still 100% compatible.
template.UserListToWriter(userList, ctx)
})

View File

@ -1,258 +1,34 @@
package mvc2
import "reflect"
type bindType uint32
const (
objectType bindType = iota // simple assignable value.
functionResultType // dynamic value, depends on the context.
import (
"github.com/kataras/di"
"reflect"
)
type bindObject struct {
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
Value reflect.Value
BindType bindType
ReturnValue func(ctx []reflect.Value) reflect.Value
}
// makeReturnValue takes any function
// that accept a context and returns something
// and returns a binder function, which accepts the context as slice of reflect.Value
// and returns a reflect.Value for that.
// Iris uses to
// resolve and set the input parameters when a handler is executed.
//
// The "fn" can have the following form:
// `func(iris.Context) UserViewModel`.
//
// The return type of the "fn" should be a value instance, not a pointer, for your own protection.
// The binder function should return only one value and
// it can accept only one input argument,
// the Iris' Context (`context.Context` or `iris.Context`).
func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
typ := indirectTyp(fn.Type())
// invalid if not a func.
if typ.Kind() != reflect.Func {
return nil, typ, errBad
}
// invalid if not returns one single value.
if typ.NumOut() != 1 {
return nil, typ, errBad
}
// invalid if input args length is not one.
if typ.NumIn() != 1 {
return nil, typ, errBad
}
var (
typeChecker = func(fn reflect.Type) bool {
// invalid if that single input arg is not a typeof context.Context.
if !isContext(typ.In(0)) {
return nil, typ, errBad
return isContext(fn.In(0))
}
outTyp := typ.Out(0)
zeroOutVal := reflect.New(outTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
hijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) {
if isContext(fieldOrFuncInput) {
return newContextBindObject(), true
}
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
}
return bf, outTyp, nil
}
func makeBindObject(v reflect.Value) (b bindObject, err error) {
if isFunc(v) {
b.BindType = functionResultType
b.ReturnValue, b.Type, err = makeReturnValue(v)
} else {
b.BindType = objectType
b.Type = v.Type()
b.Value = v
}
return
return nil, false
}
)
// newContextBindObject is being used on both targetFunc and targetStruct.
// if the func's input argument or the struct's field is a type of Context
// then we can do a fast binding using the ctxValue
// which is used as slice of reflect.Value, because of the final method's `Call`.
func newContextBindObject() *bindObject {
return &bindObject{
func newContextBindObject() *di.BindObject {
return &di.BindObject{
Type: contextTyp,
BindType: functionResultType,
BindType: di.Dynamic,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
return ctxValue[0]
},
}
}
func (b *bindObject) IsAssignable(to reflect.Type) bool {
return equalTypes(b.Type, to)
}
func (b *bindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
if b.BindType == functionResultType {
toSetter(b.ReturnValue(ctx))
return
}
toSetter(b.Value)
}
type (
targetField struct {
Object *bindObject
FieldIndex []int
}
targetFuncInput struct {
Object *bindObject
InputIndex int
}
)
type targetStruct struct {
Fields []*targetField
Valid bool // is True when contains fields and it's a valid target struct.
}
func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *targetStruct {
typ := indirectTyp(v.Type())
s := &targetStruct{}
fields := lookupFields(typ, nil)
for _, f := range fields {
// if it's context then bind it directly here and continue to the next field.
if isContext(f.Type) {
s.Fields = append(s.Fields, &targetField{
FieldIndex: f.Index,
Object: newContextBindObject(),
})
continue
}
for _, val := range bindValues {
// the binded values to the struct's fields.
b, err := makeBindObject(val)
if err != nil {
return s // if error stop here.
}
if b.IsAssignable(f.Type) {
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
s.Fields = append(s.Fields, &targetField{
FieldIndex: f.Index,
Object: &b,
})
break
}
}
}
s.Valid = len(s.Fields) > 0
return s
}
func (s *targetStruct) Fill(destElem reflect.Value, ctx ...reflect.Value) {
for _, f := range s.Fields {
f.Object.Assign(ctx, func(v reflect.Value) {
// if isContext(v.Type()) {
// println("WTF BIND CONTEXT TYPE WHEN BASE CONTROLLER?")
// }
destElem.FieldByIndex(f.FieldIndex).Set(v)
})
}
}
type targetFunc struct {
Inputs []*targetFuncInput
Valid bool // is True when contains func inputs and it's a valid target func.
}
func newTargetFunc(fn reflect.Value, bindValues ...reflect.Value) *targetFunc {
typ := indirectTyp(fn.Type())
s := &targetFunc{
Valid: false,
}
if !isFunc(typ) {
return s
}
n := typ.NumIn()
// function input can have many values of the same types,
// so keep track of them in order to not set a func input to a next bind value,
// i.e (string, string) with two different binder funcs because of the different param's name.
consumedValues := make(map[int]bool, n)
for i := 0; i < n; i++ {
inTyp := typ.In(i)
// if it's context then bind it directly here and continue to the next func's input arg.
if isContext(inTyp) {
s.Inputs = append(s.Inputs, &targetFuncInput{
InputIndex: i,
Object: newContextBindObject(),
})
continue
}
for valIdx, val := range bindValues {
if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
continue
}
inTyp := typ.In(i)
// the binded values to the func's inputs.
b, err := makeBindObject(val)
if err != nil {
return s // if error stop here.
}
if b.IsAssignable(inTyp) {
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
// i, b.Type.String(), val.String(), val.Pointer())
s.Inputs = append(s.Inputs, &targetFuncInput{
InputIndex: i,
Object: &b,
})
consumedValues[valIdx] = true
break
}
}
}
s.Valid = len(s.Inputs) > 0
return s
}
func (s *targetFunc) Fill(in *[]reflect.Value, ctx ...reflect.Value) {
args := *in
for _, input := range s.Inputs {
input.Object.Assign(ctx, func(v reflect.Value) {
// fmt.Printf("assign input index: %d for value: %v\n",
// input.InputIndex, v.String())
args[input.InputIndex] = v
})
}
*in = args
}

View File

@ -1,99 +0,0 @@
package mvc2
import (
"reflect"
)
/// TODO:
// create another package because these bindings things are useful
// for other libraries I'm working on, so something like github.com/kataras/di
// will be great, combine these with the bind.go and controller's inside handler
// but generic things.
type ValueStore []reflect.Value
// Bind binds values to this controller, if you want to share
// binding values between controllers use the Engine's `Bind` function instead.
func (bv *ValueStore) Bind(values ...interface{}) {
for _, val := range values {
bv.bind(reflect.ValueOf(val))
}
}
func (bv *ValueStore) bind(v reflect.Value) {
if !goodVal(v) {
return
}
*bv = append(*bv, v)
}
// Unbind unbinds a binding value based on the type,
// it returns true if at least one field is not binded anymore.
//
// The "n" indicates the number of elements to remove, if <=0 then it's 1,
// this is useful because you may have bind more than one value to two or more fields
// with the same type.
func (bv *ValueStore) Unbind(value interface{}, n int) bool {
return bv.unbind(reflect.TypeOf(value), n)
}
func (bv *ValueStore) unbind(typ reflect.Type, n int) (ok bool) {
input := *bv
for i, in := range input {
if equalTypes(in.Type(), typ) {
ok = true
input = input[:i+copy(input[i:], input[i+1:])]
if n > 1 {
continue
}
break
}
}
*bv = input
return
}
// BindExists returns true if a binder responsible to
// bind and return a type of "typ" is already registered to this controller.
func (bv *ValueStore) BindExists(value interface{}) bool {
return bv.bindTypeExists(reflect.TypeOf(value))
}
func (bv *ValueStore) bindTypeExists(typ reflect.Type) bool {
input := *bv
for _, in := range input {
if equalTypes(in.Type(), typ) {
return true
}
}
return false
}
// BindIfNotExists bind a value to the controller's field with the same type,
// if it's not binded already.
//
// Returns false if binded already or the value is not the proper one for binding,
// otherwise true.
func (bv *ValueStore) BindIfNotExists(value interface{}) bool {
return bv.bindIfNotExists(reflect.ValueOf(value))
}
func (bv *ValueStore) bindIfNotExists(v reflect.Value) bool {
var (
typ = v.Type() // no element, raw things here.
)
if !goodVal(v) {
return false
}
if bv.bindTypeExists(typ) {
return false
}
bv.bind(v)
return true
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"reflect"
"github.com/kataras/di"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/core/router/macro"
@ -89,17 +91,16 @@ type ControllerActivator struct {
// the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
reservedMethods []string
// input are always empty after the `activate`
// are used to build the bindings, and we need this field
// because we have 3 states (Engine.Input, OnActivate, Bind)
// that we can add or override binding values.
ValueStore // TODO: or ... this is dirty code I will have to re format it a bit tomorrow.
// 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
// the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields.
bindings *targetStruct
// on activate.
injector *di.StructInjector
}
func newControllerActivator(router router.Party, controller interface{}, bindValues ...reflect.Value) *ControllerActivator {
func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator {
var (
val = reflect.ValueOf(controller)
typ = val.Type()
@ -115,7 +116,7 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal
// the end-developer when declaring the controller,
// activate listeners needs them in order to know if something set-ed already or not,
// look `BindTypeExists`.
bindValues = append(lookupNonZeroFieldsValues(val), bindValues...)
d.Values = append(lookupNonZeroFieldsValues(val), d.Values...)
c := &ControllerActivator{
// give access to the Router to the end-devs if they need it for some reason,
@ -133,20 +134,22 @@ func newControllerActivator(router router.Party, controller interface{}, bindVal
//
// TODO: now that BaseController is totally optionally
// we have to check if BeginRequest and EndRequest should be here.
reservedMethods: []string{
"BeginRequest",
"EndRequest",
"OnActivate",
},
// set the input as []reflect.Value in order to be able
// to check if a bind type is already exists, or even
// override the structBindings that are being generated later on.
ValueStore: bindValues,
reservedMethods: whatReservedMethods(typ),
Dependencies: d,
}
return c
}
func whatReservedMethods(typ reflect.Type) []string {
methods := []string{"OnActivate"}
if isBaseController(typ) {
methods = append(methods, "BeginRequest", "EndRequest")
}
return methods
}
// checks if a method is already registered.
func (c *ControllerActivator) isReservedMethod(name string) bool {
for _, s := range c.reservedMethods {
@ -178,15 +181,8 @@ func (c *ControllerActivator) parseMethods() {
}
}
// SetBindings will override any bindings with the new "values".
func (c *ControllerActivator) SetBindings(values ...reflect.Value) {
// set field index with matching binders, if any.
c.ValueStore = values
c.bindings = newTargetStruct(c.Value, values...)
}
func (c *ControllerActivator) activate() {
c.SetBindings(c.ValueStore...)
c.injector = c.Dependencies.Struct(c.Value)
c.parseMethods()
}
@ -236,11 +232,13 @@ 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.
funcBindings := newTargetFunc(m.Func, pathParams...)
funcDependencies := c.Dependencies.Clone()
funcDependencies.Add(pathParams...)
funcInjector := funcDependencies.Func(m.Func)
// we will make use of 'n' to make a slice of reflect.Value
// to pass into if the function has input arguments that
// are will being filled by the funcBindings.
// are will being filled by the funcDependencies.
n := len(funcIn)
// the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request.
@ -249,19 +247,8 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
implementsBase := isBaseController(c.Type)
handler := func(ctx context.Context) {
// create a new controller instance of that type(>ptr).
ctrl := reflect.New(elemTyp)
// // the Interface(). is faster than MethodByName or pre-selected methods.
// b := ctrl.Interface().(BaseController)
// // init the request.
// b.BeginRequest(ctx)
// // if begin request stopped the execution.
// if ctx.IsStopped() {
// return
// }
if implementsBase {
// the Interface(). is faster than MethodByName or pre-selected methods.
b := ctrl.Interface().(BaseController)
@ -273,34 +260,32 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return
}
// EndRequest will be called at any case except the `BeginRequest` is
// stopped.
defer b.EndRequest(ctx)
}
if !c.bindings.Valid && !funcBindings.Valid {
if !c.injector.Valid && !funcInjector.Valid {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else {
ctxValue := reflect.ValueOf(ctx)
if c.bindings.Valid {
if c.injector.Valid {
elem := ctrl.Elem()
c.bindings.Fill(elem, ctxValue)
c.injector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
return
}
// we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args.
if !funcBindings.Valid {
if !funcInjector.Valid {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
}
}
// otherwise, it has one or more valid input binders,
// make the input and call the func using those.
if funcBindings.Valid {
if funcInjector.Valid {
in := make([]reflect.Value, n, n)
in[0] = ctrl
funcBindings.Fill(&in, ctxValue)
funcInjector.Inject(&in, ctxValue)
if ctx.IsStopped() {
return
}
@ -309,12 +294,6 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
}
}
// if ctx.IsStopped() {
// return
// }
// b.EndRequest(ctx)
}
// register the handler now.

View File

@ -421,8 +421,8 @@ type testControllerActivateListener struct {
}
func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
if !ca.BindExists(&testBindType{}) {
ca.Bind(&testBindType{
if !ca.Dependencies.BindExists(&testBindType{}) {
ca.Dependencies.Bind(&testBindType{
title: "default title",
})
}

View File

@ -2,9 +2,10 @@ package mvc2
import (
"errors"
"reflect"
"github.com/kataras/di"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
@ -16,38 +17,28 @@ var (
)
type Engine struct {
Input []reflect.Value
dependencies *di.D
}
func New() *Engine {
return new(Engine)
return &Engine{
dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
}
func (e *Engine) Bind(values ...interface{}) *Engine {
for _, val := range values {
if v := reflect.ValueOf(val); goodVal(v) {
e.Input = append(e.Input, v)
}
}
e.dependencies.Bind(values...)
return e
}
func (e *Engine) Child() *Engine {
child := New()
// copy the current parent's ctx func binders and services to this new child.
if n := len(e.Input); n > 0 {
input := make([]reflect.Value, n, n)
copy(input, e.Input)
child.Input = input
}
child.dependencies = e.dependencies.Clone()
return child
}
func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.Input...)
h, err := MakeHandler(handler, e.dependencies.Values...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
@ -55,7 +46,7 @@ func (e *Engine) Handler(handler interface{}) context.Handler {
}
func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Input...)
ca := newControllerActivator(router, controller, e.dependencies)
// give a priority to the "onActivate"
// callbacks, if any.

View File

@ -267,7 +267,7 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
ca.Bind(t)
ca.Dependencies.Bind(t)
})
e := httptest.New(t, app)

View File

@ -2,6 +2,7 @@ package mvc2
import (
"fmt"
"github.com/kataras/di"
"reflect"
"runtime"
@ -64,7 +65,7 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand
return h, nil
}
s := newTargetFunc(fn, bindValues...)
s := di.MakeFuncInjector(fn, hijacker, typeChecker, bindValues...)
if !s.Valid {
pc := fn.Pointer()
fpc := runtime.FuncForPC(pc)
@ -72,14 +73,14 @@ func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Hand
callerName := fpc.Name()
err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s",
n, len(s.Inputs), fn.Type().String(), callerFileName, callerLineNumber, callerName)
n, s.Length, fn.Type().String(), callerFileName, callerLineNumber, callerName)
return nil, err
}
h := func(ctx context.Context) {
in := make([]reflect.Value, n, n)
s.Fill(&in, reflect.ValueOf(ctx))
s.Inject(&in, reflect.ValueOf(ctx))
if ctx.IsStopped() {
return
}

View File

@ -110,6 +110,10 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
for i, n := 0, elemTyp.NumField(); i < n; i++ {
f := elemTyp.Field(i)
if f.PkgPath != "" {
continue // skip unexported.
}
if indirectTyp(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)

View File

@ -22,7 +22,7 @@ type SessionController struct {
// 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) OnActivate(ca *ControllerActivator) {
if didntBindManually := ca.BindIfNotExists(defaultSessionManager); didntBindManually {
if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually {
ca.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.