public static ArrayList<String> getExtendedTypeClasses(Project project, String... formTypeNames) {

    List<String> formTypeNamesList = Arrays.asList(formTypeNames);

    ArrayList<String> extendedTypeClasses = new ArrayList<String>();

    FormExtensionServiceParser formExtensionServiceParser =
        ServiceXmlParserFactory.getInstance(project, FormExtensionServiceParser.class);
    for (String formClass : formExtensionServiceParser.getFormExtensions().keySet()) {

      PsiElement psiElements[] =
          PhpElementsUtil.getPsiElementsBySignature(
              project, "#M#C\\" + formClass + ".getExtendedType");
      for (PsiElement psiElement : psiElements) {
        PhpReturn phpReturn = PsiTreeUtil.findChildOfType(psiElement, PhpReturn.class);
        if (phpReturn != null) {
          PhpPsiElement returnValue = phpReturn.getFirstPsiChild();
          if (returnValue instanceof StringLiteralExpression
              && formTypeNamesList.contains(
                  ((StringLiteralExpression) returnValue).getContents())) {
            extendedTypeClasses.add(formClass);
          }
        }
      }
    }

    return extendedTypeClasses;
  }
  /**
   * Gets parameter which are non optional and at the end of a function signature
   *
   * <p>foo($container, $bar = null, $foo = null);
   */
  @NotNull
  public static Parameter[] getFunctionRequiredParameter(@NotNull Function function) {

    // nothing we need to do
    Parameter[] parameters = function.getParameters();
    if (parameters.length == 0) {
      return new Parameter[0];
    }

    // find last optional parameter
    int last = -1;
    for (int i = parameters.length - 1; i >= 0; i--) {
      if (!parameters[i].isOptional()) {
        last = i;
        break;
      }
    }

    // no required argument found
    if (last == -1) {
      return new Parameter[0];
    }

    return Arrays.copyOfRange(parameters, 0, last + 1);
  }
  private void collectSelects(
      QueryBuilderScopeContext qb, MethodReference methodReference, String name) {

    if (!Arrays.asList("select", "addSelect").contains(name)) {
      return;
    }

    // $qb->select('foo')
    PsiElement psiElement = PsiElementUtils.getMethodParameterPsiElementAt(methodReference, 0);
    String literalValue = PhpElementsUtil.getStringValue(psiElement);
    if (literalValue != null) {
      qb.addSelect(literalValue);
      return;
    }

    // $qb->select(array('foo', 'bar', 'accessoryDetail'))
    if (psiElement instanceof ArrayCreationExpression) {
      for (PsiElement arrayValue :
          PsiElementUtils.getChildrenOfTypeAsList(
              psiElement, PlatformPatterns.psiElement(PhpElementTypes.ARRAY_VALUE))) {
        if (arrayValue.getChildren().length == 1) {
          String arrayValueString = PhpElementsUtil.getStringValue(arrayValue.getChildren()[0]);
          if (arrayValueString != null) {
            qb.addSelect(arrayValueString);
          }
        }
      }
    }
  }
  public static boolean isFunctionReference(
      PsiElement psiElement, int wantIndex, String... funcName) {

    PsiElement variableContext = psiElement.getContext();
    if (!(variableContext instanceof ParameterList)) {
      return false;
    }

    ParameterList parameterList = (ParameterList) variableContext;
    PsiElement context = parameterList.getContext();
    if (!(context instanceof FunctionReference)) {
      return false;
    }

    FunctionReference methodReference = (FunctionReference) context;
    String name = methodReference.getName();

    if (name == null || !Arrays.asList(funcName).contains(name)) {
      return false;
    }

    ParameterBag currentIndex = getCurrentParameterIndex(psiElement);
    if (currentIndex == null) {
      return false;
    }

    return !(wantIndex >= 0 && currentIndex.getIndex() != wantIndex);
  }
  private void collectJoins(
      QueryBuilderScopeContext qb, MethodReference methodReference, String name) {

    if (!collectJoins
        || !Arrays.asList("join", "leftJoin", "rightJoin", "innerJoin").contains(name)) {
      return;
    }

    String join = PsiElementUtils.getMethodParameterAt(methodReference, 0);
    String alias = PsiElementUtils.getMethodParameterAt(methodReference, 1);
    if (join != null && alias != null) {
      qb.addJoin(alias, new QueryBuilderJoin(join, alias));
    }
  }
  private void collectParameter(
      QueryBuilderScopeContext qb, MethodReference methodReference, String name) {

    if (!collectParameter || !Arrays.asList("where", "andWhere").contains(name)) {
      return;
    }

    String value = PsiElementUtils.getMethodParameterAt(methodReference, 0);
    if (value != null) {
      Matcher matcher = Pattern.compile(":(\\w+)", Pattern.MULTILINE).matcher(value);
      while (matcher.find()) {
        qb.addParameter(matcher.group(1));
      }
    }
  }
  public static boolean isMethodWithFirstString(PsiElement psiElement, String... methodName) {

    // filter out method calls without parameter
    // $this->methodName('service_name')
    // withName is not working, so simulate it in a hack
    if (!PlatformPatterns.psiElement(PhpElementTypes.METHOD_REFERENCE)
        .withChild(
            PlatformPatterns.psiElement(PhpElementTypes.PARAMETER_LIST)
                .withFirstChild(PlatformPatterns.psiElement(PhpElementTypes.STRING)))
        .accepts(psiElement)) {

      return false;
    }

    // cant we move it up to PlatformPatterns? withName condition dont looks working
    String methodRefName = ((MethodReference) psiElement).getName();

    return null != methodRefName && Arrays.asList(methodName).contains(methodRefName);
  }
  /**
   * $this->methodName('service_name') $this->methodName(SERVICE::NAME)
   * $this->methodName($this->name)
   */
  public static boolean isMethodWithFirstStringOrFieldReference(
      PsiElement psiElement, String... methodName) {

    if (!PlatformPatterns.psiElement(PhpElementTypes.METHOD_REFERENCE)
        .withChild(
            PlatformPatterns.psiElement(PhpElementTypes.PARAMETER_LIST)
                .withFirstChild(
                    PlatformPatterns.or(
                        PlatformPatterns.psiElement(PhpElementTypes.STRING),
                        PlatformPatterns.psiElement(PhpElementTypes.FIELD_REFERENCE),
                        PlatformPatterns.psiElement(PhpElementTypes.CLASS_CONSTANT_REFERENCE))))
        .accepts(psiElement)) {

      return false;
    }

    // cant we move it up to PlatformPatterns? withName condition dont looks working
    String methodRefName = ((MethodReference) psiElement).getName();

    return null != methodRefName && Arrays.asList(methodName).contains(methodRefName);
  }