-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontainer.go
185 lines (164 loc) · 5.58 KB
/
container.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package di
import (
"fmt"
"reflect"
"strings"
)
type ContainerInterface interface {
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* panic if No entry was found for **this** identifier.
*
* @return mixed Entry.
*/
Get(id string) *any
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `Has(id)` returning true does not mean that `Get(id)` will not throw an exception.
* It does however mean that `Get(id)` will not panic if the Service is not existing.
*
* @param string id Identifier of the entry to look for.
*
* @return bool
*/
Has(id string) bool
}
type Container struct {
Compiled bool
// Holds Service-Parameters
ParameterBag *ParameterBag
// Holds compiled Services with all Deps
Instances map[string]any
// Storage of object definitions.
Definitions map[string]*Definition
// Used to collect IDs of objects instantiated during build to detect circular references.
Building map[any]bool
// Tags
TaggedServices map[string][]string
compilerPasses []func(c *Container)
}
func NewContainer() *Container {
container := &Container{}
container.Compiled = false
container.ParameterBag = NewParameterBag()
container.Instances = make(map[string]any)
container.Definitions = make(map[string]*Definition)
container.Building = make(map[any]bool)
container.TaggedServices = make(map[string][]string)
return container
}
func (this *Container) AddParameter(name string, value string) {
if this.Compiled {
panic("Container is allready compiled, you cant add Parameters anymore!")
}
this.ParameterBag.Set(name,value)
}
func (this *Container) Get(id string) any {
if !this.Compiled {
this.Compile()
}
return this.getInternal(id)
}
func (this *Container) getInternal(id string) any {
service,ok := this.Instances[id]
if !ok {
panic(fmt.Sprintf("Requested Service %s not found!",id))
}
return service
}
func (this *Container) getDefinition(id string) *Definition {
if this.Compiled {
panic("Container is allready compiled, you cant access Definitions anymore!")
}
definition,ok := this.Definitions[id]
if !ok {
panic(fmt.Sprintf("Definition '%s' not found!", id))
}
return definition
}
func (this *Container) Has(id string) bool {
if !this.Compiled {
this.Compile()
}
_,ok := this.Instances[id]
return ok
}
func (this *Container) Add(id string, service any) *Definition {
if this.Compiled {
panic("Container is allready compiled, you cant add services anymore!")
}
if reflect.TypeOf(service).Kind() != reflect.Pointer {
panic(fmt.Sprintf("[%s] Unsupported Type! Please declare your Services as Pointer-Structs!",id))
}
this.Definitions[id] = &Definition{Id: id,Service: service}
return this.Definitions[id]
}
func (this *Container) AddCompilerPass(cpfn func(c *Container)) {
this.compilerPasses = append(this.compilerPasses,cpfn)
}
func (this *Container) Compile() {
for id,definition := range this.Definitions {
this.Instances[id] = this.build(definition)
}
this.Compiled = true
for _,cpfn := range this.compilerPasses {
cpfn(this)
}
}
func (this *Container) build(def *Definition) any {
service := def.Service
for _,tag := range def.Tags {
this.registerTag(def.Id,tag)
}
rtype := reflect.TypeOf(service)
if _,allreadyBuilding := this.Building[service]; allreadyBuilding {
var builds []string
for defInBuild := range this.Building {
builds = append(builds,fmt.Sprintf("%#v",defInBuild))
}
panic(fmt.Sprintf(`Circular reference to %s detected while building: %s.`,rtype,strings.Join(builds,",")))
}
this.Building[service] = true
vf := reflect.VisibleFields(reflect.TypeOf(reflect.ValueOf(service).Elem().Interface()))
for _, field := range vf {
// Inject a service
if serviceVal, ok := field.Tag.Lookup("service"); ok {
if serviceVal == "" {
panic(fmt.Sprintf("service-Tag must not be empty for %s/%s::%s",rtype.PkgPath(),rtype.Name(),field.Name))
}
dependService := this.getDefinition(serviceVal).Service
reflect.ValueOf(service).Elem().FieldByName(field.Name).Set(reflect.ValueOf(dependService))
}
// inject a param
if serviceVal, ok := field.Tag.Lookup("serviceparam"); ok {
if serviceVal == "" {
panic(fmt.Sprintf("serviceparam-Tag must not be empty for %s/%s::%s",rtype.PkgPath(),rtype.Name(),field.Name))
}
param, exists := this.ParameterBag.Get(serviceVal)
if !exists {
panic(fmt.Sprintf("serviceparameter '%s' not found! Make sure you have added Parameters!", param))
}
reflect.ValueOf(service).Elem().FieldByName(field.Name).Set(reflect.ValueOf(param))
}
}
return service
}
func (this *Container) registerTag(id string, tag string) {
this.TaggedServices[tag] = append(this.TaggedServices[tag],id);
}
func (this *Container) GetTaggedServices(tag string) ([]any,bool) {
ids,exist := this.TaggedServices[tag]
if !exist {
return nil,false
}
var taggedServices []any
for _,id := range ids {
taggedServices = append(taggedServices,this.getInternal(id))
}
return taggedServices,true
}