1111
1212namespace Symfony \Bundle \FrameworkBundle \Command ;
1313
14+ use Symfony \Component \Console \Helper \Table ;
1415use Symfony \Component \Console \Input \InputArgument ;
1516use Symfony \Component \Console \Input \InputOption ;
1617use Symfony \Component \Console \Input \InputInterface ;
1718use Symfony \Component \Console \Output \OutputInterface ;
19+ use Symfony \Component \Console \Style \SymfonyStyle ;
1820use Symfony \Component \Filesystem \Exception \IOException ;
21+ use Symfony \Component \Filesystem \Filesystem ;
1922use Symfony \Component \Finder \Finder ;
23+ use Symfony \Component \HttpKernel \Bundle \BundleInterface ;
2024
2125/**
2226 * Command that places bundle web assets into a given directory.
2327 *
2428 * @author Fabien Potencier <fabien@symfony.com>
29+ * @author Gábor Egyed <gabor.egyed@gmail.com>
2530 */
2631class AssetsInstallCommand extends ContainerAwareCommand
2732{
33+ const METHOD_COPY = 'copy ' ;
34+ const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink ' ;
35+ const METHOD_RELATIVE_SYMLINK = 'relative symlink ' ;
36+
37+ /**
38+ * @var Filesystem
39+ */
40+ private $ filesystem ;
41+
2842 /**
2943 * {@inheritdoc}
3044 */
@@ -63,8 +77,6 @@ protected function configure()
6377
6478 /**
6579 * {@inheritdoc}
66- *
67- * @throws \InvalidArgumentException When the target directory does not exist or symlink cannot be used
6880 */
6981 protected function execute (InputInterface $ input , OutputInterface $ output )
7082 {
@@ -74,77 +86,164 @@ protected function execute(InputInterface $input, OutputInterface $output)
7486 throw new \InvalidArgumentException (sprintf ('The target directory "%s" does not exist. ' , $ input ->getArgument ('target ' )));
7587 }
7688
77- $ filesystem = $ this ->getContainer ()->get ('filesystem ' );
89+ $ this -> filesystem = $ this ->getContainer ()->get ('filesystem ' );
7890
7991 // Create the bundles directory otherwise symlink will fail.
8092 $ bundlesDir = $ targetArg .'/bundles/ ' ;
81- $ filesystem ->mkdir ($ bundlesDir , 0777 );
93+ $ this -> filesystem ->mkdir ($ bundlesDir , 0777 );
8294
83- // relative implies symlink
84- $ symlink = $ input -> getOption ( ' symlink ' ) || $ input -> getOption ( ' relative ' );
95+ $ io = new SymfonyStyle ( $ input , $ output );
96+ $ io -> newLine ( );
8597
86- if ($ symlink ) {
87- $ output ->writeln ('Trying to install assets as <comment>symbolic links</comment>. ' );
98+ if ($ input ->getOption ('relative ' )) {
99+ $ expectedMethod = self ::METHOD_RELATIVE_SYMLINK ;
100+ $ io ->text ('Trying to install assets as <info>relative symbolic links</info>. ' );
101+ } elseif ($ input ->getOption ('symlink ' )) {
102+ $ expectedMethod = self ::METHOD_ABSOLUTE_SYMLINK ;
103+ $ io ->text ('Trying to install assets as <info>absolute symbolic links</info>. ' );
88104 } else {
89- $ output ->writeln ('Installing assets as <comment>hard copies</comment>. ' );
105+ $ expectedMethod = self ::METHOD_COPY ;
106+ $ io ->text ('Installing assets as <info>hard copies</info>. ' );
90107 }
91108
109+ $ io ->newLine ();
110+
111+ $ rows = array ();
112+ $ copyUsed = false ;
113+ $ exitCode = 0 ;
114+ /** @var BundleInterface $bundle */
92115 foreach ($ this ->getContainer ()->get ('kernel ' )->getBundles () as $ bundle ) {
93- if (is_dir ($ originDir = $ bundle ->getPath ().'/Resources/public ' )) {
94- $ targetDir = $ bundlesDir .preg_replace ('/bundle$/ ' , '' , strtolower ($ bundle ->getName ()));
95-
96- $ output ->writeln (sprintf ('Installing assets for <comment>%s</comment> into <comment>%s</comment> ' , $ bundle ->getNamespace (), $ targetDir ));
97-
98- $ filesystem ->remove ($ targetDir );
99-
100- if ($ symlink ) {
101- if ($ input ->getOption ('relative ' )) {
102- $ relativeOriginDir = $ filesystem ->makePathRelative ($ originDir , realpath ($ bundlesDir ));
103- } else {
104- $ relativeOriginDir = $ originDir ;
105- }
106-
107- try {
108- $ filesystem ->symlink ($ relativeOriginDir , $ targetDir );
109- if (!file_exists ($ targetDir )) {
110- throw new IOException ('Symbolic link is broken ' );
111- }
112- $ output ->writeln ('The assets were installed using symbolic links. ' );
113- } catch (IOException $ e ) {
114- if (!$ input ->getOption ('relative ' )) {
115- $ this ->hardCopy ($ originDir , $ targetDir );
116- $ output ->writeln ('It looks like your system doesn \'t support symbolic links, so the assets were installed by copying them. ' );
117- }
118-
119- // try again without the relative option
120- try {
121- $ filesystem ->symlink ($ originDir , $ targetDir );
122- if (!file_exists ($ targetDir )) {
123- throw new IOException ('Symbolic link is broken ' );
124- }
125- $ output ->writeln ('It looks like your system doesn \'t support relative symbolic links, so the assets were installed by using absolute symbolic links. ' );
126- } catch (IOException $ e ) {
127- $ this ->hardCopy ($ originDir , $ targetDir );
128- $ output ->writeln ('It looks like your system doesn \'t support symbolic links, so the assets were installed by copying them. ' );
129- }
130- }
116+ if (!is_dir ($ originDir = $ bundle ->getPath ().'/Resources/public ' )) {
117+ continue ;
118+ }
119+
120+ $ targetDir = $ bundlesDir .preg_replace ('/bundle$/ ' , '' , strtolower ($ bundle ->getName ()));
121+
122+ if (OutputInterface::VERBOSITY_VERBOSE <= $ output ->getVerbosity ()) {
123+ $ message = sprintf ("%s \n-> %s " , $ bundle ->getName (), $ targetDir );
124+ } else {
125+ $ message = $ bundle ->getName ();
126+ }
127+
128+ try {
129+ $ this ->filesystem ->remove ($ targetDir );
130+
131+ if (self ::METHOD_RELATIVE_SYMLINK === $ expectedMethod ) {
132+ $ method = $ this ->relativeSymlinkWithFallback ($ originDir , $ targetDir );
133+ } elseif (self ::METHOD_ABSOLUTE_SYMLINK === $ expectedMethod ) {
134+ $ method = $ this ->absoluteSymlinkWithFallback ($ originDir , $ targetDir );
131135 } else {
132- $ this ->hardCopy ($ originDir , $ targetDir );
136+ $ method = $ this ->hardCopy ($ originDir , $ targetDir );
133137 }
138+
139+ if (self ::METHOD_COPY === $ method ) {
140+ $ copyUsed = true ;
141+ }
142+
143+ if ($ method === $ expectedMethod ) {
144+ $ rows [] = array (sprintf ('<fg=green;options=bold>%s</> ' , '\\' === DIRECTORY_SEPARATOR ? 'OK ' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */ ), $ message , $ method );
145+ } else {
146+ $ rows [] = array (sprintf ('<fg=yellow;options=bold>%s</> ' , '\\' === DIRECTORY_SEPARATOR ? 'WARNING ' : '! ' ), $ message , $ method );
147+ }
148+ } catch (\Exception $ e ) {
149+ $ exitCode = 1 ;
150+ $ rows [] = array (sprintf ('<fg=red;options=bold>%s</> ' , '\\' === DIRECTORY_SEPARATOR ? 'ERROR ' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */ ), $ message , $ e ->getMessage ());
151+ }
152+ }
153+
154+ $ io ->table (array ('' , 'Bundle ' , 'Method / Error ' ), $ rows );
155+
156+ if (0 !== $ exitCode ) {
157+ $ io ->error ('Some errors occurred while installing assets. ' );
158+ } else {
159+ if ($ copyUsed ) {
160+ $ io ->note ('Some assets were installed via copy. If you make changes to these assets you have to run this command again. ' );
134161 }
162+ $ io ->success ('All assets were successfully installed. ' );
135163 }
164+
165+ return $ exitCode ;
136166 }
137167
138168 /**
169+ * Try to create relative symlink.
170+ *
171+ * Falling back to absolute symlink and finally hard copy.
172+ *
139173 * @param string $originDir
140174 * @param string $targetDir
175+ *
176+ * @return string
141177 */
142- private function hardCopy ($ originDir , $ targetDir )
178+ private function relativeSymlinkWithFallback ($ originDir , $ targetDir )
179+ {
180+ try {
181+ $ this ->symlink ($ originDir , $ targetDir , true );
182+ $ method = self ::METHOD_RELATIVE_SYMLINK ;
183+ } catch (IOException $ e ) {
184+ $ method = $ this ->absoluteSymlinkWithFallback ($ originDir , $ targetDir );
185+ }
186+
187+ return $ method ;
188+ }
189+
190+ /**
191+ * Try to create absolute symlink.
192+ *
193+ * Falling back to hard copy.
194+ *
195+ * @param string $originDir
196+ * @param string $targetDir
197+ *
198+ * @return string
199+ */
200+ private function absoluteSymlinkWithFallback ($ originDir , $ targetDir )
143201 {
144- $ filesystem = $ this ->getContainer ()->get ('filesystem ' );
202+ try {
203+ $ this ->symlink ($ originDir , $ targetDir );
204+ $ method = self ::METHOD_ABSOLUTE_SYMLINK ;
205+ } catch (IOException $ e ) {
206+ // fall back to copy
207+ $ method = $ this ->hardCopy ($ originDir , $ targetDir );
208+ }
209+
210+ return $ method ;
211+ }
145212
146- $ filesystem ->mkdir ($ targetDir , 0777 );
213+ /**
214+ * Creates symbolic link.
215+ *
216+ * @param string $originDir
217+ * @param string $targetDir
218+ * @param bool $relative
219+ *
220+ * @throws IOException If link can not be created.
221+ */
222+ private function symlink ($ originDir , $ targetDir , $ relative = false )
223+ {
224+ if ($ relative ) {
225+ $ originDir = $ this ->filesystem ->makePathRelative ($ originDir , realpath (dirname ($ targetDir )));
226+ }
227+ $ this ->filesystem ->symlink ($ originDir , $ targetDir );
228+ if (!file_exists ($ targetDir )) {
229+ throw new IOException (sprintf ('Symbolic link "%s" is created but appears to be broken. ' , $ targetDir ), 0 , null , $ targetDir );
230+ }
231+ }
232+
233+ /**
234+ * Copies origin to target.
235+ *
236+ * @param string $originDir
237+ * @param string $targetDir
238+ *
239+ * @return string
240+ */
241+ private function hardCopy ($ originDir , $ targetDir )
242+ {
243+ $ this ->filesystem ->mkdir ($ targetDir , 0777 );
147244 // We use a custom iterator to ignore VCS files
148- $ filesystem ->mirror ($ originDir , $ targetDir , Finder::create ()->ignoreDotFiles (false )->in ($ originDir ));
245+ $ this ->filesystem ->mirror ($ originDir , $ targetDir , Finder::create ()->ignoreDotFiles (false )->in ($ originDir ));
246+
247+ return self ::METHOD_COPY ;
149248 }
150249}
0 commit comments