Skip to content

Commit 4504a5e

Browse files
authored
feat: ✨ Ability to override services from autowire (#216)
feat: ✨ Ability to override services from autowire #213
1 parent 197446d commit 4504a5e

File tree

16 files changed

+239
-18
lines changed

16 files changed

+239
-18
lines changed

.github/workflows/build.yml

+15-15
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ name: Build
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66
pull_request:
7-
branches: [ master ]
7+
branches: [master]
88

99
jobs:
1010
build:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
node-version: [14.x, 15.x, 16.x, 17.x]
14+
node-version: [14.x, 15.x, 16.x, 17.x, 18.x, 19.x, 20.x]
1515
steps:
1616
- uses: actions/checkout@v3
1717
- name: Use Node.js ${{ matrix.node-version }}
@@ -24,18 +24,18 @@ jobs:
2424
- run: npm test
2525

2626
coverage:
27-
needs: [ build ]
27+
needs: [build]
2828
name: coverage
2929
runs-on: ubuntu-latest
3030
steps:
31-
- uses: actions/checkout@master
32-
- uses: actions/setup-node@master
33-
with:
34-
node-version: '17'
35-
- run: npm ci
36-
- uses: paambaati/[email protected]
37-
env:
38-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
39-
with:
40-
coverageCommand: npm run test:coverage
41-
coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov
31+
- uses: actions/checkout@master
32+
- uses: actions/setup-node@master
33+
with:
34+
node-version: "17"
35+
- run: npm ci
36+
- uses: paambaati/[email protected]
37+
env:
38+
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
39+
with:
40+
coverageCommand: npm run test:coverage
41+
coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ node_modules/
55
coverage/
66
coverage.lcov
77
npm-debug.log
8-
.DS_STORE
8+
.DS_STORE
9+
.vscode

lib/Autowire.js

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Reference from './Reference'
77
import ServiceFile from './ServiceFile'
88
import AutowireIdentifier from './AutowireIdentifier'
99
import ContainerDefaultDirMustBeSet from './Exception/ContainerDefaultDirMustBeSet'
10+
import PassConfig from './PassConfig'
11+
import AutowireOverridePass from './CompilerPass/AutowireOverridePass'
1012

1113
export default class Autowire {
1214
/**
@@ -121,6 +123,10 @@ export default class Autowire {
121123
if (this._serviceFile instanceof ServiceFile) {
122124
await this._serviceFile.generateFromContainer(this._container)
123125
}
126+
this._container.addCompilerPass(
127+
new AutowireOverridePass(),
128+
PassConfig.TYPE_BEFORE_OPTIMIZATION
129+
)
124130
}
125131

126132
/**
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Reference from '../Reference'
2+
3+
export default class AutowireOverridePass {
4+
/**
5+
* @param {ContainerBuilder} container
6+
*/
7+
process (container) {
8+
this._definitions = container.instanceManager.definitions
9+
const overrideDefinitions = container.instanceManager.searchDefinitionsToOverrideArgs()
10+
const toDelete = []
11+
12+
for (const [key, definitionToOverride] of overrideDefinitions) {
13+
toDelete.push(key)
14+
this._processOverride(definitionToOverride, container)
15+
}
16+
17+
this._removeDefinitions(toDelete)
18+
}
19+
20+
_processOverride (definitionToOverride, container) {
21+
const definitionsToOverride = container.instanceManager.searchNotOverrideDefinitionsByObject(definitionToOverride.Object)
22+
23+
for (const overrideArg of definitionToOverride.overrideArgs) {
24+
const references = this._getReferencesForOverrideArg(overrideArg.id)
25+
26+
for (const [, definitionFromOverride] of definitionsToOverride) {
27+
definitionFromOverride.args = [...references]
28+
}
29+
}
30+
}
31+
32+
_getReferencesForOverrideArg (overrideArgId) {
33+
const argumentsToOverride = this._searchDefinitionsByClassName(overrideArgId)
34+
return argumentsToOverride.map(arg => new Reference(arg.key))
35+
}
36+
37+
_searchDefinitionsByClassName (className) {
38+
const result = []
39+
for (const [key, definition] of this._definitions) {
40+
if (definition.Object?.name === className) {
41+
result.push({ key, definition })
42+
}
43+
}
44+
return result
45+
}
46+
47+
_removeDefinitions (keysToDelete) {
48+
for (const key of keysToDelete) {
49+
this._definitions.delete(key)
50+
}
51+
}
52+
}

lib/Definition.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ class Definition {
66
* @param {*|null} Object
77
* @param {Array} args
88
*/
9-
constructor (Object = null, args = []) {
9+
constructor (Object = null, args = [], overrideArgs = []) {
1010
this._Object = Object
1111
this._args = args
12+
this._overrideArgs = overrideArgs
1213
this._calls = []
1314
this._tags = []
1415
this._properties = new Map()
@@ -221,6 +222,20 @@ class Definition {
221222
this._parent = value
222223
}
223224

225+
/**
226+
* @param {Array} value
227+
*/
228+
set overrideArgs (value) {
229+
this._overrideArgs = value
230+
}
231+
232+
/**
233+
* @returns {Array}
234+
*/
235+
get overrideArgs () {
236+
return this._overrideArgs
237+
}
238+
224239
/**
225240
* @param {Object|Reference} Object
226241
* @param {string} method
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default class CannotAutowireOverrideSearch extends Error {
2+
constructor (className = null) {
3+
super(`Cannot Autowire Override ${className}`)
4+
this.name = 'CannotAutowireOverrideSearch'
5+
this.stack = (new Error()).stack
6+
}
7+
}

lib/InstanceManager.js

+29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ export default class InstanceManager {
2020
this._alias = alias
2121
}
2222

23+
/**
24+
* @returns {Map}
25+
*/
26+
get definitions () {
27+
return this._definitions
28+
}
29+
2330
/**
2431
* @private
2532
* @param {string} id
@@ -338,4 +345,26 @@ export default class InstanceManager {
338345

339346
service[call.method](...args)
340347
}
348+
349+
/**
350+
* @returns {Map}
351+
*/
352+
searchDefinitionsToOverrideArgs () {
353+
return new Map(
354+
[...this._definitions]
355+
.filter(([key, definition]) => definition.overrideArgs.length > 0)
356+
)
357+
}
358+
359+
/**
360+
* @param {Function} Object
361+
*
362+
* @returns {Map}
363+
*/
364+
searchNotOverrideDefinitionsByObject (Object) {
365+
return new Map(
366+
[...this._definitions]
367+
.filter(([key, definition]) => definition.Object?.name === Object.name && definition.overrideArgs.length === 0)
368+
)
369+
}
341370
}

lib/Loader/FileLoader.js

+11
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class FileLoader {
126126
definition.shared = service.shared
127127

128128
this._parseArguments(definition, service.arguments)
129+
this._parseOverrideArguments(definition, service.override_arguments)
129130
this._parseProperties(definition, service.properties)
130131
this._parseCalls(definition, service.calls)
131132
this._parseTags(definition, service.tags)
@@ -244,6 +245,16 @@ class FileLoader {
244245
definition[argument] = this._getParsedArguments(args)
245246
}
246247

248+
/**
249+
* @param {Definition} definition
250+
* @param {Array} args
251+
*
252+
* @private
253+
*/
254+
_parseOverrideArguments (definition, args = []) {
255+
definition.overrideArgs = this._getParsedArguments(args)
256+
}
257+
247258
/**
248259
* @param {string} classObject
249260
* @param {string} mainClassName
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
rootDir: ../src
5+
6+
App.NoExists:
7+
class: './FooBarAutowireOverride'
8+
override_arguments:
9+
- '@Caca'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
rootDir: ../src
5+
6+
App.FooBarAutowireOverride:
7+
class: './FooBarAutowireOverride'
8+
override_arguments:
9+
- '@CiAdapter'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default interface Adapter {
2+
toString(): string;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Adapter from "../Adapter";
2+
3+
export default class BarAdapter implements Adapter {
4+
toString(): string {
5+
return "bar";
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Adapter from "../Adapter";
2+
3+
export default class CiAdapter implements Adapter {
4+
toString(): string {
5+
return 'ci';
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Adapter from "../Adapter";
2+
3+
export default class FooAdapter implements Adapter {
4+
toString(): string {
5+
return "foo";
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Adapter from "./Adapter";
2+
3+
export default class FooBarAutowireOverride {
4+
constructor(private readonly _adapter: Adapter) {}
5+
6+
getString(): string {
7+
return this._adapter.toString();
8+
}
9+
10+
get adapter() {
11+
return this._adapter
12+
}
13+
}

test/node-dependency-injection/lib-ts/Autowire.spec.js

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it } from 'mocha'
2-
import chai from 'chai'
2+
import chai, { config } from 'chai'
33
import chaiAsPromised from 'chai-as-promised';
44
chai.use(chaiAsPromised);
55
import path from 'path'
@@ -25,6 +25,7 @@ import ImplementsOnePath from '../../Resources-ts/AutowireModulePath/src/Service
2525
import ImplementsTwoPath from '../../Resources-ts/AutowireModulePath/src/Service/ImplementsTwo'
2626
import PathExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/ExcludedService'
2727
import PathInFolderExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/InFolderExclude/InFolderExcludedService'
28+
import FooBarAutowireOverride from '../../Resources-ts/Autowire-Override/src/FooBarAutowireOverride'
2829
import ServiceFile from '../../../lib/ServiceFile';
2930
import RootDirectoryNotFound from '../../../lib/Exception/RootDirectoryNotFound';
3031

@@ -36,6 +37,50 @@ describe('AutowireTS', () => {
3637
const excludedServiceMessage = 'The service ExcludedService is not registered'
3738
const inFolderExcludedMessage = 'The service InFolderExcludedService is not registered'
3839

40+
it("should not override single class with autowiring if not exists", async () => {
41+
const configFile = path.join(
42+
__dirname,
43+
'..',
44+
'..',
45+
resourcesTsFolder,
46+
'Autowire-Override',
47+
'config',
48+
'services-not-exists.yaml'
49+
)
50+
const cb = new ContainerBuilder()
51+
const loader = new YamlFileLoader(cb)
52+
await loader.load(configFile)
53+
await cb.compile()
54+
55+
// Act.
56+
const actual = cb.get(FooBarAutowireOverride)
57+
58+
// Assert.
59+
assert.isUndefined(actual.adapter)
60+
});
61+
62+
it("should override single class with autowiring", async () => {
63+
const configFile = path.join(
64+
__dirname,
65+
'..',
66+
'..',
67+
resourcesTsFolder,
68+
'Autowire-Override',
69+
'config',
70+
'services.yaml'
71+
)
72+
const cb = new ContainerBuilder()
73+
const loader = new YamlFileLoader(cb)
74+
await loader.load(configFile)
75+
await cb.compile()
76+
77+
// Act.
78+
const actual = cb.get(FooBarAutowireOverride)
79+
80+
// Assert.
81+
assert.equal(actual.getString(), "ci")
82+
});
83+
3984
it('should get service file when was properly set', () => {
4085
// Arrange.
4186
const dir = path.join(__dirname, '..', '..', resourcesTsFolder, 'Autowire', 'src')

0 commit comments

Comments
 (0)