Applications, not content:
Another world:
<script> var foo = {bar: 'baz'}; foo.bar === foo['bar']; // true </script>
<script> var foo = function() { return new Error(); } foo(); </script>
<script> var foo = function returnsAnError() { return new Error(); } foo(); // same as calling returnsAnError() </script>
class Klass { public __construct() { } } $inst = new Klass(); // new Instance from Klass class
var KlassWrapper = (function returnsTheClass() { var Klass = function theConstructor() { // Functions are objects }; return Klass; }()); var inst = new KlassWrapper(); // new Instance from Klass object
class Klass { public $propy; public __construct($val) { $this->propy=$val; } } $inst = new Klass(); $inst->propy; // Klass knows about a variable container called propy, // but the value belongs to instance.
var Klass = (function returnsTheClass() { var Klass = function theConstructor(val) { this.propy = val; }; return Klass; }()); var inst = new Klass(); inst.propy; // Klass doesn’t know about propy.
class Klass { const propy = ‘whatever’; static public function foo() { return self::propy; } } Klass::propy; Klass::foo();
var Klass = (function Wrap() { var Klass = { ‘propy’: ‘whatever’ }; Klass.foo = function (){ return Klass.propy; } return Klass; }()); Klass.propy; // Sorry, you can edit this if you're not careful. Klass.foo();
var Klass = (function returnsTheClass() { function Klass theConstructor(val) { // Remember, that's an alias for var Klass = function Klass theConstructor(val) {... this._propy = val; // Not actually enforced private }; // THIS DOES NOT WORK Klass.getPropy = function publicGetPropy() { return this._propy; // ‘this’ variable is not looking at your instance. } return Klass; }()); var inst = new Klass(); inst.getPropy(); // getPropy is a method on a different object.
class Klass { private propy; public __construct(val) { this->propy=val; } // This is bad idea, do not do public static getPropy() { return this->propy; } } $inst = new Klass(); $Klass->getPropy(); // getPropy is clearly not going to return what you want
var Klass = function() { // Remember, functions ARE objects this.instanceVal = '1'; // They accept properties }; Klass.prototype.getInVal = function() { return this.instanceVal; } Klass.notAProtoMethod = function() { return this; } // Values of things in comments Klass; // function () { this.instanceVal = '1'} Klass.notAProtoMethod; // function () { return this } Klass.notAProtoMethod(); // function () { this.instanceVal = '1'} new Klass(); // Klass {instanceVal: "1", getInVal: function } new Klass().notAProtoMethod; // undefined new Klass().getInVal // function () { return this.instanceVal; } new Klass().getInVal() // "1"
class Klass { private propy; public __construct(val) { this->propy=val; } public getPropy() { return this->propy; } } $inst = new Klass(); $inst->getPropy;
var Klass = (function () { var Klass = function (val) { this._propy = val; }; Klass.prototype.getPropy = function () { return this._propy; } return Klass; }()); var inst = new Klass(1); inst.getPropy(); // returns 1
class Klass { private propy; public __construct(val) { this->propy= $this->randy(); } private randy () { return rand(); } } $inst = new Klass(); // Propy is private
var Klass = (function Wrap() { var Klass = function (val) { this._propy = randy(); }; // Only things inside the scope of Wrap can call randy var randy = function () { return Math.random(); } return Klass; }()); var inst = new Klass(1); inst._propy; // It’s up to you to not call _propy; // some people try to write libs just for that
PHP
JavaScript
<script> this.awhSnapYo = “I’m everywhere”; awhSnapYo === this.awhSnapYo === window.awhSnapYo // true </script>
Oops, you've attached to the global scope
<script> ({ x: “I only exist here”, getX: function() { return this.x; // 'getX' is part of the { object } } }).getX() // "I only exist here" </script>
'this' is set to the object the method is called on
function f1(){ return this; } f1() === window; // global object
function f2(){ "use strict"; // tells ES5 engine to use strict mode return this; // strict mode prevents access of window via ‘this’ } f2() === undefined; // true
Don't be scared, those { } braces create object literals. They aren't for scope resolution.
var x = “I wont exist inside the following object”; var previousObject = { x: “I only exist here”, getX: function() {return this.x;} }; var boundGetXMethod = previousObject.getX.bind( {x: “exclusive to bound method”} ); // This does not execute the method boundGetXMethod() // “exclusive to bound method”
A list where items build:
<?php $someArray = $_POST; ?> <script> <?php foreach ($someArray as $value) { echo '$("' . $value . '").fadeIn()'; // FIXME: Introduces XSS exploit } ?> </script>
Exhibit A: The architectural antipattern equivalent of using eval:
<script> $("#foo").fadeIn(); $("#bar").fadeIn(); $("#baz").fadeIn(); $(""); window.location="lol-credit-card-fraud-scam.org.biz.gov.uk.new.tld; //").fadeIn(); </script>
A list where items build:
<?php $someArray = $_POST; ?> <script> // Expose a configuration set, separated from the app. var items = <?php echo json_encode($someArray); ?>; // Expose your app (external <script> files are even better) _.each(items, function(item) { $(item).fadeIn(); }); </script>
Treat your JS apps like they are vendors.
composer.json+lock
require: { "namespaced/php-package-name": "v1.0.0" }
package.json
dependencies: { "namespaced-package-name": "git+ssh://git.example.com/namespaced/package-name#v1.1.1" }
You could use just composer if you wanted to. I find that this improves separation of concerns.
<body ng-apps="namespacedApp"> <ng-view></ng-view> <script src="{{ assets_common('js/app.js', 'namespacedapp') }}"></script> {# assets_common is a custom twig call that extends assetic, you could just drop in the path #} <script> angular.module('namespacedApp').run(['$rootScope', function($rootScope){ $rootScope.configuration = {{ variableWithJsonPassedFromController|raw }}; }]); </script> </body>
Application updates only need to worry about data changes, not templates and states everywhere
var applicationInterface = new AppInterface({ apiInterface: new apiInterface(), flashElement: '{{ swfId }}' topic: new Topic('someSubscriptionTopic', '{{ url('sf2_endpoint') }}'), debug: {{ app.debug ? 'true' : 'false '}}, initializationValues: {{ initializationValues }} }); swfobject.embedSWF( // Third party flash loading library '{{ asset_flash('app.swf') }}', '{{ swfId }}', ..., { jsPublishEndpoint: function calledWhenFlashPushesData(data) { applicationInterface.publish(data); } } );
All we do here is define a configuration set.
JS gets an API endpoint. Flash can talk to JS, and it gets a JS function to call.
7242a48 Changes polling code into websocket queue code. f5e62b7 Removes XHR things. ae78d08 Adds socket.io client 39bc101 added authentication for nodejs incoming connection
class Authentication// ... { // ... public function saveAuthentication(FilterResponseEvent $event) { $this->redisClient->set($key, json_encode([ 'isAuthenticated' => // ... 'sessionId' => // ... ]) ...
+-----------+ Backend side +----------> Redis +----------+ | +-----------+ | +-----+--+ +-----v-----+ | SF2 | | Node.JS | // Node.js Could send Private API calls to SF2 | | | Socket.io| +--^-+---+ +----|------+ +--------|-|---+-----+---------------+--+----|-----------+ | | +---------------+ | Browser side | +---------> <-------+ +-+---------+ Browser | +---------------+
class QueuePusher { protected static $KEY_QUEUE = 'queue_name'; protected $queueClient; public function __construct($queueClient) { ... } public function notify($data) { $this->queueClient->publish( self::$KEY_QUEUE, json_encode($data) ); } }
Node side I just subscribe to messages. This kind of thing has been done enough it's easy to search for.
Before...
XHRTransport.prototype.connect = function(topic) { this._request('GET', topic).then( function onFulfilled(response) { if (response.status !== 304) { // because cache happens topic.executeCallback.bind(topic)(message); } }); };
After...
SocketTransport.prototype.connect = function(topic) { var socket = this._socket; socket.on(topic.client, function(){ topic.executeCallback.bind(topic)(message); }); };
Also some non-free stuff, but there's a discount code they said I could give out
Slides are on my github!