Skip to content

feat: ✨ Ability to override services from autowire #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/codeclimate-action@v3.2.0
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/codeclimate-action@v3.2.0
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
Loading