Part one

JavaScript is not PHP

Change how you think about JavaScript

Don't abuse JS, use JS.

Applications, not content:

  • Serve application configuration, not content.
  • JS is not a glorified HTML template

Another world:

  • Prototypes are not classes.
  • Objects, arrays, and syntax are not identical
  • Multiple versions of JS interpreters
  • This in JS is not This in PHP
  • Functions are not scary

{} != []

Key language differences and gotchas.

  • Objects in JS do not have order
  • PHP's array() is an ordered map, not an array!
  • Iterating over objects in JS requires a safety guard
  • JavaScript requires you use var, PHP will fail if you use var.
  • In JavaScript there is no object arrow operator.
                <script>
                    var foo = {bar: 'baz'};
                    foo.bar === foo['bar']; // true
                </script>
            

Functions are just data

        <script>
            var foo = function() {
                return new Error();
            }

            foo();
        </script>
      
  • Argh! Stack trace says this came from "Anonymous function" instead of function "foo" !

Good practice: name your functions

Do what you do in PHP: name them after what they do

        <script>
            var foo = function returnsAnError() {
                return new Error();
            }
            
            foo(); // same as calling returnsAnError()
        </script>
      
  • Stack trace says this came from "returnsAnError function"
  • Self-documenting

Classless objects

Get a new instance

            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
      

Classless objects

Properties

        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.
      

Classless objects

Static as an object

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();
      

Classless objects the wrong way

This code only demonstrates a common mistake, don't do this.

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.
      

Classless objects the wrong way

This broken code demonstrates what that mistake might look like in PHP.

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
      

What's a prototype

If properties are for instances, prototypes are for...?

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"
      

Classless objects

Class methods, not instance methods

class Klass
{
    private propy;

    public __construct(val) {
        this->propy=val;
    }

    public getPropy() {
        return this->propy;
    }
}

$inst = new Klass();
$inst->getPropy;
      

Classless objects

Prototypes

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
      

Classless objects

Private

class Klass
{
    private propy;
    public __construct(val) {
        this->propy= $this->randy();
    }

    private randy () {
        return rand();
    }
}

$inst = new Klass();
// Propy is private
      

Classless objects

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
      

'this' keyword

PHP

  • Reference to object's instance


JavaScript

  • This is not what you think it is
  • Mutable references to context of an object
  • This will confuse you

'this' keyword

Think about the scope it is used in, not about the "class".

    <script>
        this.awhSnapYo = “I’m everywhere”;
        awhSnapYo === this.awhSnapYo === window.awhSnapYo // true
    </script>
          

Oops, you've attached to the global scope

'this' keyword

Think about the scope it is used in, not about the "class".

    <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

'this' keyword

Good practice: use strict mode

        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
          

'this' keyword

Can be changed whenever you want

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”
        

Part 2

How to cleanly put JavaScript inside PHP

Sending JS from PHP

The crappy dynamic code generation way

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>

Sending JS from PHP

Passing a configuration to a fixed program

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.

Practical application

Integrating AngularJS with my PHP app

  • Project includes non-PHP devs
  • Can prototype it before knowing about how it would be connected
  • Allows modular parts of a larger system

JS is just another vendor

composer.json+lock

  • Verson locks
  • require: {
        "namespaced/php-package-name": "v1.0.0"
    }
    

package.json

  • Verson locks
  • 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.

JS is just another vendor

Here's how you can bootstrap an angular app from another repo

<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

Try to avoid vendor duplication

  • Usually contextual to what you're doing. I don't know of any silver bullet.
  • Bootstrap and jQuery are vendors. Try to use just one, and source them from just one place at build time.
  • CDNs are great
  • You can always make your own assets CDN

Practical application

Change a polling API to a NodeJS Websocket connection

  • Flash app spoke in binary serialized objects to "Zend AMF".
  • Hard to change quickly, hook into things, extend.
  • Hard to offer a good standalone mode option.

PHP to JS. JS to Flash.

    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.

Refactor polling to websockets!


  • Apache doesn't do it
  • The PHP WAMP server's load kept crashing the server
  • "Why are we writing an event based webserver in PHP, instead of Node?"

Changing code from XHR polling to Websockets.

Key commits.

7242a48  Changes polling code into websocket queue code.
f5e62b7  Removes XHR things.
ae78d08  Adds socket.io client
39bc101  added authentication for nodejs incoming connection

Adding Node.js: Authentication

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      |                
                     +---------------+                

Adding Node.js: Push data

Just a bridge between Node.js and PHP

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.

Adding Node.js: client-side

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);
   });
};

More ways to level up!

Also some non-free stuff, but there's a discount code they said I could give out

<Thank You!>

Slides are on my github!