zazoomauro/node-dependency-injection

View on GitHub
lib/ContainerBuilder.js

Summary

Maintainability
B
4 hrs
Test Coverage
A
98%
import path from 'path'
import fs from 'fs'
import Definition from './Definition'
import PassConfig from './PassConfig'
import Compiler from './Compiler'
import CompilerPass from './CompilerPass'
import InstanceManager from './InstanceManager'
import DefinitionNotFoundException from './Exception/DefinitionNotFoundException'
import LoadMethodNotFoundException from './Exception/LoadMethodNotFoundException'
import LoggerWarnMethodNotFoundException from './Exception/LoggerWarnMethodNotFoundException'
import WrongDefinitionException from './Exception/WrongDefinitionException'
import FrozenContainerException from './Exception/FrozenContainerException'
import RootDirectoryNotFound from './Exception/RootDirectoryNotFound'
import RootDirectoryMustBeAbsolute from './Exception/RootDirectoryMustBeAbsolute'
import ServiceFile from './ServiceFile'

class ContainerBuilder {
  /**
   * @param {boolean} containerReferenceAsService
   * @param {String} defaultDir
   */
  constructor (containerReferenceAsService = false, defaultDir = null) {
    this.ensureIsAbsolute(defaultDir)
    this.ensureDirectoryExists(defaultDir)
    this._definitions = new Map()
    this._parameters = new Map()
    this._alias = new Map()
    this._container = new Map()
    this._frozen = false
    this._compilerPass = new CompilerPass(this)
    this._extensions = []
    this._logger = console
    this._instanceManager = undefined
    this._containerReferenceAsService = containerReferenceAsService
    this._defaultDir = defaultDir
    this._autowire = null
  }

  /**
   * @param {Autowire} autowire
   */
  set autowire (autowire) {
    this._autowire = autowire
  }

  get autowire () {
    return this._autowire
  }

  ensureDirectoryExists (directory) {
    if (directory && fs.existsSync(directory) === false) {
      throw new RootDirectoryNotFound()
    }
  }

  ensureIsAbsolute (directory) {
    if (directory && path.isAbsolute(directory) === false) {
      throw new RootDirectoryMustBeAbsolute()
    }
  }

  /**
   * @returns {String}
   */
  get defaultDir () {
    return this._defaultDir
  }

  set defaultDir (value) {
    this.ensureIsAbsolute(value)
    this.ensureDirectoryExists(value)
    this._defaultDir = value
  }

  /**
   * @returns {boolean}
   */
  get containerReferenceAsService () {
    return this._containerReferenceAsService
  }

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

  /**
   * @returns {boolean}
   */
  get frozen () {
    return this._frozen
  }

  /**
   * @param {boolean} value
   */
  set frozen (value) {
    this._frozen = value
  }

  /**
   * @return {InstanceManager}
   */
  get instanceManager () {
    if (!this._instanceManager) {
      this._instanceManager = new InstanceManager(
        this,
        this._definitions,
        this._alias
      )
    }
    return this._instanceManager
  }

  /**
   * @returns {Array}
   */
  get extensions () {
    return this._extensions
  }

  /**
   * @returns {Console|*}
   */
  get logger () {
    return this._logger
  }

  /**
   * @param {Console|*} value
   */
  set logger (value) {
    if (typeof value.warn !== 'function') {
      throw new LoggerWarnMethodNotFoundException()
    }
    this._logger = value
  }

  /**
   * @return {Map}
   */
  get services () {
    return this._container
  }

  /**
   * @param {string} id
   * @param {*|null} object
   * @param {Array} args
   * @returns {Definition}
   */
  register (id, object = null, args = []) {
    if (!this.frozen) {
      const definition = new Definition()
      definition.Object = object
      definition.args = args

      if (!object) {
        definition.synthetic = true
      }

      return this.setDefinition(id, definition)
    }

    throw new FrozenContainerException()
  }

