diff --git a/doc/user/project/repository/code_suggestions/repository_xray.md b/doc/user/project/repository/code_suggestions/repository_xray.md
index 596bb7faef90d42606378da26d2db8b67360b092..e593fe12e15ce8a170f11d8e2eb9f690a0d0f90b 100644
--- a/doc/user/project/repository/code_suggestions/repository_xray.md
+++ b/doc/user/project/repository/code_suggestions/repository_xray.md
@@ -47,7 +47,7 @@ The Repository X-Ray searches a maximum of two directory levels from the reposit
 | Java       | Gradle          | `build.gradle`                   | 17.4 or later  |
 | Java       | Maven           | `pom.xml`                        | 17.4 or later  |
 | Kotlin     | Gradle          | `build.gradle.kts`               | 17.5 or later  |
-| PHP        | Composer        | `composer.lock`                  | 17.5 or later  |
+| PHP        | Composer        | `composer.lock`, `composer.json` | 17.5 or later  |
 | Python     | Conda           | `environment.yml`                | 17.5 or later  |
 | Python     | Pip             | `requirements.txt`               | 17.5 or later  |
 | Python     | Poetry          | `poetry.lock`, `pyproject.toml`  | 17.5 or later  |
diff --git a/ee/lib/ai/context/dependencies/config_files/constants.rb b/ee/lib/ai/context/dependencies/config_files/constants.rb
index 506c97cb6e65af33af3001b40a01feee070b1bb8..5b1b8e73561efd2e1a717db019dc986bb07cc72a 100644
--- a/ee/lib/ai/context/dependencies/config_files/constants.rb
+++ b/ee/lib/ai/context/dependencies/config_files/constants.rb
@@ -28,10 +28,11 @@ module Constants
             ConfigFiles::JavaMaven,
             ConfigFiles::KotlinGradle,
             ConfigFiles::PhpComposerLock,
+            ConfigFiles::PhpComposer,
             ConfigFiles::PythonConda,
             ConfigFiles::PythonPip,
-            ConfigFiles::PythonPoetry,
             ConfigFiles::PythonPoetryLock,
+            ConfigFiles::PythonPoetry,
             ConfigFiles::RubyGemsLock
           ].freeze
         end
diff --git a/ee/lib/ai/context/dependencies/config_files/php_composer.rb b/ee/lib/ai/context/dependencies/config_files/php_composer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..de86341bf8b8048e86e75f2b629da61916bea314
--- /dev/null
+++ b/ee/lib/ai/context/dependencies/config_files/php_composer.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Ai
+  module Context
+    module Dependencies
+      module ConfigFiles
+        class PhpComposer < Base
+          def self.file_name_glob
+            'composer.json'
+          end
+
+          def self.lang_name
+            'PHP'
+          end
+
+          private
+
+          ### Example format:
+          #
+          # {
+          #   { ... },
+          #   "require": {
+          #     "php": "^7.2.5 || ^8.0",
+          #     "composer/ca-bundle": "^1.5"
+          #   },
+          #   "require-dev": {
+          #     "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1",
+          #     "phpstan/phpstan": "^1.11.8"
+          #   },
+          #   { ... }
+          # }
+          #
+          def extract_libs
+            parsed = ::Gitlab::Json.parse(content)
+
+            %w[require require-dev].flat_map do |key|
+              dig_in(parsed, key).try(:map) do |name, version|
+                Lib.new(name: name, version: version)
+              end
+            end.compact
+          rescue JSON::ParserError
+            raise ParsingError, 'content is not valid JSON'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/ai/context/dependencies/config_files/php_composer_spec.rb b/ee/spec/lib/ai/context/dependencies/config_files/php_composer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..29714da17ab625651557561f07dda660754de2e8
--- /dev/null
+++ b/ee/spec/lib/ai/context/dependencies/config_files/php_composer_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ai::Context::Dependencies::ConfigFiles::PhpComposer, feature_category: :code_suggestions do
+  it 'returns the expected language value' do
+    expect(described_class.lang).to eq('php')
+  end
+
+  it_behaves_like 'parsing a valid dependency config file' do
+    let(:config_file_content) do
+      <<~JSON
+        {
+          "name": "composer/composer",
+          "type": "library",
+          "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
+          "keywords": [
+            "package",
+            "dependency",
+            "autoload"
+          ],
+          "require": {
+            "ext-pcre": "*",
+            "php": "^7.2 || ^8.0"
+          }
+        }
+      JSON
+    end
+
+    let(:expected_formatted_lib_names) do
+      ['ext-pcre (*)', 'php (^7.2 || ^8.0)']
+    end
+  end
+
+  context 'when the content contains dev dependencies' do
+    it_behaves_like 'parsing a valid dependency config file' do
+      let(:config_file_content) do
+        <<~JSON
+          {
+            "name": "composer/composer",
+            "type": "library",
+            "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.",
+            "keywords": [
+              "package",
+              "dependency",
+              "autoload"
+            ],
+            "require": {
+              "ext-pcre": "*",
+              "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+              "phpstan/phpstan": "^1.10",
+              "psr/log": "^1.0 || ^2.0 || ^3.0"
+            }
+          }
+        JSON
+      end
+
+      let(:expected_formatted_lib_names) do
+        ['ext-pcre (*)', 'php (^7.2 || ^8.0)', 'phpstan/phpstan (^1.10)', 'psr/log (^1.0 || ^2.0 || ^3.0)']
+      end
+    end
+  end
+
+  context 'when config file content is an array' do
+    it_behaves_like 'parsing an invalid dependency config file' do
+      let(:invalid_config_file_content) { '[]' }
+      let(:expected_parsing_error_message) { 'encountered invalid node' }
+    end
+  end
+
+  it_behaves_like 'parsing an invalid dependency config file' do
+    let(:expected_parsing_error_message) { 'content is not valid JSON' }
+  end
+
+  describe '.matches?' do
+    using RSpec::Parameterized::TableSyntax
+
+    where(:path, :matches) do
+      'composer.json'             | true
+      'dir/composer.json'         | true
+      'dir/subdir/composer.json'  | true
+      'dir/composer.js'           | false
+      'Composer.json'             | false
+      'composer_json'             | false
+      'composer.lock'             | false
+    end
+
+    with_them do
+      it 'matches the file name glob pattern at various directory levels' do
+        expect(described_class.matches?(path)).to eq(matches)
+      end
+    end
+  end
+end