Symfony to generate dynamic routing

For dynamic routing, one of problem is Symphony looking for router

from cache file, and dynamic router is not saved into cache, for deal with this problem,

we have to delete Symphony’s router cache file. here is how to do it.

1> we have to build up a service. define service file first

example.cache:
    class: Example\CacheBundle\Service\RouterCacheService
    arguments:
      - @router
      - %kernel.environment%
      - %kernel.cache_dir%

use Symfony\Component\Routing\Router;

class RouterCacheService
{
    private $router;
    private $environment;
    private $cache_dir;
    public function __construct(Router $router, $env, $cacheDir)
    {
        $this->router = $router;
        $this->environment = $env;
        $this->cache_dir = $cacheDir;
    }
    public function clearCache($environment = null)
    {
        if (null === $environment) {
            $environment = $this->environment;
        }

        $this->router->clearCache($this->cache_dir, $environment, $this->environment == $environment);
    }
}

2> overriding cache clean function. In Symphony , there is a
Symfony\Bundle\FrameworkBundle\Routing\Router class, we need to extends this class, and overriding clearCache method. here is code: under bundler’s Routing folder, create this class,
use Symfony\Bundle\FrameworkBundle\Routing\Router as BaseRouter;

class CacheCleanRouter extends BaseRouter
{
    public function clearCache($cache_dir, $environment, $warm_up)
    {
        $environment = ucfirst($environment);

        @unlink($cache_dir .'/app'. $environment .'UrlMatcher.php');
        @unlink($cache_dir .'/app'. $environment .'UrlGenerator.php');

        if ($warm_up) {
            $this->matcher   = null;
            $this->generator = null;
            $this->warmUp($cache_dir);
        }
    }
}

 

So now , we are able to clean our cache, this will make Dynamic URL correctly recognized by Symfony framework.

the next step we have to do is to write class to generate dynamic URL.

Symfony official doc, https://symfony.com/doc/current/routing/custom_route_loader.html

Pay attention to load method,and $routesContainer. under bundler’s Routing folder, create this class,

class RoutesLoader implements LoaderInterface
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var RoutesContainer
     */
    protected $routesContainer;

    public function __construct(EntityManagerInterface $entityManager, RoutesContainer $routesContainer)
    {
        $this->entityManager = $entityManager;
        $this->routesContainer = $routesContainer;
    }

    /**
     * {@inheritdoc}
     */
    public function load($resource, $type = null)
    {
        $examples = $this->entityManager->getRepository('MyExampleBundle:Examples')->findAll();
        $routes = new RouteCollection();

        foreach ($examples as $example) {
            $custom = $this->routesContainer->getCustomRoutes($example->getSlug());

            foreach ($custom as $name => $route) {
                $routes->add($name, $route);
            }
        }

        return $routes;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($resource, $type = null)
    {
        return 'my_example' === $type;
    }

    /**
     * {@inheritdoc}
     */
    public function getResolver()
    {
        //
    }

    /**
     * {@inheritdoc}
     */
    public function setResolver(LoaderResolverInterface $resolver)
    {
        //
    }
}

 

after defined RoutesLoader class, we need to set it up as a service

myexample.loader.routes_loader:
    class: myexample\Routing\RoutesLoader
    arguments:
        - @doctrine.orm.entity_manager
        - @myexample.routing.routes_container
    tags:
        - { name: routing.loader }
now, it time to define a class to generate dynamic url. create the class as a service first,
myexample.routing.routes_container:
    class: myexample\Routing\RoutesContainer
    arguments:
        - @router

under bundler’s Routing folder, create this class, define a examples.list string which represents /{anytext}-example router.