  /**
   * @param {string|any} id
   */
  get (id) {
    return this.instanceManager.getInstance(id)
  }

  async compile () {
    await new Compiler(this).run()
    if (this._autowire?.serviceFile instanceof ServiceFile) {
      await this._autowire.serviceFile.generateFromContainer(this)
    }
  }

  /**
   * @param {*} compilerPass
   * @param {string} type
   * @param {number} priority
   */
  addCompilerPass (
    compilerPass,
    type = PassConfig.TYPE_BEFORE_OPTIMIZATION,
    priority = 0
  ) {
    this._compilerPass.register(compilerPass, type, priority)
  }

  /**
   * @param {string} alias
   * @param {string} id
   */
  setAlias (alias, id) {
    this._alias.set(alias, id)
  }

  /**
   * @param {string} alias
   * @returns {boolean}
   */
  hasAlias (alias) {
    return this._alias.has(alias)
  }

  /**
   * @param {string} id
   * @param {Definition} definition
   * @returns {Definition}
   */
  setDefinition (id, definition) {
    if (definition instanceof Definition) {
      this._definitions.set(id, definition)

      return definition
    }

    throw new WrongDefinitionException()
  }

  /**
   * @param {string} name
   * @returns {Iterable}
   */
  * findTaggedServiceIds (name) {
    for (const [id, definition] of this._definitions) {
      if (definition.tags.some((tag) => { return tag.name === name })) {
        yield { id, definition }
      }
    }
  }

  /**
   * @param {string} key
   * @param {string||Array|boolean|Object} value
   */
  setParameter (key, value) {
    if (typeof value !== 'string' && !Array.isArray(value) &&
      typeof value !== 'boolean' && typeof value !== 'object') {
      throw new TypeError(
        'The expected value is not a flat string, an array, a boolean or an object')
    }

    this._parameters.set(key, value)
  }

  /**
   * @param {string} key
   * @returns {string|Array}
   */
  getParameter (key) {
    return this._parameters.get(key)
  }

  /**
   * @param {string} key
   * @returns {boolean}
   */
  hasParameter (key) {
    return this._parameters.has(key)
  }

  /**
   * @param {string} key
   * @returns {boolean}
   */
  hasDefinition (key) {
    return this._definitions.has(key)
  }

  /**
   * @param {string} key
   * @returns {boolean}
   */
  has (key) {
    return this._definitions.has(key) ||
      this._parameters.has(key) ||
      this._alias.has(key)
  }

  /**
   * @param {string} method
   * @param {string} key
   * @returns {Definition|boolean}
   * @private
   */
  _definition (method, key) {
    if (this._definitions.has(key)) {
      return this._definitions[method](key)
    }

    throw new DefinitionNotFoundException(key)
  }

  /**
   * @param {string} key
   * @returns {Definition}
   */
  getDefinition (key) {
    return this._definition('get', key)
  }

  /**
   * @param {string} key
   * @returns {boolean}
   */
  removeDefinition (key) {
    return this._definition('delete', key)
  }

  /**
   * @param {string} key
   * @returns {Definition}
   */
  async findDefinition (key) {
    key = this._alias.get(key) || key

    if (this._definitions.has(key)) {
      return this._definitions.get(key)
    }

    throw new DefinitionNotFoundException(key)
  }

  /**
   * @param {*} extension
   */
  registerExtension (extension) {
    if (typeof extension.load !== 'function') {
      throw new LoadMethodNotFoundException(extension.constructor.name)
    }

    this._extensions.push(extension)
  }

  /**
   * @param {string} id
   * @param {*} instance
   */
  set (id, instance) {
    this._container.set(id, instance)
  }

  /**
   * @param {string} id
   */
  remove (id) {
    this._container.delete(id)
  }

  /**
   * @param {string} id
   * @returns {boolean}
   */
  isSet (id) {
    return this._container.has(id)
  }
}

export default ContainerBuilder