Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Static Class Methods

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Go back and look at the new get _selectors() method:

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 5 - 30
get _selectors() {
return {
newRepForm: '.js-new-rep-log-form'
}
}
... lines 36 - 175
}
... lines 177 - 206
})(window, jQuery, Routing, swal);

Interesting: PhpStorm is highlighting it like something is wrong! If you hover over it, it says:

Method can be static.

In the first episode, we talked about how when you add your methods to the prototype, it's like creating non-static methods on PHP classes:

Greeter = function (greeting) {
    this.greeting = greeting;
}

Greeter.prototype.sayHi = function () {
    console.log(this.greeting);
}

In other words, when you create new instances of your object, each method has access to its own instance properties:

greeter = new Greeter('YO!');
greeter.sayHi(); // YO!

I also said that if you decided not to put a method on the prototype, that is legal, but it effectively becomes static:

Greeter = function (greeting) {
    // ...
}

Greeter.sayHi = function () {
    console.log('YO!');
}

Greeter.sayHi(); // YO!

If that didn't make a lot of sense then, it's okay. Because with the new class syntax, it's much easier to think about!

PhpStorm is suggesting that this method could be static for one simple reason: the method doesn't use the this variable. That's the same as in PHP: if a method doesn't use the this variable, it could be made static if we wanted.

It's probably fine either way, but let's make this static! Add static before get _selectors():

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 5 - 30
static get _selectors() {
return {
newRepForm: '.js-new-rep-log-form'
}
}
... lines 36 - 175
}
... lines 177 - 206
})(window, jQuery, Routing, swal);

And as soon as we do that, we can't say this._selectors anymore. Instead, we need to say RepLogApp._selectors:

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
... lines 5 - 134
_mapErrorsToForm(errorData) {
... line 136
const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
... lines 138 - 151
}
... lines 153 - 175
}
... lines 177 - 206
})(window, jQuery, Routing, swal);

And that makes sense: in PHP, we do the same thing: we use the class name to reference items statically. Let's change that in a few other places:

... lines 1 - 2
(function(window, $, Routing, swal) {
class RepLogApp {
constructor($wrapper) {
... lines 6 - 20
this.$wrapper.on(
'submit',
RepLogApp._selectors.newRepForm,
this.handleNewFormSubmit.bind(this)
);
}
... lines 27 - 134
_mapErrorsToForm(errorData) {
... line 136
const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
... lines 138 - 151
}
_removeFormErrors() {
const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
... lines 156 - 157
}
_clearForm() {
... lines 161 - 162
const $form = this.$wrapper.find(RepLogApp._selectors.newRepForm);
... line 164
}
... lines 166 - 175
}
... lines 177 - 206
})(window, jQuery, Routing, swal);

Perfect!

Time to try things! Refresh! Yes! No errors!

Let's see one more example: scroll all the way down to the Helper class. Create a new method: static _calculateWeight() with an $elements argument:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 177
/**
* A "private" object
*/
class Helper {
... lines 182 - 201
static _calculateWeights($elements) {
... lines 203 - 208
}
}
... lines 211 - 212
})(window, jQuery, Routing, swal);

This will be a new static utility method whose job is to loop over whatever elements I pass, look for their weight data attribute, and then return the total weight:

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 177
/**
* A "private" object
*/
class Helper {
... lines 182 - 201
static _calculateWeights($elements) {
let totalWeight = 0;
$elements.each((index, element) => {
totalWeight += $(element).data('weight');
});
return totalWeight;
}
}
... lines 211 - 212
})(window, jQuery, Routing, swal);

We don't really need to make this change, but it's valid.

Now, in calculateTotalWeight(), just say: return Helper - because we need to reference the static method by its class name Helper._calculateTotalWeight() and pass it the elements: this.$wrapper.find('tbody tr'):

... lines 1 - 2
(function(window, $, Routing, swal) {
... lines 4 - 177
/**
* A "private" object
*/
class Helper {
... lines 182 - 185
calculateTotalWeight() {
return Helper._calculateWeights(
this.$wrapper.find('tbody tr')
);
}
... lines 191 - 209
}
... lines 211 - 212
})(window, jQuery, Routing, swal);

Coolio! Try that out! And we still see the correct total.

And that is 10 times easier to understand as a PHP developer! Sure, JavaScript still has prototypical inheritance behind the scenes... but most of the time, we won't know or care.

Leave a comment!

2
Login or Register to join the conversation
Richard Avatar
Richard Avatar Richard | posted 5 years ago

you guys need to get your video download numbering sorted ;)

Reply

Hey Richard ,

Oh, man, that was a real mess! But now all the video names are fixed, and should be sorted well now ;) Btw, thanks for reporting this problem, I hate wrong order and incorrect file names. If you find such problem somewhere else - just let me know!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses Symfony 3. But, since this is a JavaScript tutorial, all the concepts work fine in newer versions of Symfony.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.2.*", // v3.2.14
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.0" // v3.2.2
    }
}
userVoice