class RoutesContainer
{
    protected $router;
    protected $config = [
'examples.list'                    => [
            'pattern'  => '/%prefix%-example',
            'nested'   => true,
            'methods'  => [
                'GET',
            ],
            'defaults' => [
                '_controller' => 'myexample:Examples:list',
            ],
       ],
       'examples.edit'                    => [
            'pattern'  => '/%prefix%-example/{id}/edit',
            'nested'   => true,
            'methods'  => [
                'GET',
            ],
           'defaults' => [
              '_controller' => 'myexample:Examples:edit',
           ],
      ],
 ];

protected $routesPrefix = 'myexamples.';

public function __construct(RouterInterface $router)
{
    $this->router = $router;
}

public function getCustomRoutes($prefix)
{
    $routes = [];

    foreach ($this->config as $name => $params) {
        list($name, $route) = $this->createRoute($name, $prefix);
        $routes[$name] = $route;
    }

    return $routes;
}

public function getRealRouteName($routeName, $prefix)
{
    if (!isset($this->config[$routeName])) {
        throw new RouteNotFoundException(sprintf('Route %s not found', $routeName));
    }
    $config = $this->config[$routeName];
    $name = implode('.', [$prefix, $routeName]);

    if ($config['nested']) {
        $name = $this->routesPrefix.$name;
    }

    return $name;
}

/** TODO: this should be more explicit */
public function getPrefixFromRoute($name)
{
    $name = str_replace($this->routesPrefix, '', $name);

    return substr($name, 0, strpos($name, '.'));
}

public function checkAvailability($pattern)
{
    $routes = $this->router->getRouteCollection()->all();

    foreach ($routes as $route) {
        if ($route->getPath() === $pattern) {
            return false;
        }
    }

    return true;
}

public function generateRoute($name, $prefix, array $params = [], $absolute = RouterInterface::ABSOLUTE_PATH)
{
    return $this->router->generate($this->getRealRouteName($name, $prefix), $params, $absolute);
}

protected function createRoute($name, $prefix)
{
    $config = $this->config[$name];
    $name = $this->getRealRouteName($name, $prefix);

    $route = new Route(str_replace('%prefix%', $prefix, $config['pattern']), $config['defaults']);
    $route->setMethods($config['methods']);

    return [$name, $route];
}

}

 

now all code is here, but how we going to use it? says, you want to know what is route name for ‘examples.list’ in your controller.

$prefix = 'pen';

$this->get('myexample.routing.routes_container')->getRealRouteName('examples.list', $prefix);

the above code will find out router for pen-example
want redirect to dynamic url in your controller?
$prefix = 'pen';
$id = 2;
$url = $this->get('myexample.routing.routes_container')->getRealRouteName('examples.edit', $prefix);
$this->redirectToRoute(
    $url
    [
     'id' => $id,
    ]
);

			

Leave a comment

signing a powershell script

code signing need to executed in administrator context

0> write a powershell script hello.ps1
1> Set-ExecutionPolicy AllSigned
2> New-SelfSignedCertificate -DnsName powershell-self-sign -Type CodeSigning

3> Set-AuthenticodeSignature C:\t\hello.ps1 @(gci Cert:\LocalMachine\My -DnsName powershell-self-sign -codesigning)[0]

4> run cert from start menu.
5> from Personal/Certificate, find powershell-self-sign cert
6> right click the cert , and copy , then paste the cert to intermediate certificate authoritties/Certificates
7> paste the cert to Trusted Root / certificate.

Leave a comment

PHPStorm 2016, vagrant, Symfony and xdebug

How to set up remote debug for symfony app.

1> go to run -> Edit Configurations

2> Click on ‘+’ symbol, top – left corner

3> adding a PHP web application, now a window popup, and give a name to this application.

4> select a server, if no server set up, following below steps.

4.1> give server a name.

4.2> your host name and port (not xdebug port)

4.3> choose xdebug

4.5> set up mapping.

for example:

if your symfony app is intalled on folder is e:\www\test\dev, and Vagrantfile is under e:\www\test, then on vagrant server,

you will have /vagrant/dev, and Symfony app’s doc root is /vagrant/dev/web/

now, use above information to set up mapping.

e:\www\test mapping to /vagrant

e:\www\test\dev mapping to /vagrant/dev

e:\www\test\dev\web mapping to /vagrant/dev/web

5> start URL set to “/”

6> select a browser.

On vagrant server side, install xdebug.

put following xdebuger content to php config file.

xdebug.max_nesting_level=200
xdebug.remote_enable=on
xdebug.remote_host=127.0.0.1
xdebug.idekey=PHPSTORM

 

restart web server or php5-fpm instance.

 

now, go back to hosting machine. I am use windows 10.

run putty.exe, on Connection->SSH->Tunnels set up ssh tunnel.

tunnel

now login into vagrant. make sure port 9000 is not been occupied.

go to phpstrom. turn on debug listener, on top – right corner.

debug

go to File->Settings->Languages & Frameworks -> PHP -> debug, make sure xdebug port is 9000

debug_port

 

set a break point on Symfony app,and from run -> Debug “you app” to start debugging symfony app.

 

Leave a comment

programatically add a new Page to Silverstripe

adding a page with page type ContactPage

if (class_exists("ContactPage") && !DataObject::get_one("ContactPage")) {
    $page = new ContactPage();
    $page->Title = _t("Contact.Title", "Contact");
    $page->Content = "";
    $page->URLSegment = "contact";
    $page->Status = "Published";
    $page->Sort = 1;
    $page->ShowInMenus = 1;
    $page->ShowInSearch = 1;
    $page->write();
    $page->doRestoreToStage();
    $page->publish("Stage", "Live");
    $page->flushCache();
    DB::alteration_message("contact page created", 'created');
}

If is just a page type:
$page = new SiteTree();

or

$page = new Page();
To get parent page:

$parent = SiteTree::get_by_link($link);
$link is string which is a URL.

To attaching a page to a parent page:

$page->setParent($parentID);

 

Leave a comment

Drupal 8 , Custom Button, Editor form and Ajax

I have to add a button to a Drupal 8 content type editor form, when the button was clicked,

It passes all form values to a backend service via Ajax, and backend service will call google geo api and return latitude and longitude.

First, at default , Drupal 8 only has submit button, when you click the button, it submit whole editor form,

This is not what I want, I just want a normal button which does an Ajax call.

So, I have to create a custom button type:

Under module\src\Element, I have created a php file, called JustButton.

use Drupal\Core\Render\Element\Button;
use Drupal\Core\Render\Element;

/**
*
*
* @FormElement(“just_button”)
*/

class JustButton extends Button
{
public static function preRenderButton($element) {

$element[‘#attributes’][‘type’] = ‘button’;
Element::setAttributes($element, array(‘id’, ‘name’, ‘value’));

$element[‘#attributes’][‘class’][] = ‘button’;
if (!empty($element[‘#button_type’])) {
$element[‘#attributes’][‘class’][] = ‘button–‘ . $element[‘#button_type’];
}
$element[‘#attributes’][‘class’][] = ‘js-form-submit’;
$element[‘#attributes’][‘class’][] = ‘form-submit’;

if (!empty($element[‘#attributes’][‘disabled’])) {
$element[‘#attributes’][‘class’][] = ‘is-disabled’;
}

return $element;
}
}

Now , in my module file (netbyte_locator.module), use form hook to add JustButton to form:

pay close attention to Bold sentence: there are 2 hooks not one.

function netbyte_locator_form_node_locator_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id)
{
    $a  = \Drupal\Core\Url::fromRoute('locator.geoip');
    $form['Looking up latitude/longitude'] = array(
        '#type' => 'just_button',
        '#value' => t('Looking up latitude/longitude'),
        '#weight' => 40,
        '#ajax' => array(
            'url' => $a,
            'progress' => array(
                'type' => 'throbber',
                'message' => t('Looking up geo information...'),
            ),
            'event' => 'mousedown',
            'keypress' => TRUE
        ),
        '#suffix' => '<span class="geo-valid-message"></span>'

    );
}

function netbyte_locator_form_node_locator_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id)
{
    netbyte_locator_form_node_locator_form_alter($form, $form_state, $form_id);
}

 

locator.geoip is my backend service. and inside that service , I have added following code:

public function getGeoInformation($data)
{
    $address = $this->getAddress($data);
    $infor = \Drupal::service('netbyte_locator.geoip')->lookup($address);
    $ajax_response = new AjaxResponse();

    $lat = new InvokeCommand('#edit-field-latitude-0-value','val' , array($infor['lat']));

    $lng = new InvokeCommand('#edit-field-ongitude-0-value','val' , array($infor['lng']));

    $ajax_response->addCommand($lat);
    return $ajax_response->addCommand($lng);
}

that 'val' is a jQuery method, equivalent to $("#edit-field-latitude-0-value").val("your value")

#edit-field-latitude-0-value (Latitude) is form input element. like below:

drupal_8_ajax

 

Leave a comment

Auto save url alias – Drupal 8

Drupal 8: Implement hook_node_insert()/update(),
call \Drupal::service(‘path.alias_storage’)->save($entity->urlInfo()->getInternalPat‌​h(), $alias, $entity->language()->getId())

1 Comment

adding category to Page

function add_taxonomies_to_pages() {
register_taxonomy_for_object_type( ‘post_tag’, ‘page’ );
register_taxonomy_for_object_type( ‘category’, ‘page’ );
}
add_action( ‘init’, ‘add_taxonomies_to_pages’ );

1 Comment