Skip to content

Instantly share code, notes, and snippets.

@solocommand
Last active January 3, 2016 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save solocommand/8402724 to your computer and use it in GitHub Desktop.
Save solocommand/8402724 to your computer and use it in GitHub Desktop.
Composer script to auto-enable installed bundles in Symfony2 AppKernel
<?php
namespace Cygnus\CoreBundle\Composer;
use Composer\Script\Event;
use Sensio\Bundle\GeneratorBundle\Manipulator\KernelManipulator;
class ScriptHandler
{
private static $coreBundleName = 'cygnus/base-core-bundle';
private static $packagesToEnable = array();
private static $lockedRepository;
public static function postActionCmd(Event $event)
{
// Get the composer instance from the recieved event
$composer = $event->getComposer();
// Set the locked Repository (installed packages) for later use.
self::$lockedRepository = $composer->getLocker()->getLockedRepository();
// Get the core package
$corePackage = self::getPackage(self::$coreBundleName);
// Recursively get packages that should be enabled in AppKernel.
self::registerPackages($corePackage);
// Initiate translation of package names and insertion into AppKernel.
self::updateAppKernel();
// Load routes from installed bundles, if applicable.
self::updateBundleRoutes();
}
private static function getBundlePath($package, $packageName)
{
$baseDir = substr(__DIR__, 0, strpos(__DIR__, 'vendor/') + strlen('vendor/'));
$namespace = self::getNamespace($package);
$srcPath = $package->getAutoload()['psr-0'];
$targetDir = (is_null($package->getTargetDir())) ? $namespace : $package->getTargetDir();
$path = ($srcPath == 'src/') ? $srcPath.$targetDir : $targetDir;
// Stupid hack for symfony components
if (0 === strpos($packageName, 'symfony')) {
return $baseDir."symfony/symfony/src/Symfony/Bundle/".ucfirst(str_replace('symfony/', '', str_replace('-bundle', 'Bundle', $packageName)));
}
return $fullPath = $baseDir.$packageName."/".$path;
}
private static function getNamespace($package)
{
return key($package->getAutoload()['psr-0']);
}
private static function getBundleNames()
{
$bundleNames = array();
foreach (self::$packagesToEnable as $packageName => $package)
{
$className = null;
$fullPath = self::getBundlePath($package, $packageName);
if ($handle = @opendir($fullPath))
{
// Check for Bundle within root path
while(false !== ($entry = readdir($handle)))
{
if (false !== strpos($entry, 'Bundle'))
{
$className = self::getClassNameFromFile("$fullPath/$entry");
}
}
// Check for Bundle file within namespace subdir path.
$namespacePath = $fullPath . "/" . str_replace("\\", "/", self::getNamespace($package));
if (null == $className && $namespaceHandle = @opendir($namespacePath))
{
while(false !== ($entry = readdir($namespaceHandle)))
{
if (false !== strpos($entry, 'Bundle'))
{
$className = self::getClassNameFromFile("$namespacePath/$entry");
}
}
}
} else {
echo "Unable to open path $fullPath for package {$package->getName()}.\r\n";
}
if (null == $className)
{
echo "Unable to find className for package {$package->getName()}. Checked paths: {$fullPath} and {$namespacePath}.\r\n";
} else {
$bundleNames[] = $className;
}
}
return array_unique($bundleNames);
}
private static function getRoutableBundles()
{
$routableBundles = array();
foreach (self::$packagesToEnable as $packageName => $package)
{
// Only allow dynamic routing from cygnus bundles.
// If third-party routes are needed, define them in the package that requires them.
if (false === strpos($packageName, 'cygnus')) continue;
$className = null;
$bundlePath = self::getBundlePath($package, $packageName);
// Get the Bundle name from the path, e.g. vendor/cygnus/base-core-bundle/Cygnus/CoreBundle becomes CygnusCoreBundle
$bundleName = str_replace('/', '', substr($bundlePath, strrpos($bundlePath, '/', -1 - strlen(substr($bundlePath, strrpos($bundlePath, '/'))))+1));
if (file_exists("$bundlePath/Resources/config/routing.yml")) {
$routableBundles[$bundleName] = "@$bundleName/Resources/config/routing.yml";
} elseif (file_exists("$bundlePath/Resources/config/routing.xml")) {
$routableBundles[$bundleName] = "@$bundleName/Resources/config/routing.xml";
} elseif (file_exists("$bundlePath/Resources/config/routing.php")) {
$routableBundles[$bundleName] = "@$bundleName/Resources/config/routing.php";
}
}
return array_unique($routableBundles);
}
private static function updateBundleRoutes()
{
// Bring in routes from loaded cygnus bundles
$appBase = substr(__DIR__, 0, strpos(__DIR__, 'vendor/')) . "app/";
$routesToAdd = self::getRoutableBundles();
$routingConfig = "";
$routeLines = file("{$appBase}config/routing.yml");
$existingRoutes = array();
foreach ($routeLines as $route) {
if (strpos($route, '# ') === 0) {
$comment = str_replace('# ', '', $route);
if (false !== strpos($comment, ' ')) {
$existingRoutes[] = substr($comment, 0, strpos($comment, ' '));
}
}
}
$appRoutesHandle = fopen("{$appBase}config/routing.yml", 'a');
foreach ($routesToAdd as $bundleName => $bundleResource)
{
// $bundleName = self::getBundleName($routingConfig);
if (in_array($bundleName, $existingRoutes)) continue;
$routeComment = "# $bundleName generated routes\r\n";
$routeName = strtolower(preg_replace('/\B([A-Z])/', '_$1', $bundleName)) . ":\r\n";
$routeResource = " resource: \"$bundleResource\"\r\n\r\n";
$routingConfig .= $routeComment;
$routingConfig .= $routeName;
$routingConfig .= $routeResource;
}
if (empty($routingConfig)) return;
echo "Appending routes to configuration.\r\n";
fwrite($appRoutesHandle, $routingConfig);
}
private static function updateAppKernel()
{
// Automagically pull in required dependancies via SensioGeneratorBundle's KernelManipulator
$bundlesToLoad = self::getBundleNames();
$appBase = substr(__DIR__, 0, strpos(__DIR__, 'vendor/')) . "app/";
require_once $appBase."AppKernel.php";
$kernel = new \AppKernel('dev', true);
$kernelManipulator = new KernelManipulator($kernel);
foreach ($bundlesToLoad as $bundle)
{
try {
$kernelManipulator->addBundle($bundle);
echo "Enabled bundle ".$bundle." in AppKernel.\r\n";
} catch (\RuntimeException $e) {
// Catch already loaded bundles and prevent a runtime exception
echo "Bundle ".$bundle." was already enabled in AppKernel, skipping.\r\n";
}
}
}
private static function registerPackages($package) {
foreach ($package->getRequires() as $key => $packageLink)
{
if (FALSE !== strpos($key, 'bundle')) {
$package = self::getPackage($key);
self::$packagesToEnable[$key] = $package;
// Recursively register dependant bundles -- @todo: may need to be cygnus only?
$subRequires = $package->getRequires();
if (!empty($subRequires)) {
self::registerPackages($package);
}
}
}
}
private static function getPackage($name, $version = null)
{
if (is_null($version))
{
$version = self::findLockedPackageVersion($name);
}
return self::$lockedRepository->findPackage($name, $version);
}
private static function findLockedPackageVersion(&$name)
{
$lockedPackages = self::$lockedRepository->getPackages();
$version = null;
foreach ($lockedPackages as $package)
{
if ($name == $package->getName() || $name == $package->getPrettyName()) {
return $package->getPrettyVersion();
} else if (in_array($name, $package->getNames())) {
$name = $package->getName();
return $package->getPrettyVersion();
} else {
$names = $package->getNames();
}
}
throw new \Exception('Unable to find installed/locked version of '.$name.' package.');
}
/**
* getClassNameFromFile
* https://github.com/WouterJ/shift-php/blob/master/src/Wj/Shift/Bundle/Bundle.php#L41-84
* @license https://github.com/WouterJ/shift-php/blob/master/LICENSE
*
* @param string $file File to parse
*
* @access private
* @static
*
* @return string Class Name
*/
private static function getClassNameFromFile($file)
{
$code = file_get_contents($file);
$tokens = token_get_all($code);
$namespace = '';
$class = '';
$ns_token = false;
$class_token = false;
foreach ($tokens as $token) {
if (!is_array($token)) {
continue;
}
if (\T_NAMESPACE === $token[0]) {
$ns_token = true;
$class_token = false;
continue;
} elseif (\T_CLASS === $token[0]) {
$class_token = true;
$ns_token = false;
continue;
}
if ($ns_token) {
if (\T_WHITESPACE === $token[0] || \T_NS_SEPARATOR === $token[0] || \T_STRING === $token[0]) {
$namespace .= $token[1];
} else {
$ns_token = false;
}
}
if ($class_token) {
if (\T_WHITESPACE === $token[0] || \T_STRING === $token[0]) {
$class .= $token[1];
} else {
$class_token = false;
break;
}
}
}
return $class == '' ? false : ltrim(trim($namespace).'\\'.trim($class), '\\');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment