Tuesday, July 31, 2018
Monday, July 30, 2018
Sunday, July 29, 2018
Saturday, July 28, 2018
Friday, July 27, 2018
AniJS: Easy CSS Animations Without Coding
Many people want to add subtle animations to their website in response to clicks or other actions by their visitors. However, not everyone is well-versed in using CSS or JavaScript. Some just know how to modify the HTML and have the change reflect on the website.
Generally, this is the part where developers take over and add the necessary JavaScript and CSS to make your website stand out. However, if you want to be able to build an animated site yourself, without coding, a library called AniJS would help a great deal.
AniJS lets you create animated styling for your website without any JavaScript or CSS coding! You can specify all your animations from HTML using a simple If-On-Do-To syntax.
Installation
Before you can begin animating the elements on your webpage using data-anijs
attributes, you will have to include the necessary files. Three different files are needed to access all the functionality of AniJS. These files are the core JS library, the CSS file for the animations and another helper JavaScript file for using some special AniJS syntax like $addClass
, $toggleClass
, and $removeClass
.
You can also install the library using Bower by running the following command:
bower install anijs --save
Once you have included all the necessary files, the elements on your webpage will be animation ready.
Getting Started with AniJS Syntax
In its basic form, AniJS uses the following syntax to animate particular elements based on any event.
If (any event happens), On (any element), Do (something like animate, add/remove class), To (this or any other element)
Here, the If part specifies the event which will trigger the animation or class manipulation. The On part specifies the element whose events AniJS should be listening to. This can be different from the element on which you have set up the data-anijs
attribute. The Do part specifies the action to take. Here, you can specify the name of the animation that you want to apply etc. Finally, the To part is used to specify the element which needs to be animated or manipulated.
The If part is necessary for the AniJS statement that you add to animate any element. The On part is optional and if left unspecified uses the current element as the default value. The Do part is also necessary as it tells the browser what to do when the specified event happens. The To part is also optional and defaults to the current element when not specified.
You can also use the Before and After hooks to specify what should happen before and after AniJS does the thing mentioned inside the Do part.
Animating Different Elements
AniJS allows you to run an animation by triggering it on any applicable event listed on the MDN page. Similarly, you can use the on
and to
target any element you want using CSS selectors. For example, you could specify that you want to listen to an event on div.promotion
or section div p.first
etc. The do
part can be used to specify the animation that you want to apply to different elements. AniJS has a lot of animations which can be applied on any element you want.
The following HTML snippet will show you how to apply some animations on elements which will be triggered on certain events.
<div class="orange box" data-anijs="if: mouseover, do: swing animated"></div> <div class="pink box" data-anijs="if: click, do: tada animated"></div> <div class="red box" data-anijs="if: dblclick, do: rubberBand animated"></div> <div class="purple box" data-anijs="if: mousemove, do: shake animated"></div> <div class="black box" data-anijs="if: mouseover, on: body div.green, do: flash animated"></div> <div class="green box" data-anijs="if: mouseover, on: div.brown, do: fadeIn animated"></div> <div class="yellow box" data-anijs="if: dblclick, on: body, do: bounce animated"></div> <div class="brown box" data-anijs="if: click, on: div, do: wobble animated"></div>
In each case, all you have to do is just write the statements inside the data-anijs
attribute and the library will take care of the rest. (We have skipped the to
part in all these animations so the animation is applied on the element inside which we have specified the data-anijs
attribute.)
The last four boxes have different values for the on
part. This, for example, means that the animation on green box will happen only when the mouse moves over the brown box. Similarly, the bounce animation on the yellow box will start playing whenever a user double clicks anywhere inside the body.
You can try these animations out yourself in the embedded CodePen demo.
Manipulating Classes and HTML Elements
AniJS allows you to do more than simply animate different elements. For example, you can use it to add, remove or toggle classes applied on different elements. Similarly, you can also remove HTML elements or clone them without adding a single line of JavaScript. The library also allows you to traverse the DOM using special reserved keywords.
Let's begin with class manipulation. AniJS has three reserved keywords for manipulating classes. These are $addClass
, $removeClass
and $toggleClass
. As, the name suggests, you can use them to add, remove and toggle one or multiple classes of an element respectively. All you have to do is specify the class names after the reserved keywords.
Similarly, you can use reserved keywords like $parent
, $ancestors
, $closest
, $find
and $children
to traverse the DOM.
You can use these two sets of reserved keywords together to do something like add a certain class to all the children of an element after a visitor double clicks that particular element. However, which children you are referring to can be ambiguous in certain cases. For example, you might have applied the data-anijs
attribute on one element but set the value of On part to something else using CSS selectors. In this particular situation, AniJS will have no way of knowing if the class has to be added to the children of the element referred by the CSS selector or the element on which you have applied the data-anijs
attribute. In such cases, you can remove the ambiguity by using another reserved keyword called target
. Here, target
refers to the element pointed by the CSS selector.
Consider the following three examples in which AniJS has been used to toggle classes of different elements:
<div class="box" data-anijs="if: click, do: $toggleClass orange"></div> <div class="box second" data-anijs="if: click, do: $toggleClass red, to: $children"> <span class="shells"></span> <!-- Many more span tags --> <span class="shells"></span> </div> <div class="box" data-anijs="if: click, on: .shells, do: $toggleClass yellow, to: $parent target; if: click, on: .shells, do: $toggleClass yellow, to: $parent"></div>
In the above example, I have reformatted the HTML to make it easier to read and see what's going on.
Let's begin with the first div
. In this case, we have omitted both the on
and to
part of the data-anijs
attribute value. Therefore, they both default to the current div
itself. If you try to click on this particular div
, it will toggle the orange
class which in turn changes the box to orange.
In case of second div
, we are telling AniJS to toggle the class called red
for all elements which are children of that particular div
.This will rotate all the children span
elements as well as change their color to red while setting the border-radius to zero.
We have supplied two different statements inside the data-anijs
attribute of the third div
. Both these statements toggle the same yellow
class. However, the effects are completely different due to use of the target
keyword.
In the first case, we have added the target
keyword after the $parent
keyword. This tells AniJS that we want to toggle the class for the parent of the elements pointed by the shells
class. In the second case, we have skipped the target
keyword, so AniJS changes the background of the parent of current div
. Since, the parent of the div
is the body
itself, the whole page turns yellow.
You can try clicking on different elements and see how they affect the page in the embedded CodePen demo.
One more thing worth noticing is that even though the data-anijs
attribute for the third box has two statements, clicking the box itself does not have any effect. This is because we have instructed Anijs to listen to the click events on the span
elements with class shells
in both the cases.
Other Ways to Manipulate HTML
Another way to manipulate HTML elements on a webpage using AniJS would be to clone or remove them. The library has reserved the keywords $remove
and $clone
which will tell it whether you want to remove an element or clone it.
You can pass multiple selectors to $remove
in order to remove multiple elements from the webpage. Keep in mind that different CSS selectors need to be separated with the pipe |
character.
The $clone
keyword also accepts two parameters. The first one is the CSS selector to specify the element you want to clone. The second one is a number to specify how many copies you want to make. For instance, $clone .shells|10
will make 10 copies of the elements with class shells
and append them as children of the element on which the data-anijs
attribute has been specified. If the copies have to be appended to a different element, you can point AniJS to it by specifying the appropriate CSS selector after to
in the AniJS statement.
Conclusion
The aim of this tutorial was to help you get started with AniJS as quickly as possible. As you might have noticed, the library is very easy to use. All you have to do is specify the right attribute values and AniJS will take care of everything else like changing classes, manimulating the DOM, and animating any changes.
The library offers a lot of other features that we have not covered in this tutorial. You should go through the official documentation to learn more about it and use it to its full potential.
Thursday, July 26, 2018
Wednesday, July 25, 2018
Tuesday, July 24, 2018
Monday, July 23, 2018
How to Create Custom CLI Commands Using the Symfony Console Component
In this article, we're going to explore how you could create custom command-line interface (CLI) commands in your PHP applications using the Symfony Console component. After installing the necessary libraries, we'll create a few handful of examples to demonstrate the concepts of the Console component.
In fact, this component is used by several PHP frameworks to develop CLI applications, and a few popular frameworks are already using this component as a starting point.
What Is the Console Component?
The Symfony Console component allows you to create custom CLI commands in your PHP applications. If you have ever worked with Laravel or Symfony, you might be aware of the CLI tools they provide in order to ease day-to-day operations like:
- generating scaffolding code
- clearing caches
- installing, enabling and disabling add-on services
- running database migrations
- and more
In the case of Laravel, for example, it comes with the artisan
tool which provides plenty of utility commands that make our life easier. You may be surprised to know that the artisan
tool is built on top of the Symfony Console component itself! In fact, there are many frameworks that leverage the Console component to build their command-line tools.
In this article, we're going to explore the basics of the Console component so that you can create custom CLI commands in your PHP applications. To start with, we'll go ahead and install the Console component using Composer. After installation, we'll build a few examples for demonstration purposes.
Installation and Configuration
In this section, we're going to install the Console component that is required in order to create CLI commands in your PHP applications. I assume that you've installed Composer in your system—we'll need it to install the Console component which is available from Packagist.
Once you've installed Composer, go ahead and install the Console component using the following command.
$composer require symfony/console
That should have created the composer.json file, which should look like this:
{ "require": { "symfony/console": "^4.0" } }
Let's modify the composer.json file to look like the following one:
{ "require": { "symfony/console": "^4.0" }, "autoload": { "psr-4": { "Console\\": "src" }, "classmap": ["src"] } }
As we've added a new classmap
entry, let's go ahead and update the Composer autoloader by running the following command.
$composer dump -o
Now, you can use the Console
namespace to autoload classes under the src directory.
Your First HelloWorld Command
Creating CLI commands using the Console component is a two-step process.
- First, you need to create a console application which loads the necessary dependencies and registers your custom commands.
- Next, you need to create files for all commands that you have registered with the console application.
Create the Console Application
In this section, we'll go ahead and create our custom console application.
The proposed directory structure of our console application looks like this.
|-- bin | `-- console |-- composer.json |-- composer.lock |-- src | `-- App | `-- Commands | |-- ClearcacheCommand.php | `-- HelloworldCommand.php `-- vendor
Go ahead and create the main application file bin/console with the following contents. Please note that there's no file extension, and also make sure that it's executable as well since we'll need to run it from the command line.
#!/usr/bin/env php <?php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\Console\Application; $app = new Application(); $app->run();
The first line in the file #!/usr/bin/env php
makes sure that it's run under the PHP environment. Go ahead and try to run it and see how it goes.
$bin/console Console Tool Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: help Displays help for a command list Lists commands
Not bad! With just a few lines of code, you have a custom console application rolling at your disposal! But it's not doing anything useful at the moment. In the next section, we'll see how you can create custom commands and register it with our custom console application.
Create the Hello World Command File
Let's go ahead and create our first custom command: HelloworldCommand
. Create the src/App/Commands/HelloworldCommand.php file with the following contents.
<?php namespace Console\App\Commands; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; class HelloworldCommand extends Command { protected function configure() { $this->setName('hello-world') ->setDescription('Prints Hello-World!') ->setHelp('Demonstration of custom commands created by Symfony Console component.') ->addArgument('username', InputArgument::REQUIRED, 'Pass the username.'); } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln(sprintf('Hello World!, %s', $input->getArgument('username'))); } }
There are two main methods that you should create while creating your custom command: configure
and execute
.
As the name suggests, the configure
method allows you to configure your command so that you can set up the command name, a short description of the command, help text, and more. You can also configure arguments for your command if you want to pass parameters while running a command.
In the above example, the command name is set to hello-world
. Also, we want to pass a username as the first argument, and hence we've configured it using the addArgument
method.
On the other hand, the execute
method contains the application logic of the command. In our case, we've kept it pretty simple by displaying Hello World as the output of the command.
Before you can go ahead and actually run this command, you need to register it with the console application which we've created in the previous section. Let's quickly revise the bin/console file to look like the following one.
#!/usr/bin/env php <?php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\Console\Application; use Console\App\Commands\HelloworldCommand; $app = new Application(); $app->add(new HelloworldCommand()); $app->run();
As you can see, we've used the add
method of the Application
object to add the HelloworldCommand
command. Let's quickly list all the available commands.
$bin/console list Console Tool Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: hello-world Prints Hello-World! help Displays help for a command list Lists commands
As expected, the hello-world
command appears in the list of available commands! Go ahead and run it!
$bin/console hello-world tutsplus Hello World!, tutsplus
So that's how you can set up basic commands!
A Real-World Example—Clear Cache Command
In the previous section, we built the hello-world
command to demonstrate the concepts of the Console component. In this section, we'll go ahead and create a real-world example which demonstrates how you could build a command to clear caches in your application.
Create the Clear Cache Command File
Go ahead and create the src/App/Commands/ClearcacheCommand.php file with the following contents.
<?php namespace Console\App\Commands; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; class ClearcacheCommand extends Command { protected function configure() { $this->setName('clear-cache') ->setDescription('Clears the application cache.') ->setHelp('Allows you to delete the application cache. Pass the --groups parameter to clear caches of specific groups.') ->addOption( 'groups', 'g', InputOption::VALUE_OPTIONAL, 'Pass the comma separated group names if you don\'t want to clear all caches.', '' ); } protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Cache is about to cleared...'); if ($input->getOption('groups')) { $groups = explode(",", $input->getOption('groups')); if (is_array($groups) && count($groups)) { foreach ($groups as $group) { $output->writeln(sprintf('%s cache is cleared', $group)); } } } else { $output->writeln('All caches are cleared.'); } $output->writeln('Complete.'); } }
The configure
method is pretty much the same, except that we've used the addOption
method to add an option to our command. Thus, you could pass group values by using the --groups
parameter.
On the other hand, the execute
method contains the application logic of our command.
If you want to clear the cache of specific groups, you need to pass group names along with the --group
parameter. On the other hand, skip the --group
parameter if you want to clear all caches. You may have noticed that we've kept the --group
parameter optional by providing the InputOption::VALUE_OPTIONAL
value in the third argument of the addOption
method.
Registration and Testing With the Console Application
Before we go ahead and actually run it, let's register the command with our console application.
#!/usr/bin/env php <?php require_once __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\Console\Application; use Console\App\Commands\HelloworldCommand; use Console\App\Commands\ClearcacheCommand; $app = new Application(); $app->add(new HelloworldCommand()); $app->add(new ClearcacheCommand()); $app->run();
Now, go ahead and run the bin/console clear-cache
command to clear all caches!
$bin/console clear-cache Cache is about to cleared... All caches are cleared. Complete.
Next, if you want to clear specific caches, you could try something like this.
$bin/console clear-cache --groups=group1,group2 Cache is about to cleared... group1 cache is cleared group2 cache is cleared Complete.
Of course, you will need to implement the actual logic to clear caches, but that should serve as a good starting point.
Conclusion
Today, we went through one of the popular components provided by the Symfony framework: the Console Component. It's really a useful component should you wish to develop your own CLI application which helps you execute your day-to-day utility tasks with ease.
In the first half, we went through the installation and configuration of the component. Then, in the second half, we created a couple of examples of console commands.
Let us know what you think in the comments below.
Sunday, July 22, 2018
Saturday, July 21, 2018
Friday, July 20, 2018
Thursday, July 19, 2018
Wednesday, July 18, 2018
Tuesday, July 17, 2018
Monday, July 16, 2018
Sunday, July 15, 2018
Saturday, July 14, 2018
Friday, July 13, 2018
Set Up Routing in PHP Applications Using the Symfony Routing Component
Today, we'll go through the Symfony Routing component, which allows you to set up routing in your PHP applications.
What Is the Symfony Routing Component?
The Symfony Routing Component is a very popular routing component which is adapted by several frameworks and provides a lot of flexibility should you wish to set up routes in your PHP application.
If you've built a custom PHP application and are looking for a feature-rich routing library, the Symfony Routing Component is more than a worth a look. It also allows you to define routes for your application in the YAML format.
Starting with installation and configuration, we'll go through real-world examples to demonstrate a variety of options the component has for route configuration. In this article, you'll learn:
- installation and configuration
- how to set up basic routes
- how to load routes from the YAML file
- how to use the all-in-one router
Installation and Configuration
In this section, we're going to install the libraries that are required in order to set up routing in your PHP applications. I assume that you've installed Composer in your system as we'll need it to install the necessary libraries that are available on Packagist.
Once you've installed Composer, go ahead and install the core Routing component using the following command.
$composer require symfony/routing
Although the Routing component itself is sufficient to provide comprehensive routing features in your application, we'll go ahead and install a few other components as well to make our life easier and enrich the existing core routing functionality.
To start with, we'll go ahead and install the HttpFoundation component, which provides an object-oriented wrapper for PHP global variables and response-related functions. It makes sure that you don't need to access global variables like $_GET
, $_POST
and the like directly.
$composer require symfony/http-foundation
Next, if you want to define your application routes in the YAML file instead of the PHP code, it's the YAML component that comes to the rescue as it helps you to convert YAML strings to PHP arrays and vice versa.
$composer require symfony/yaml
Finally, we'll install the Config component, which provides several utility classes to initialize and deal with configuration values defined in the different types of file like YAML, INI, XML, etc. In our case, we'll use it to load routes from the YAML file.
$composer require symfony/config
So that's the installation part, but how are you supposed to use it? In fact, it's just a matter of including the autoload.php file created by Composer in your application, as shown in the following snippet.
<?php require_once './vendor/autoload.php'; // application code ?>
Set Up Basic Routes
In the previous section, we went through the installation of the necessary routing components. Now, you're ready to set up routing in your PHP application right away.
Let's go ahead and create the basic_routes.php file with the following contents.
<?php require_once './vendor/autoload.php'; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Routing\Exception\ResourceNotFoundException; try { // Init basic route $foo_route = new Route( '/foo', array('controller' => 'FooController') ); // Init route with dynamic placeholders $foo_placeholder_route = new Route( '/foo/{id}', array('controller' => 'FooController', 'method'=>'load'), array('id' => '[0-9]+') ); // Add Route object(s) to RouteCollection object $routes = new RouteCollection(); $routes->add('foo_route', $foo_route); $routes->add('foo_placeholder_route', $foo_placeholder_route); // Init RequestContext object $context = new RequestContext(); $context->fromRequest(Request::createFromGlobals()); // Init UrlMatcher object $matcher = new UrlMatcher($routes, $context); // Find the current route $parameters = $matcher->match($context->getPathInfo()); // How to generate a SEO URL $generator = new UrlGenerator($routes, $context); $url = $generator->generate('foo_placeholder_route', array( 'id' => 123, )); echo '<pre>'; print_r($parameters); echo 'Generated URL: ' . $url; exit; } catch (ResourceNotFoundException $e) { echo $e->getMessage(); }
Setting up routing using the Symfony Routing component usually goes through a series of steps as listed below.
- Initialize the
Route
object for each of your application routes. - Add all
Route
objects to theRouteCollection
object. - Initialize the
RequestContext
object which holds the current request context information. - Initialize the
UrlMatcher
object by passing theRouteCollection
object and theRequestContext
object.
Initialize the Route Object for Different Routes
Let's go ahead and define a pretty basic foo
route.
$foo_route = new Route( '/foo', array('controller' => 'FooController') );
The first argument of the Route
constructor is the URI path, and the second argument is the array of custom attributes that you want to return when this particular route is matched. Typically, it would be a combination of the controller and method that you would like to call when this route is requested.
Next, let's have a look at the parameterized route.
$foo_placeholder_route = new Route( '/foo/{id}', array('controller' => 'FooController', 'method'=>'load'), array('id' => '[0-9]+') );
The above route can match URIs like foo/1
, foo/123
and similar. Please note that we've restricted the {id}
parameter to numeric values only, and hence it won't match URIs like foo/bar
since the {id}
parameter is provided as a string.
Add All Route Objects to the RouteCollection Object
The next step is to add route objects that we've initialized in the previous section to the RouteCollection
object.
$routes = new RouteCollection(); $routes->add('foo_route', $foo_route); $routes->add('foo_placeholder_route', $foo_placeholder_route);
As you can see, it's pretty straightforward as you just need to use the add
method of the RouteCollection
object to add route objects. The first argument of the add
method is the name of the route, and the second argument is the route object itself.
Initialize the RequestContext
Object
Next, we need to initialize the RequestContext
object, which holds the current request context information. We'll need this object when we initialize the UrlMatcher
object as we'll go through it in a moment.
$context = new RequestContext(); $context->fromRequest(Request::createFromGlobals());
Initialize the UrlMatcher
Object
Finally, we need to initialize the UrlMatcher
object along with routes and context information.
// Init UrlMatcher object $matcher = new UrlMatcher($routes, $context);
Now, we have everything we could match our routes against.
How to Match Routes
It's the match
method of the UrlMatcher
object which allows you to match any route against a set of predefined routes.
The match
method takes the URI as its first argument and tries to match it against predefined routes. If the route is found, it returns custom attributes associated with that route. On the other hand, it throws the ResourceNotFoundException
exception if there's no route associated with the current URI.
$parameters = $matcher->match($context->getPathInfo());
In our case, we've provided the current URI by fetching it from the $context
object. So, if you're accessing the http://your-domain/basic_routes.php/foo URL, the $context->getPathInfo()
returns foo
, and we've already defined a route for the foo
URI, so it should return us the following.
Array ( [controller] => FooController [_route] => foo_route )
Now, let's go ahead and test the parameterized route by accessing the http://your-domain/basic_routes.php/foo/123 URL.
Array ( [controller] => FooController [method] => load [id] => 123 [_route] => foo_placeholder_route )
It worked if you can see that the id
parameter is bound with the appropriate value 123
.
Next, let's try to access a non-existent route like http://your-domain/basic_routes.php/unknown-route, and you should see the following message.
No routes found for "/unknown-route".
So that's how you can find routes using the match
method.
Apart from this, you could also use the Routing
component to generate links in your application. Provided RouteCollection
and RequestContext
objects, the UrlGenerator
allows you to build links for specific routes.
$generator = new UrlGenerator($routes, $context); $url = $generator->generate('foo_placeholder_route', array( 'id' => 123, ));
The first argument of the generate
method is the route name, and the second argument is the array that may contain parameters if it's the parameterized route. The above code should generate the /basic_routes.php/foo/123 URL.
Load Routes From the YAML File
In the previous section, we built our custom routes using the Route
and RouteCollection
objects. In fact, the Routing
component offers different ways you could choose from to instantiate routes. You could choose from various loaders like YamlFileLoader
, XmlFileLoader
, and PhpFileLoader
.
In this section, we'll go through the YamlFileLoader
loader to see how to load routes from the YAML file.
The Routes YAML File
Go ahead and create the routes.yaml file with the following contents.
foo_route: path: /foo defaults: { controller: 'FooController::indexAction' } foo_placeholder_route: path: /foo/{id} defaults: { controller: 'FooController::loadAction' } requirements: id: '[0-9]+'
An Example File
Next, go ahead and make the load_routes_from_yaml.php file with the following contents.
<?php require_once './vendor/autoload.php'; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Config\FileLocator; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Exception\ResourceNotFoundException; try { // Load routes from the yaml file $fileLocator = new FileLocator(array(__DIR__)); $loader = new YamlFileLoader($fileLocator); $routes = $loader->load('routes.yaml'); // Init RequestContext object $context = new RequestContext(); $context->fromRequest(Request::createFromGlobals()); // Init UrlMatcher object $matcher = new UrlMatcher($routes, $context); // Find the current route $parameters = $matcher->match($context->getPathInfo()); // How to generate a SEO URL $generator = new UrlGenerator($routes, $context); $url = $generator->generate('foo_placeholder_route', array( 'id' => 123, )); echo '<pre>'; print_r($parameters); echo 'Generated URL: ' . $url; exit; } catch (ResourceNotFoundException $e) { echo $e->getMessage(); }
The only thing that's different in this case is the way we initialize routes!
$fileLocator = new FileLocator(array(__DIR__)); $loader = new YamlFileLoader($fileLocator); $routes = $loader->load('routes.yaml');
We've used the YamlFileLoader
loader to load routes from the routes.yaml file instead of initializing it directly in the PHP itself. Apart from that, everything is the same and should produce the same results as that of the basic_routes.php file.
The All-in-One Router
Lastly in this section, we'll go through the Router
class, which allows you to set up routing quickly with fewer lines of code.
Go ahead and make the all_in_one_router.php file with the following contents.
<?php require_once './vendor/autoload.php'; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Router; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Config\FileLocator; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Exception\ResourceNotFoundException; try { $fileLocator = new FileLocator(array(__DIR__)); $requestContext = new RequestContext(); $requestContext->fromRequest(Request::createFromGlobals()); $router = new Router( new YamlFileLoader($fileLocator), 'routes.yaml', array('cache_dir' => __DIR__.'/cache'), $requestContext ); // Find the current route $parameters = $router->match($requestContext->getPathInfo()); // How to generate a SEO URL $routes = $router->getRouteCollection(); $generator = new UrlGenerator($routes, $requestContext); $url = $generator->generate('foo_placeholder_route', array( 'id' => 123, )); echo '<pre>'; print_r($parameters); echo 'Generated URL: ' . $url; exit; } catch (ResourceNotFoundException $e) { echo $e->getMessage(); }
Everything is pretty much the same, except that we've instantiated the Router
object along with the necessary dependencies.
$router = new Router( new YamlFileLoader($fileLocator), 'routes.yaml', array('cache_dir' => __DIR__.'/cache'), $requestContext );
With that in place, you can straight away use the match
method of the Router object for route mapping.
$parameters = $router->match($requestContext->getPathInfo());
Also, you will need to use the getRouteCollection
method of the Router object to fetch routes.
$routes = $router->getRouteCollection();
Conclusion
Go ahead and explore the other options available in the Routing component—I would love to hear your thoughts!
Today, we explored the Symfony Routing component, which makes implementation of routing in PHP applications a breeze. Along the way, we created a handful of examples to demonstrate various aspects of the Routing component.
I hope that you've enjoyed this article, and feel free to post your thoughts using the feed below!
Thursday, July 12, 2018
Wednesday, July 11, 2018
Tuesday, July 10, 2018
Monday, July 9, 2018
Secure, Passwordless Authentication Using Auth0
In this article, you'll learn how to set up passwordless authentication using the Auth0 service. Auth0 allows you to outsource authentication features for your app.
What Is Auth0?
Auth0 is an authentication-as-a-service tool that makes implementation of authentication-related features for your app or site a breeze. If you've built an app and you want to just outsource the authentication and authorization features, a service like Auth0 is something you should consider.
Let me quickly summarize what Auth0 has to offer:
- single sign-on
- multi-factor authentication
- passwordless logins
- user management
- and much more!
Initial Setup
In this section, we'll go through the initial setup in preparation for setting up the passwordless authentication.
Before moving ahead, make sure to install Composer as that will be used to install actual Auth0 SDKs using the composer.json file. Also, if you want to follow along with the examples in this article, go ahead and get yourself a free account with Auth0.
Let's start by grabbing a clone of the sample project.
git clone https://github.com/auth0-samples/auth0-php-web-app.git .
Install Dependencies
Go ahead the run the composer install
command to install the dependencies.
cd 00-Starter-Seed composer install
According to the composer.json file, you should have installed the vlucas/phpdotenv and auth0/auth0-php packages.
{ "name": "auth0/basic-webapp-sample", "description": "Basic sample for securing a WebApp with Auth0", "require": { "vlucas/phpdotenv": "2.4.0", "auth0/auth0-php": "~5.0" }, "license": "MIT", "authors": [ { "name": "Martin Gontovnikas", "email": "martin@gon.to" }, { "name": "Germán Lena", "email": "german.lena@gmail.com" } ] }
The vlucas/phpdotenv library is used to initialize environment variables from the .env file. Thus, it allows you to separate configuration from the code that changes between environments.
On the other hand, the auth0/auth0-php package is the one which will help us set up authorization in our app.
Configure the Environment Variables
Next, let's set up the configuration for our app in the .env file. Go ahead and create the .env file by copying it from the .env.example file.
cp .env.example .env
It contains configuration values which will be used by the Auth0 library.
AUTH0_CLIENT_ID={CLIENT_ID} AUTH0_DOMAIN={DOMAIN_NAME} AUTH0_CLIENT_SECRET={CLIENT_SECRET} AUTH0_CALLBACK_URL={CALLBACK_URL} AUTH0_AUDIENCE=
You should be able to find most of the settings under Applications > Default App > Settings on the Auth0 dashboard. Please note that I'm using the default application created by the system. Of course, you could go ahead and create a new application if you want to do so.
The AUTH0_CALLBACK_URL
is the URL of your app where Auth0 will redirect users after login and logout. The value that you set in this field must be configured under Allowed Callback URLs in the application settings on the Auth0 dashboard.
Passwordless Login Using Email
To enable passwordless login using email, go to Connections > Passwordless on the Auth0 dashboard and enable the Email option.
In most cases, the default configuration settings under Email just work out of the box. Of course, if you want to change any settings, go ahead and do that. Don't forget to enable the apps for which you want passwordless authentication at Connections > Passwordless > Email > Applications.
With the initial setup in place, we can go ahead and create a file which implements the passwordless login using email.
Implement Passwordless Login
Go ahead and create the email_auth_example.php file with the following contents.
<?php // Require composer autoloader require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php'; use Auth0\SDK\Auth0; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $audience = getenv('AUTH0_AUDIENCE'); if($audience == ''){ $audience = 'https://' . $domain . '/userinfo'; } $auth0 = new Auth0([ 'domain' => $domain, 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'audience' => $audience, 'scope' => 'openid profile', 'persist_id_token' => true, 'persist_access_token' => true, 'persist_refresh_token' => true, ]); $userInfo = $auth0->getUser(); ?> <html> <head> <script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- font awesome from BootstrapCDN --> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet"> <link href="public/app.css" rel="stylesheet"> </head> <body class="home"> <div class="container"> <div class="login-page clearfix"> <?php if(!$userInfo): ?> <script src="https://cdn.auth0.com/js/lock/11.6.1/lock.min.js"></script> <script type="text/javascript"> function login() { var lock = new Auth0LockPasswordless('<?php echo $client_id; ?>', '<?php echo $domain; ?>', { allowedConnections: ['email'], // Should match the Email connection name, it defaults to 'email' passwordlessMethod: 'code', // If not specified, defaults to 'code' auth: { redirectUrl: '<?php echo $redirect_uri; ?>', responseType: 'code' } }); lock.show(); } </script> <a href="javascript:login()">Passwordless Login Using Email</a> <?php else: ?> <div class="logged-in-box auth0-box logged-in"> <h1 id="logo"><img src="//cdn.auth0.com/samples/auth0_logo_final_blue_RGB.png" /></h1> <img class="avatar" src="<?php echo $userInfo['picture'] ?>"/> <h2>Welcome <span class="nickname"><?php echo $userInfo['nickname'] ?></span></h2> <a class="btn btn-warning btn-logout" href="/logout.php">Logout</a> </div> <?php endif ?> </div> </div> </body> </html>
At the beginning, we have included auto loaders which are responsible for loading the Auth0 and environment variable related classes.
Following that, we initialize configuration variables from the .env file using the getenv
function.
Next, we have called the getUser
method on the Auth0 object to check if any active session is present. Based on that, we display the SignIn
link if there's no active session. Otherwise, the username of the logged-in user with the Logout
link is displayed.
Go ahead and run the email_auth_example.php file to test your app!
Passwordless Login Using SMS
To enable passwordless login using SMS, go to Connections > Passwordless on the Auth0 dashboard and enable the SMS option. By default, Auth0 uses the Twilio service to send messages. So go ahead and get yourself a Twilio account.
From the Twilio dashboard, get your ACCOUNT SID and AUTH TOKEN and enter those values in the Twilio SID and Twilio AuthToken fields at Connections > Passwordless > SMS > Settings on the Auth0 dashboard.
Also, you need to create a new messaging service at SMS > Messaging Services on your Twilio dashboard. After successful creation of the service, you'll get the Service ID, and that's what you will need to enter in the Copilot SID field at Connections > Passwordless > SMS > Settings.
Finally, let's take a peek at how passwordless login using SMS works. Create the sms_auth_example.php file. The contents are the same as email_auth_example.php, except the login()
function, which is as follows.
function login() { var lock = new Auth0LockPasswordless('<?php echo $client_id; ?>', '<?php echo $domain; ?>', { allowedConnections: ['sms'], // Should match the SMS connection name auth: { redirectUrl: '<?php echo $redirect_uri; ?>', responseType: 'code' } }); lock.show(); };
Everything is pretty much the same, except that we have provided sms
instead of email
in the allowedConnections
property.
Go ahead and run the sms_auth_example.php file to test your app!
Conclusion
Today, we looked at passwordless login with the Auth0 service. Among the possible methods available, we implemented the email and SMS methods with code examples.
Feel free to leave any thoughts or questions using the feed below!
Saturday, July 7, 2018
Friday, July 6, 2018
Thursday, July 5, 2018
Wednesday, July 4, 2018
Parsing HTML With PHP Using DiDOM
Every now and then developers need to scrape webpages to get some information from a website. For example, let's say you are working on a personal project where you have to get geographical information about the capitals of different countries from Wikipedia. Entering this manually would take a lot of time. However, you can do it very quickly by scraping the Wikipedia page with the help of PHP. You will also be able to automatically parse the HTML to get specific information instead of going through the whole markup manually.
In this tutorial, we will learn about an easy to use and fast HTML parser called DiDOM. We will begin with the installation process and then learn how to extract information from different elements on a webpage using different kinds of selectors like tags, classes etc.
Installation and Usage
You can easily install DiDOM in your project directory by running the following command:
composer require imangazaliev/didom
Once you have run the above command, you will be able to load HTML from a string, a local file or a webpage. Here is an example:
require_once('vendor/autoload.php'); use DiDom\Document; $document = new Document($washington_dc_html_string); $document = new Document('washington_dc.html', true); $url = 'https://en.wikipedia.org/wiki/Washington,_D.C.'; $document = new Document($url, true);
When you decide to parse HTML from a document, it could already be loaded and stored in a variable. In such cases, you can simply pass that variable to Document()
and DiDOM will prepare the string for parsing.
If the HTML has to be loaded from a file or a URL, you can pass that as the first parameter to Document()
and set the second parameter to true
.
You can also create a new Document
object by using new Document()
without any parameters. In this case, you can call the method loadHtml()
to load HTML from a string and loadHtmlFile()
to load HTML from a file or webpage.
Finding HTML Elements
The first thing that you have to do before getting the HTML or text from an element is find the element itself. The easiest way to do that is to simply use the find()
method and pass the CSS selector for your intended element as the first parameter.
You can also pass the XPath for an element as the first parameter of the find()
method. However, this requires you to pass Query::TYPE_XPATH
as the second parameter.
If you only want to use XPath values for finding an HTML element, you can simply use the xpath()
method instead of passing Query::TYPE_XPATH
as second parameter to find()
every time.
If DiDOM can find elements which match the passed CSS selector or XPATH expression, it will return an array of instances of DiDom\Element
. If no such elements are found, it will return an empty array.
Since these methods return an array, you can directly access the nth matching element by using find()[n-1]
.
An Example
In the following example, we will be getting the inner HTML from all the first and second level headings in the Wikipedia article about Washington, D.C..
require_once('vendor/autoload.php'); use DiDom\Document; $document = new Document('https://en.wikipedia.org/wiki/Washington,_D.C.', true); $main_heading = $document->find('h1.firstHeading')[0]; echo $main_heading->html(); $sub_headings = $document->find('h2'); foreach($sub_headings as $sub_heading) { if($sub_heading->text() !== 'See also') { echo $sub_heading->html(); } else { break; } }
We begin by creating a new Document object by passing the URL of the Wikipedia article about Washington, D.C.. After that, we get the main heading element using the find()
method and store it inside a variable called $main_heading
. We will now be able to call different methods on this element like text()
, innerHtml()
and html()
etc.
For the main heading, we just call html()
method which returns the HTML of whole heading element. Similarly, we can get the HTML inside a particular element by using the innerHtml()
method. Sometimes, you will be more interested in the plain text content of an element instead of its HTML. In such cases, you can simply use the text()
method an be done with it.
The level two headings divide our Wikipedia page in well defined sections. However, you might want to get rid of some of those subheadings like "See also", "Notes" etc.
One way to do so would be to loop through all the level two headings and check the value returned by the text()
method. We break out of the loop if the returned heading text is "See also".
You could directly get to the 4th or 6th level two heading by using $document->find('h2')[3]
and $document->find('h2')[5]
respectively.
Traversing Up and Down the DOM
Once you have access to a particular element, the library allows you to traverse up and down the DOM tree to access other elements with ease.
You can go to the parent of an HTML element using the parent()
method. Similarly, you can get to the next or previous sibling of an element using the nextSibling()
and previousSibling()
methods.
There are a lot of methods available to get access to the children of a DOM element as well. For instance, you can get to a particular child element using the child(n)
method. Similarly, you can get access to the first or last child of a particular element using the firstChild()
and lastChild()
methods. You can loop over all the children of a particular DOM element using the children()
method.
Once you get to a particular element, you will be able to access its HTML etc. using the html()
, innerHtml()
and text()
methods.
In the following example, we start with level two heading elements and keep checking if the next sibling element contains some text. As soon as we find a sibling element with some text, we output it to the browser.
require_once('vendor/autoload.php'); use DiDom\Document; $document = new Document('https://en.wikipedia.org/wiki/Washington,_D.C.', true); $sub_headings = $document->find('h2'); for($i = 1; $i < count($sub_headings); $i++) { if($sub_headings[$i]->text() !== 'See also') { $next_sibling = $sub_headings[$i]->nextSibling(); while(!$next_elem->html()) { $next_sibling = $next_sibling->nextSibling(); } echo $next_elem->html()."<br>"; } else { break; } }
You can use a similar technique to loop through all the sibling elements and only output the text if it contains a particular string or if the sibling element is a paragraph tag etc. Once you know the basics, finding the right information is easy.
Manipulating Element Attributes
The ability to get or set the attribute value for different elements can prove very useful in certain situations. For example, we can get the value of src attribute for all the img tags in our Wikipedia article by using $image_elem->attr('src')
. In a similar manner, you can get the value of href attributes for all the a tags in a document.
There are three way for getting the value of a given attribute for an HTML element. You can use the getAttribute('attrName')
method and pass the name of attribute you are interested in as a parameter. You can also use the attr('attrName')
method which works just like getAttribute()
. Finally, the library also allows you to directly get the attribute value using $elem->attrName
. This means that you can get the value of src attribute for an image element directly by using $imageElem->src
.
require_once('vendor/autoload.php'); use DiDom\Document; $document = new Document('https://en.wikipedia.org/wiki/Washington,_D.C.', true); $images = $document->find('img'); foreach($images as $image) { echo $image->src."<br>"; }
Once you have access to the src attributes, you can write the code to automatically download all the image files. This way you will be able to save a lot of time.
You can also set the value of a given attribute using three different techniques. First, you can use the setAttribute('attrName', 'attrValue')
method to set the attribute value. You can also use the attr('attrName', 'attrValue')
method to set the attribute value. Finally, you can set the attribute value for a given element using $Elem->attrName = 'attrValue'
.
Adding, Removing and Replacing Elements
You can also make changes to the loaded HTML document using different methods provided by the library. For example, you can add, replace or remove elements from the DOM tree using the appendChild()
, replace()
and remove()
methods.
The library also allows you to create your own HTML elements in order to append them to the original HTML document. You can create a new Element object by using new Element('tagName', 'tagContent')
.
Keep in mind that you will get a Uncaught Error: Class 'Element' not found error if your program does not contain the line use DiDom\Element
before instantiating the element object.
Once you have the element, you can either append it to other elements in the DOM using the appendChild()
method or you can use the replace()
method to use the newly instantiated element as a replacement for some old HTML element in the document. The following example should help in further clarifying this concept.
require_once('vendor/autoload.php'); use DiDom\Document; use DiDom\Element; $document = new Document('https://en.wikipedia.org/wiki/Washington,_D.C.', true); // This will result in error. echo $document->find('h2.test-heading')[0]->html()."\n"; $test_heading = new Element('h2', 'This is test heading.'); $test_heading->class = 'test-heading'; $document->find('h1')[0]->replace($test_heading); echo $document->find('h2.test-heading')[0]->html()."\n";
Initially, there is no h2 element in our document with the class test-heading. Therefore, we will keep getting an error if we try to access such an element.
After verifying that there is no such element, we create a new h2 element and change the value of its class attribute to test-heading.
After that, we replace the first h1 element in the document with our newly created h2 element. Using the find()
method on our document again to find the h2 heading with class test-heading will return an element now.
Final Thoughts
This tutorial covered the basics of PHP DiDOM HTML parser. We began with the installation and then learned how to load HTML from a string, file or URL. After that, we discussed how to find a particular element based on its CSS selector or XPath. We also learned how to get the siblings, parent or children of an element. The rest of the sections covered how we can manipulate the attributes of a particular element or add, remove and replace elements in an HTML document.
If there is anything that you would like me to clarify in the tutorial, feel free to let me know in the comments.
Tuesday, July 3, 2018
The Quirks of CSS Grid and Absolute Positioning
It’s quite possible to use CSS positioning on grid items, just as you would with most other elements. There are one or two quirks, however, so let’s take a quick look to make sure you avoid the pitfalls.
Simple Relative Positioning
Let’s begin with a simple grid, with nine items, laid out in three columns. Each column is 1fr wide, except the third column which (thanks to minmax()
) will shrink no smaller than 160px:
grid-template-columns: 1fr 1fr minmax(160px, 1fr);
By adding a couple of rules to one of the items, we can position it relatively:
.item-2 { position: relative; right: 100px; top: 30px; }
So just like we might expect, we declare that item-2
is to be positioned relatively, then define some offset properties (though don’t try using the fr unit on these, it won’t work).
You’ll notice, if you resize the window, that the grid item continues to behave (resize) exactly as it was before we repositioned it, and it’s still, selfishly, reserving its place in the grid in case it feels like coming back.
Slightly More Complex Absolute Positioning
So what happens when we absolutely position that item? Firstly, it will position itself against its closest ancestor which has a declared position value. If you don’t set position: relative;
(for example) on the grid container, the grid item will fly out of the grid’s boundaries in search of something else to hold onto, such as the html element.
You’ll see in the demo above that the item is now absolutely positioned 100px from the left and 30px from the top of the grid container. It has effectively been removed from the document flow, as is normal with absolutely positioned elements. Its slot in the grid has been filled by item-3
and the other items have placed themselves to fill up the remaining gaps.
Note: if our grid container were to having padding, the positioning would be in relation to those outer padding boundaries.
You’ll also see that it no longer has the dimensions it was using when it was part of the grid. It has shrunk to the size of its contents. The grid doesn’t influence the sizing of the element, and the element doesn’t influence the sizing of the grid in any way.
Absolute Position Within the Grid
It might take some getting used to, but in addition to the normal offsets you can also position a grid item using the grid-placement properties. For example, let’s place our item-2
absolutely on grid-area: 3 / 2;
(in other words, starting on the third row line down, and the second column line across).
It looks odd, but you can see that the item, still unaffected by the sizing of the grid and still outside the flow, has positioned itself rudely on top of item-9
. It’s as though it has a grid all of its own, on top of the original.
Note: z-index will allow you to change the stacking order of the items, if you want.
Moving on, if we then add an offset into the mix (top: 50px;
for example) our item will apply that offset, while remaining true to its own imaginary grid placement:
A Note on the Implicit Grid
In our previous tutorial we talked about how Grid will create implicit tracks if they’re needed; tracks beyond those we explicitly define. We can position items on those implicit grid tracks if they exist, but Grid won’t create those tracks for elements outside of the flow.
In the demo below, we’ve positioned item-2
on grid-area: 2 / 4;
but this is only possible because item-6
, which is still in the flow, has already prompted Grid to create those extra tracks for us.
Conclusion
Why would you need positioning with Grid? Initially it might seem like overkill. But when you consider broken grid layouts and moving beyond the straightforward “across and down” web pages we’ve become used to, I think you’ll find positioning quite useful.
-
CSS Grid LayoutCreate a Broken Grid Layout Using CSS GridIan Yates
-
CSS Grid LayoutCSS Grid Layout: A Quick Start GuideIan Yates