3 namespace Drupal\drupalmoduleupgrader;
5 use Doctrine\Common\Collections\ArrayCollection;
6 use Drupal\Component\Utility\SafeMarkup;
9 use Pharborist\RootNode;
10 use Symfony\Component\DependencyInjection\ContainerInterface;
11 use Symfony\Component\Finder\Finder;
14 * Default implementation of TargetInterface.
16 class Target implements TargetInterface {
19 * The target module's machine name.
26 * @var \Drupal\Component\Plugin\PluginManagerInterface
28 protected $indexerManager;
31 * The target module's base path.
38 * @var IndexerInterface[]
40 protected $indexers = [];
43 * @var \Doctrine\Common\Collections\ArrayCollection
50 * @var \Pharborist\RootNode[]
52 protected $documents = [];
55 * Constructs a Target.
58 * The base path of the target module.
59 * @param ContainerInterface $container
60 * The current container, to pull any dependencies out of.
62 public function __construct($path, ContainerInterface $container) {
63 $this->indexerManager = $container->get('plugin.manager.drupalmoduleupgrader.indexer');
66 $this->basePath = $path;
69 throw new \RuntimeException(SafeMarkup::format('Invalid base path: @path', ['@path' => $path]));
76 public function id() {
77 if (empty($this->id)) {
78 $dir = $this->getBasePath();
79 $info = (new Finder)->in($dir)->depth('== 0')->name('*.info')->getIterator();
82 if ($info_file = $info->current()) {
83 $this->id = subStr($info_file->getFilename(), 0, -5);
86 throw new \RuntimeException(SafeMarkup::format('Could not find info file in @dir', ['@dir' => $dir]));
95 public function getBasePath() {
96 return $this->basePath;
102 public function getPath($file) {
103 if ($file{0} == '.') {
104 $file = $this->id() . $file;
106 return $this->getBasePath() . '/' . ltrim($file, '/');
112 public function getFinder() {
113 // We do NOT want to include submodules. We can detect one by the presence
114 // of an info file -- if there is one, its directory is a submodule.
115 $directories = (new Finder)
117 ->in($this->getBasePath())
118 ->filter(function(\SplFileInfo $dir) {
119 return (new Finder)->files()->in($dir->getPathname())->depth('== 0')->name('*.info')->count() === 0;
122 $directories = array_keys(iterator_to_array($directories));
123 $directories[] = $this->getBasePath();
128 // We don't need to recurse, because we've already determined which
129 // directories to search.
141 public function getIndexer($which) {
142 if (empty($this->indexers[$which])) {
143 /** @var IndexerInterface $indexer */
144 $indexer = $this->indexerManager->createInstance($which);
145 $indexer->bind($this);
146 $this->indexers[$which] = $indexer;
148 return $this->indexers[$which];
154 public function getServices() {
155 if (empty($this->services)) {
156 $this->services = new ArrayCollection();
158 return $this->services;
162 * Runs all available indexers on this target.
164 public function buildIndex() {
165 $indexers = array_keys($this->indexerManager->getDefinitions());
166 foreach ($indexers as $id) {
167 $this->getIndexer($id)->build();
169 // Release syntax trees that were opened during indexing.
174 * Destroys all index data for this target.
176 public function destroyIndex() {
177 $indexers = array_keys($this->indexerManager->getDefinitions());
178 foreach ($indexers as $id) {
179 $this->getIndexer($id)->destroy();
186 public function implementsHook($hook) {
187 return $this->getIndexer('function')->has('hook_' . $hook);
193 public function executeHook($hook, array $arguments = []) {
194 if ($this->implementsHook($hook)) {
195 return $this->getIndexer('function')->execute('hook_' . $hook, $arguments);
199 '@module' => $this->id(),
202 throw new \InvalidArgumentException(SafeMarkup::format('@module does not implement hook_@hook.', $variables));
209 public function open($file) {
210 if (empty($this->documents[$file])) {
211 $this->documents[$file] = Parser::parseFile($file);
213 return $this->documents[$file];
219 public function save(Node $node = NULL) {
221 $file = $this->getFileOf($node);
223 $doc = $node instanceof RootNode ? $node : $node->parents()->get(0);
225 $victory = file_put_contents($file, $doc->getText());
226 if ($victory === FALSE) {
227 throw new IOException(SafeMarkup::format('Failed to save @file.', [ '@file' => $file ]));
231 throw new IOException('Cannot save a node that is not attached to an open document.');
235 array_walk($this->documents, [ $this, 'save' ]);
242 public function create($file, $ns = NULL) {
243 $this->documents[$file] = RootNode::create($ns);
244 return $this->documents[$file];
250 public function flush() {
251 $this->documents = [];
255 * Determines which currently-open file a node belongs to, if any. Nodes
256 * which are not part of any open syntax tree will return NULL.
258 * @return string|NULL
260 public function getFileOf(Node $node) {
261 if ($node instanceof RootNode) {
265 $parents = $node->parents();
266 if ($parents->isEmpty()) {
269 $root = $parents->get(0);
272 foreach ($this->documents as $file => $doc) {
273 if ($root === $doc) {