Skip to content

Commit

Permalink
feat: ✨ Ability to override services from autowire (#216)
Browse files Browse the repository at this point in the history
feat: ✨ Ability to override services from autowire #213
  • Loading branch information
zazoomauro authored Sep 27, 2024
1 parent 197446d commit 4504a5e
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 18 deletions.
30 changes: 15 additions & 15 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ name: Build

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 15.x, 16.x, 17.x]
node-version: [14.x, 15.x, 16.x, 17.x, 18.x, 19.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -24,18 +24,18 @@ jobs:
- run: npm test

coverage:
needs: [ build ]
needs: [build]
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: '17'
- run: npm ci
- uses: paambaati/[email protected]
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
with:
coverageCommand: npm run test:coverage
coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: "17"
- run: npm ci
- uses: paambaati/[email protected]
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
with:
coverageCommand: npm run test:coverage
coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ node_modules/
coverage/
coverage.lcov
npm-debug.log
.DS_STORE
.DS_STORE
.vscode
6 changes: 6 additions & 0 deletions lib/Autowire.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Reference from './Reference'
import ServiceFile from './ServiceFile'
import AutowireIdentifier from './AutowireIdentifier'
import ContainerDefaultDirMustBeSet from './Exception/ContainerDefaultDirMustBeSet'
import PassConfig from './PassConfig'
import AutowireOverridePass from './CompilerPass/AutowireOverridePass'

export default class Autowire {
/**
Expand Down Expand Up @@ -121,6 +123,10 @@ export default class Autowire {
if (this._serviceFile instanceof ServiceFile) {
await this._serviceFile.generateFromContainer(this._container)
}
this._container.addCompilerPass(
new AutowireOverridePass(),
PassConfig.TYPE_BEFORE_OPTIMIZATION
)
}

/**
Expand Down
52 changes: 52 additions & 0 deletions lib/CompilerPass/AutowireOverridePass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Reference from '../Reference'

export default class AutowireOverridePass {
/**
* @param {ContainerBuilder} container
*/
process (container) {
this._definitions = container.instanceManager.definitions
const overrideDefinitions = container.instanceManager.searchDefinitionsToOverrideArgs()
const toDelete = []

for (const [key, definitionToOverride] of overrideDefinitions) {
toDelete.push(key)
this._processOverride(definitionToOverride, container)
}

this._removeDefinitions(toDelete)
}

_processOverride (definitionToOverride, container) {
const definitionsToOverride = container.instanceManager.searchNotOverrideDefinitionsByObject(definitionToOverride.Object)

for (const overrideArg of definitionToOverride.overrideArgs) {
const references = this._getReferencesForOverrideArg(overrideArg.id)

for (const [, definitionFromOverride] of definitionsToOverride) {
definitionFromOverride.args = [...references]
}
}
}

_getReferencesForOverrideArg (overrideArgId) {
const argumentsToOverride = this._searchDefinitionsByClassName(overrideArgId)
return argumentsToOverride.map(arg => new Reference(arg.key))
}

_searchDefinitionsByClassName (className) {
const result = []
for (const [key, definition] of this._definitions) {
if (definition.Object?.name === className) {
result.push({ key, definition })
}
}
return result
}

_removeDefinitions (keysToDelete) {
for (const key of keysToDelete) {
this._definitions.delete(key)
}
}
}
17 changes: 16 additions & 1 deletion lib/Definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ class Definition {
* @param {*|null} Object
* @param {Array} args
*/
constructor (Object = null, args = []) {
constructor (Object = null, args = [], overrideArgs = []) {
this._Object = Object
this._args = args
this._overrideArgs = overrideArgs
this._calls = []
this._tags = []
this._properties = new Map()
Expand Down Expand Up @@ -221,6 +222,20 @@ class Definition {
this._parent = value
}

/**
* @param {Array} value
*/
set overrideArgs (value) {
this._overrideArgs = value
}

/**
* @returns {Array}
*/
get overrideArgs () {
return this._overrideArgs
}

/**
* @param {Object|Reference} Object
* @param {string} method
Expand Down
7 changes: 7 additions & 0 deletions lib/Exception/CannotAutowireOverrideSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default class CannotAutowireOverrideSearch extends Error {
constructor (className = null) {
super(`Cannot Autowire Override ${className}`)
this.name = 'CannotAutowireOverrideSearch'
this.stack = (new Error()).stack
}
}
29 changes: 29 additions & 0 deletions lib/InstanceManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export default class InstanceManager {
this._alias = alias
}

/**
* @returns {Map}
*/
get definitions () {
return this._definitions
}

/**
* @private
* @param {string} id
Expand Down Expand Up @@ -338,4 +345,26 @@ export default class InstanceManager {

service[call.method](...args)
}

/**
* @returns {Map}
*/
searchDefinitionsToOverrideArgs () {
return new Map(
[...this._definitions]
.filter(([key, definition]) => definition.overrideArgs.length > 0)
)
}

/**
* @param {Function} Object
*
* @returns {Map}
*/
searchNotOverrideDefinitionsByObject (Object) {
return new Map(
[...this._definitions]
.filter(([key, definition]) => definition.Object?.name === Object.name && definition.overrideArgs.length === 0)
)
}
}
11 changes: 11 additions & 0 deletions lib/Loader/FileLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class FileLoader {
definition.shared = service.shared

this._parseArguments(definition, service.arguments)
this._parseOverrideArguments(definition, service.override_arguments)
this._parseProperties(definition, service.properties)
this._parseCalls(definition, service.calls)
this._parseTags(definition, service.tags)
Expand Down Expand Up @@ -244,6 +245,16 @@ class FileLoader {
definition[argument] = this._getParsedArguments(args)
}

/**
* @param {Definition} definition
* @param {Array} args
*
* @private
*/
_parseOverrideArguments (definition, args = []) {
definition.overrideArgs = this._getParsedArguments(args)
}

/**
* @param {string} classObject
* @param {string} mainClassName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
rootDir: ../src

App.NoExists:
class: './FooBarAutowireOverride'
override_arguments:
- '@Caca'
9 changes: 9 additions & 0 deletions test/Resources-ts/Autowire-Override/config/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
rootDir: ../src

App.FooBarAutowireOverride:
class: './FooBarAutowireOverride'
override_arguments:
- '@CiAdapter'
3 changes: 3 additions & 0 deletions test/Resources-ts/Autowire-Override/src/Adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default interface Adapter {
toString(): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Adapter from "../Adapter";

export default class BarAdapter implements Adapter {
toString(): string {
return "bar";
}
}
7 changes: 7 additions & 0 deletions test/Resources-ts/Autowire-Override/src/Adapters/CiAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Adapter from "../Adapter";

export default class CiAdapter implements Adapter {
toString(): string {
return 'ci';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Adapter from "../Adapter";

export default class FooAdapter implements Adapter {
toString(): string {
return "foo";
}
}
13 changes: 13 additions & 0 deletions test/Resources-ts/Autowire-Override/src/FooBarAutowireOverride.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Adapter from "./Adapter";

export default class FooBarAutowireOverride {
constructor(private readonly _adapter: Adapter) {}

getString(): string {
return this._adapter.toString();
}

get adapter() {
return this._adapter
}
}
47 changes: 46 additions & 1 deletion test/node-dependency-injection/lib-ts/Autowire.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it } from 'mocha'
import chai from 'chai'
import chai, { config } from 'chai'
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
import path from 'path'
Expand All @@ -25,6 +25,7 @@ import ImplementsOnePath from '../../Resources-ts/AutowireModulePath/src/Service
import ImplementsTwoPath from '../../Resources-ts/AutowireModulePath/src/Service/ImplementsTwo'
import PathExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/ExcludedService'
import PathInFolderExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/InFolderExclude/InFolderExcludedService'
import FooBarAutowireOverride from '../../Resources-ts/Autowire-Override/src/FooBarAutowireOverride'
import ServiceFile from '../../../lib/ServiceFile';
import RootDirectoryNotFound from '../../../lib/Exception/RootDirectoryNotFound';

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

it("should not override single class with autowiring if not exists", async () => {
const configFile = path.join(
__dirname,
'..',
'..',
resourcesTsFolder,
'Autowire-Override',
'config',
'services-not-exists.yaml'
)
const cb = new ContainerBuilder()
const loader = new YamlFileLoader(cb)
await loader.load(configFile)
await cb.compile()

// Act.
const actual = cb.get(FooBarAutowireOverride)

// Assert.
assert.isUndefined(actual.adapter)
});

it("should override single class with autowiring", async () => {
const configFile = path.join(
__dirname,
'..',
'..',
resourcesTsFolder,
'Autowire-Override',
'config',
'services.yaml'
)
const cb = new ContainerBuilder()
const loader = new YamlFileLoader(cb)
await loader.load(configFile)
await cb.compile()

// Act.
const actual = cb.get(FooBarAutowireOverride)

// Assert.
assert.equal(actual.getString(), "ci")
});

it('should get service file when was properly set', () => {
// Arrange.
const dir = path.join(__dirname, '..', '..', resourcesTsFolder, 'Autowire', 'src')
Expand Down

0 comments on commit 4504a5e

Please sign in to comment.