Categories
Web Development

How to fix Symfony FosJsRoutingBundle outputs routes in browser

Yeah I got this problem once too. The routing bundle outputs the routes in your browser on a plain white background, giving the user no options to navigate etc. after they register or login.

A user needs to see a real page

So how do you fix this? Read the last step of this article I wrote How to get URL Routes in your Javascript in Symfony 5+

It is a long article but it explains everything. See the note about the Last Step. I don’t want to repeat it here because I don’t think many will view this article anyways.

Categories
Uncategorized

How to fix Symfony Compile Error: Cannot use Repository as Repository because the name is already in use

So you got this error? WTF does it even mean?

baby what does that even mean meme
What does this error mean?

Well my actual error was this.
Compile Error: Cannot use App\Repository\PageUrlsRepository as PageUrlsRepository because the name is already in use

I oopsied

You can see here what I did is accidentally add a second use statement. I remove that and BOOM all back to normal.

Categories
Web Development Web Security

How to secure individual Symfony AJAX api routes without using API Platform

I am in the process of updating this article entirely. Please stay tuned.

Creating the Symfony route is easy. Checking if the request was sent by AJAX is again easy. But what stops a mischievous hacker from hitting that endpoint and trying to get a list of used emails or something else with a script?

What if you have routes that you want to access with AJAX without API Platform? Maybe your project leader won’t let you use it. Maybe you don’t want to use it. What ever reason you still need to secure your endpoints to some extent. Api platform adds lots of great useful features though so you may want to check it out after reading this article.

For me, some parts of my app are too complex to be dealt with as objects rehydrated etc. My app checks permissions in many places to  see if the user has rights etc. and does creative things with the data, another reason I don’t use Doctrine all the time either. Doctrine and API platform can work together to make most simple actions… even more simple, which can speed up development of simple apps.

With Symfony, standard forms created with the Form Component, your forms are CSRF protected. But, when you are sending an AJAX request to an endpoint without a form how do you protect it?

There is probably some Symfony approved way I am not aware of. There is also more than one way to do this. One is cheap the other is expensive. The cheap I’ll cover first, the expensive I’ll cover eventually.

The cheap I call cheap because it is fast and easy and I’ll describe it below. The expensive I call expensive because it involves more steps + calculations = more cpu = more time etc it may not be much but it is still more. The second way is using JWT’s or JSON Web tokens, which is what API platform does anyways. They are quite complex, take more time to setup and are a little slower than the quick and easy way. JWT’s also increase your bandwidth usage and cause other issues.

The quick and easy I use for API endpoints that do simple things that need to  be as quick as possible, like an endpoint that returns a list of hashtags for a hint list in a form for example. JWT’s take more time to implement and are slower as they require more steps than just checking if two secret strings match.

If you send the whole form you can use a different procedure and use the CSRF string stored in the form. I’ll try to cover that too, somewhere below.

The cheap way

For simple situations where you need to randomly access a route you can do something similar to the CSRF form protection by generating a unique string and saving in a Session cookie and to the page/form.

Where you save the string in the page is up to you, but it should be a hidden element. This element needs a unique ID in the page so that you can access it with Javascript. A hidden input element in a form works great, otherwise use a hidden span element.(use css to hide the element).

When you need to make a request to the route you use javascript to get the value you hid in the element. Make sure it is just the unique string that you fetch not the entire element html or this wont work. Include this string with the data you are sending to the route. You can also store the value in a Secure cookie with HttpOnly set. The way you choose is up to you.

Inside your route fetch the unique string that you sent in your AJAX. Then try to fetch the same unique string from your session cookies. If the string exists and matches process the request.

There are tricks you can try to use with the header like checking the users browser agent. But that is useless as it can be easily spoofed by a good hacker using something like Curl.

This unique string trick isn’t 100% hacker proof. But it makes it a hell of a lot harder.  More on CSRF attacks here.

NOTE

If you are using the Symfony forms with CSRF activated then you can use Javascript to fetch the value of the nonce hidden in the _token input element. However, if your code will make multiple ajax requests, then you might want to create the custom hidden field and generate a new unique string each time and replace it in the custom field.

Step #1 create the field

To create the field add it in the FormType definition like this. The entire class is too long so I’ll show just the add section.


->add('ajaxString', HiddenType::class, [
                'mapped' => false,
                'attr' => ['class' => 'hidden-field', 'value' => $secretString]
            ])

Notice mapped is false so that I don’t get errors.

Step #2 Build the form

Now you build the form inside the Template for the form. Mine looks like this.


{{ form_start(registrationForm) }}
        {{ form_errors(registrationForm) }}
        {{ form_row(registrationForm.email) }}
        {{ form_row(registrationForm.emailMatch) }}
        {{ form_row(registrationForm.plainPassword) }}
        {{ form_row(registrationForm.passwordMatch) }}
        {{ form_row(registrationForm.userAlias) }}
        {{ form_row(registrationForm.ajaxString, { 'id': 'ajaxString'}) }}
        {{ form_row(registrationForm.agreeTerms) }}

        <div class="d-flex justify-content-center">
            <button type="submit" class="btn btn-lg btn-success">Register</button>
        </div>

        {{ form_end(registrationForm) }}

Notice how I have the id : ajaxString line. This is currently the only way to change the ID of a form field in Symfony see How to change the id for a form input in Symfony 5+

Step #3 add initial value

Inside the controller you must add the initial value for the field and store it in a session cookie.

For this I am using a simple class which generates semi random/unique strings. This doesn’t need to be super top notch secure, it is just to make sure the request is coming from a form my app built.

To access the Session Cookie in Symfony 5.3+ you must now use RequestStack instead of Session or SessionInterface for some odd reason. It just makes it more obscure and harder to figure out how to get to sessions.


$session = $this->requestStack->getCurrentRequest()->getSession();
        $secretString = RandomStringGenerator::lowercaseUppercaseNumberString(32);
        $session->set('secretString', $secretString);

 

To check the value in the Controller route endpoint I do like this.


$secretString = $request->query->get('secretString');
        $secretString = DataSanitizer::sanitizeString($secretString);
        $string = $this->requestStack->getCurrentRequest()->get('secretString');

        if ($request->isXmlHttpRequest() && $secretString === $string) {

Note that secretString is the value sent by the AJAX request. This was the value I hid in the form field to use for this purpose.
The other line

$string=$this->requestStack->getCurrentRequest()->get(‘secretString’);

gets the value I stored in the Session Cookie. Then the if statement makes sure the two values match before processing the request. If the two strings match we know that my app built the form, added the string and my Javascript copied the string and sent it to my server. This prevents people from randomly hitting your route endpoints.

&& $secretString === $string

Using the CSRF

I have discovered another way to go about this. Instead of creating your own random string, you can use the CSRF that Symfony already created for this form.

The expensive way

I’ll have to fill in all of the details later for this, it almost needs to be it’s own article. It is expensive because of the time it takes to figure out and implement. Using JWT (json web tokens) with Symfony. Here is a good article I found helpful about the subject. I don’t have time to write one right now.

Links

Here is a good link to Symfony Casts about API Platform. There are many symfony casts here to learn more. I was going to post each but this link contains all of them with pretty pictures and descriptions. LOL

More about CSRF in symfony forms here in the documentation.

Categories
Web Development

Symfony 5.3+ how to use Sessions with RequestStack

So some changes happened in Symfony 5.3. Previously you could get to a session with either Session or SessionInterface. Some didn’t like how that worked so now it is moved to RequestStack. The docs or article are not correct here.

It shows you get to the session like this.


$session = $this->requestStack->getSession();

But that doesn’t work. You will be told that RequestStack doesn’t have a getSession() method. I had to open up the source code to figure out how this works.

You get to the session instead like this now.


 $session = $this->requestStack->getCurrentRequest()->getSession();

Note you have to call getCurrentRequest() then getSession. now you can use sessions like this.


$session = $this->requestStack->getCurrentRequest()->getSession();
        $session->set('key-name', $value);

You will now have access to all of the session methods via $session. Your IDE should now list all of the methods in the Session class that you can access.

How to get the RequestStack?

So how do you get the ReqeustStack? Autowiring.

You simply Autowire it into your Controller route method or the __constructor() method. I prefer the constructor method in my Controllers if more than one route needs it.  But in other services you have no choice, it has to be autowired via the constructor like this.



   private RequestStack $requestStack;

    public function __construct(MysqlConnection $mysqlConnection, RequestStack $requestStack)
    {
        $this->mysqlConnection = $mysqlConnection;
        $this->illegalRequest = 'Sorry. Your request to this API is not allowed';
        $this->requestStack = $requestStack;
    }

Now any method can access the requestStack and through the RequestStack you can access the Session. At least for now.

Here is a link to the actual Symfony Session docs.

Categories
Web Development

Symfony 5+ how to make a form field hidden from display

This is easier than it sounds, but I am writing this in case I need to remember what the answer is.

At first I wasn’t paying attention to all of the many different Symfony form types in this long list.  I totally didn’t see the HiddenType in the list or I didn’t notice it.

I tried to simply add a class using attr in the definition. This kind of worked. It just showed the name of the field in a label, which wouldn’t work for my design and use.

HiddenType works exactly like what I needed.

When building a form in a FormType class you can create hidden fields like this one which hides a nonce for AJAX request validation.

->add('ajaxString', HiddenType::class, [
    'mapped' => false,
    'attr' => ['class' => 'hidden-field', 'value' => $secretString]
])

Always add ‘mapped’ false for any field you want to tell Symfony to ignore, like this field used for processing AJAX requests. $secretString is just a random 32 character string I am storing in a session on the backend and sending with the AJAX request to make sure the request is coming from my app.

Categories
Web Development

How to change the id for a form input in Symfony 5+

If you create your forms with classes in Symfony 5+ then changing the ID of the form fields is something you are not allowed to do apparently. LOL You can add/change the class and other attributes but not the id. For some reason Symfony ONLY lets you change the id inside the template. I don’t know why.

Yes I am serious

To start with what got me even interested in trying to use attr and row_attr is when I was messing around with some of my forms, I was copying and pasting and moving parts in the template. This lead to issues as I would miss pieces or get things wrong some how. So I started trying to do the whole thing inside the FormType definition class using the methods below.

It has been pointed out that some feel it is better to define class, id etc. in the template. But as I pointed out above, I had issues with that. So below is what I found.

What doesn’t work

If you are like me then you have probably tried changing the ID by using the attr or row_attr attributes of the Type right? That seems logical right?

These two methods  are not even consistent. First off row_attr only accepts some attributes, which ones I have no idea, it ignores placeholder and id apparently. So then I tried attr, it works with placeholder but ignores ID.

Makes sense right?

It sure would be nice if those didn’t ignore the values you sent to them wouldn’t it. This problem is nearly 10 years old. Later I may look over the code update it and do a PR.

If you are like me you are using Javascript to read hidden fields from the form for various reasons. Otherwise the standard naming of ID’s works flawlessly. I had not discovered this until I had this unique use case.

What does work

So it appears the only way to do this is inconsistentYou have to do it in the form rendering code inside the template. Like this

{{ form_row(registrationForm.ajaxString, { 'id': 'ajaxString'}) }}

You can also change/add other attributes this way, but you can ONLY CHANGE THE ID THIS WAY.

Otherwise Symfony takes it upon itself to name the field for you and ignore your request.

Dictating like…

It would be much easier and consistent if I could just add the ID in the FormType definition class instead of having to add it to the template. Class and other attributes can be added/changed like this, but not id. Just a little confusing that is all.

Categories
Software Development Web Development

Symfony 5+ Twig templates don’t forget to call the parent

Twig templates use inheritance and allows you to create named sections like this.


<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel='icon' href="{{ asset('images/favicon.ico') }}" type='image/x-icon' >
    {% block head_extra %}{% endblock %}
    <title>{% block title %}{{ title }}{% endblock %}</title>
    {% block stylesheets %}
        {# 'app' must match the first argument to addEntry() in webpack.config.js #}
        {{ encore_entry_link_tags('app') }}
    {% endblock %}
</head>
<body>

<main>
    <div class="container">
       {% block navbar %}{{ include('_nav_bar.html.twig') }}{% endblock %}
        {% block body %}{% endblock %}
        {% block javascripts %}
            {# these files are needed for getting url routes in javascript  #}
            <script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
            <script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>
            {# 'app' must match the first argument to addEntry() in webpack.config.js #}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}

        {% block javascript_extra %}{% endblock %}
    </div>
</main>
</body>
</html>

And then you can reuse/inherit this template in another template like this.

{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}

{% block stylesheets %}
{{ parent() }}

{% endblock %}
{% block body %}

{{ form_start(registrationForm) }}
{{ form_errors(registrationForm) }}
{{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.emailMatch) }}
{{ form_row(registrationForm.plainPassword) }}
{{ form_row(registrationForm.passwordMatch) }}
{{ form_row(registrationForm.userAlias) }}
{{ form_row(registrationForm.agreeTerms) }}

{{ form_end(registrationForm) }}

{% endblock %}
{% block javascript_extra %}

{{ encore_entry_script_tags('registration') }}

{% endblock %}

Notice the extends keyword and notice this section

{% block stylesheets %}
{{ parent() }}

{% endblock %}

Notice the use of the “parent” keyword here.

{% block stylesheets %} {{ parent() }} {% endblock %}

Any other css code you want included must come after the parent call like this.


{% block stylesheets %}
    {# 'app' must match the first argument to addEntry() in webpack.config.js #}
    {{ parent() }}
    {{ encore_entry_link_tags('editAboutUser') }}
    {{ encore_entry_link_tags('sogiDraw') }}
{% endblock %}

If I didn’t call “parent()” above then only the CSs for editAboutUser and sogiDraw would be included app would not be included. The CSS for app which is called in the base template in the stylesheets section would not be included without the call to parent().

So if some of your CSS is not working, then the reason is you probably forgot to call parent. This call to parent is saying “Include what was in the parent stylesheet section”. Otherwise without the call you will be missing Styles and be saying WTF?

Don’t forget to call parent
Categories
Web Development

Symfony 5+ how to include page specific javascript or css when using Webpack encore and SASS

In this article I will cover how to do this with Webpack in Symfony with CSS and SASS. This is slightly confusing.

Symfony has it’s own Webpack configuration called encore. Read that documentation article if you need more info, more links at the bottom of the page.

Webpack Config

First lets checkout the file app\webpack.config.js

Mine currently looks like this.


const Encore = require('@symfony/webpack-encore');

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
    // directory where compiled assets will be stored
    .setOutputPath('public/build/')
    // public path used by the web server to access the output path
    .setPublicPath('/build')
    // only needed for CDN's or sub-directory deploy
    //.setManifestKeyPrefix('build/')

    /*
     * ENTRY CONFIG
     *
     * Add 1 entry for each "page" of your app
     * (including one that's included on every page - e.g. "app")
     *
     * Each entry will result in one JavaScript file (e.g. app.js)
     * and one CSS file (e.g. app.scss) if your JavaScript imports CSS.
     */
    .addEntry('app', './assets/js/app.js')
    .addEntry('modalAction', './assets/js/modal-call-to-action.js')
    .addEntry('registration', './assets/js/registration.js')
    .addEntry('featuredImage','./assets/js/featured-image.js')
    .addEntry('editAboutUser','./assets/js/edit-about-user.js')
    .addEntry('sogiDraw', './assets/js/sogi-draw.js')
    // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
    .splitEntryChunks()

    // will require an extra script tag for runtime.js
    // but, you probably want this, unless you're building a single-page app
    .enableSingleRuntimeChunk()

    /*
     * FEATURE CONFIG
     *
     * Enable & configure other features below. For a full
     * list of features, see:
     * https://symfony.com/doc/current/frontend.html#adding-more-features
     */
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    // enables hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    // enables Sass/SCSS support
    .enableSassLoader()

    // uncomment if you use TypeScript
    //.enableTypeScriptLoader()

    // uncomment to get integrity="..." attributes on your script & link tags
    // requires WebpackEncoreBundle 1.4 or higher
    //.enableIntegrityHashes(Encore.isProduction())

    // uncomment if you're having problems with a jQuery plugin
    .autoProvidejQuery()

    // uncomment if you use API Platform Admin (composer req api-admin)
    //.enableReactPreset()
    //.addEntry('admin', './assets/js/admin.js')
;

module.exports = Encore.getWebpackConfig();

See each of the addEntry() lines. Each one of those is including  the processing of the Javascript files. If you need some CSS for example on every page you would put a line like the following inside the app.js file at the top to import the CSS.

import {DrawingForm} from "../javascript/templates/drawing-form";
import {DrawingActions} from "../javascript/objects/DrawingActions";
import '/assets/css/dialog/styles.scss';

In the last line here I am including styles.scss with an import statement. The .scss indicates the file is a SASS file.

This mixing of JS and CSS imports gets to be REALLY confusing. You see just importing the CSS into the Javascript is not enough. Nope you must also include the CSS and the Matching Javascript files with tags.

If you just link to the Javascript that is not enough and you will see your JS elements don’t have any related CSS. Importing the CSS just tells Webpack to process it and create another file.

homer simpson meme
Many baby steps to get to your CSS

So the import statement inside the Javascript file for the .scss file tells webpack to take that import along with any other .scss imports it finds and combine them all into one .css file. If there are no .scss inports there will be no matching .css file created.

All of this is supposed to make web development easier.

Modern web development keeps getting easier by the day
Look at the SASS files

Now lets look inside the styles.css really quick to see what it is doing.

@import 'variables';
@import 'dialog-search-form';
//@import 'grids';
@import 'forms';
@import 'overlay';
@import 'dialog';
@import 'canvas-color-picker';
@import 'editor-canvas';
@import 'image-selector';

.content {
    margin: 55px auto 0 auto;
    background-color: #fff;
    width: 95%;
    height: auto;
}
.hidden {
    display: none;
}

.clear-fix:after {
    content: "";
    display: table;
    clear: both;
}

#drawingCanvas{
    background-color: #fff;

}
.canvas-border, #drawingCanvas {
    border: 2px solid #000;
    margin: 0px auto;
    display: block;
}
.center-image {
    text-align: center;
    margin: 0px auto;
}

Notice all of the import statements. This is because I am using SASS. Each of those files contains a small amount of SCSS and Webpack uses SASS to compile it all into a  single file in the end. The import statement lets Webpack do tree shaking and remove unused code.

borat nice meme
Very nice!!!

So to include a page specific CSS you should create a javascript file inside any folder, this is what gets confusing.( you can use an existing JS file) I keep mine in assets\css\ sometimes in further folders like Dialog or Overlay etc. Then inside that file you import the CSS file like this

import ‘/assets/css/dialog/styles.scss’;

replacing styles.scss with your stylesheet or SASS file name.

But that is not all. You still need to include the final css file that webpack creates and saves in public\build\ folder in your template.

If you imported anything in the .scss file webpack compiles it and includes it in the matching .css file. Webpack will create a matching JS and CSS file based on your addEntry() method calls and store them in the build folder.

So you never make changes, EVER to the files in public\build you store all of your files in the assets folder and add an entryPoint definition in the Webpack Encore file.

So to get your CSS working, (actually included in the page) you need to add a line like this in the template. In your templates head section add  a line to include the CSS file like this.

{{ encore_entry_link_tags('styles') }}

That line is in my base.html.twig file which is the main page template that all of my other page templates inherit from.  So public\build\app.css will be available to every page in my app. Notice it does not include the file type. Twig template knows what to do since encore_entry_link_tags is a twig function.

Page specific CSS only.

So to summarize. If you wanted a CSS file included in only one page of your app, you would do the following. This is a dialog I want to include in only one page of my application.

Step #1 create the css

Create a css file in app\assets\css\dialog\dialog.scss directory for example. In my case I will be using SASS so I will create app\assets\css\dialog\dialog.scss Inside there I import the other tiny SASS .scss files I need.

Step #2 create the javascript

Create a Javascript file or import the css in an existing Javascript file. This is just how Webpack works, you need to include your CSS in your JS files and it does everything else (tree shaking), at least for now. So I create the Javascript file app\assets\js\dialog.js Inside that file I import the css like this

import '/assets/css/dialog/dialog.scss';

Now with this in place, Webpack will compile both the css and the javascript from that file and save it in app\public\build\dialog.css and app\public\build\dialog.js These are the files we must include with template specific functions, not the .scss files. (show below) Webpack will take all .scss import calls and do tree shaking to only include the code that is used.

Step #3 register the Entry point

Now you must register dialog.js (whatever your Javascript file name is) inside the webpack.encore.js file shown above add a line like this.

.addEntry('my-page', './assets/js/dialog.js')

Now restart webpack. In my case I am running webpack with watch so I use the following after I ctrl + c to stop the current watch

yarn run encore dev --watch

Webpack Encore will now create the new Javascript and CSS imported in it to the matching file names inside the public\build\ directory.

Now you must include both the Javascript and the CSS files. This is what tripped me up. I was like WTF why is the related CSS not working but the javascript is?

Uhm, wait… what?

Step #4 include the CSS file

So this is one of the most important steps. You have to include the CSS file located in the public\build\ directory, in the head area of your Twig template like this.

{{ encore_entry_link_tags('dialog') }}

Notice there is no .css it is just the name of the file. also note encore_entry_link_tags is used for the CSS only and javascript has it’s own function covered next. This CSS file was created by Webpack thanks to it being imported in dialog.js. The addEntry() function will create the Javascript file plus a CSS file for each import statement it finds. It stores all the imported scss in a single file that matches the javscript file name but with .css tile type.

Step #5 include the Javascript

Now you must include the matching Javascript that uses the imported CSS file. This is the Javascript Webpack Encore created due to the addEntry() line in the Webpack configuration above.

{{ encore_entry_script_tags('dialog') }}

This should be placed in the section where you include all of your Javascripts, mine are included before the closing body tag. Notice this looks exactly the same as including the CSS, except for the function name is now encore_entry_script_tags

I only have a few page specific Javascripts.  Which is why I can never remember this process.

Next up “Don’t forget to call your templates parent” More fun I discovered while using Twig templates.

Links

Learn how to activate SASS in Symfony webpack encore.

Managing CSS and Javascript from Symfony Documentation.

How to activate CSS preprocessors in Symfony documentation.

Categories
Web Development

How to make AJAX requests to Symfony 5+ controllers

I couldn’t find anything in the documentation about this and there is little on the internet about it too.  There is probably a Bundle somewhere for this or some Symfony way, but I didn’t find anything.

I did find this SymfonyCast about submitting a whole form. If you need to test your Controller route read this. If you want to know more read this in the docs about Browserkit.

But what if you need to just send a simple AJAX request to a controller route? Well that is what this is about. This is very basic, there are settings you can add to the request for mime type for example and more.

How to send a simple AJAX request?

First you need the javascript to make a request to your controller endpoint. To do this you will need access to your routes, in your javascript. Read my article How to get URL Routes in your Javascript in Symfony 5+   to find out how.

Once you have your routing setup and you understand how that works you need to build your AJAX request. I use a custom class I built called EzAjax.js which is a wrapper around the jquery ajax function shown below.

The AJAX solution
import {Utils} from "./Utils";

class EZAjax {
    /**
     *
     * @param errorCallback
     * @param successCallback
     * @param requestUrl
     * @param data
     * @param ajaxOptions
     */
    constructor(errorCallback, successCallback, requestUrl, data, ajaxOptions = {}) {

        this.ajaxObj = null;
        this.ajaxOptionsObj = ajaxOptions;
        this.attempts = 0;
        this.delay = 300;
        this.errorCallback = errorCallback;
        this.successCallback = successCallback;
        //add the settings to the ajaxOptions
        this.ajaxOptionsObj.url = requestUrl;
        this.ajaxOptionsObj.data = data;
        this.ajaxOptionsObj.error = this.ajaxError.bind(this);
        this.ajaxOptionsObj.success = this.ajaxSuccess.bind(this);

    }

    ajaxError(jqXHR, errorString, errorThrown) {
        console.log('error string ' + errorString + ' error thrown ' + errorThrown);
        if (this.attempts <= 3) {
            console.log('attempts is ' + this.attempts + ' delay ' + this.delay);
            setTimeout(() => {
                this.performRequest();
            }, this.delay *= 2);
        } else {
            this.errorCallback(jqXHR, errorString, errorThrown);
        }
    }

    ajaxSuccess(response) {
        this.successCallback(response);
    }

    performRequest() {

        this.attempts++;
        console.log('performing request ' + this.attempts);
        //prevent multiple requests
        if (this.ajaxObj) {
            this.ajaxObj.abort();
        }
        console.log('the ajax options ' + JSON.stringify(this.ajaxOptionsObj));
        this.ajaxObj = $.ajax(this.ajaxOptionsObj);
    }

}

export {EZAjax};

This makes it easier to work with. I just create a simple fail and success function and pass the names in like this.

let ezAjax = new EZAjax(emailExistsError, emailExistsSuccess, checkEmailUrl, data);
ezAjax.performRequest();
Testing inside Javascript

The below code goes inside a class you place inside app\assets\javascript\ directory I called mine registration.js  Below is a snippet of the code for checking if the email exists.


//code for comparing email fields before submit and make sure the email doesn't exist
$(emailMatchField).focusout(function (event) {
    let email = $(emailField).val();
    let emailConfirm = $(emailMatchField).val();

    if (email !== emailConfirm) {

        $('#noEmailMatch').remove();
        $(emailMatchField).css('background-color', redBgColor);
        let matchHtml = '<small id=' + emailMatchErrorId + '" class="red-text" >Email fields must match</small>';
        $(emailMatchField).after(matchHtml);

    }
  if (email === emailConfirm) {
        let data = {"email" : email};
        // if the email fields match make sure the email does not exist in the system
        let checkEmailUrl = Routing.generate( 'email_exists');
        let ezAjax = new EZAjax(emailExistsError, emailExistsSuccess, checkEmailUrl, data);
        console.log('the route is ' + checkEmailUrl);
        ezAjax.performRequest();
        $('#noEmailMatch').remove();
        $(emailMatchField).css('background-color', whiteBgColor);
    }
});

The first if statement checks if the fields match if not it shows an error. The second if statement checks if the email exists only if both fields match first.

The emailExistsError function will open a dialog to inform the user the email they entered exists and give them a login link instead. emailExistsSuccess does nothing I could just put function() in it’s place if I wanted.

Notice this line though

let data = {"email" : email};

That is the data we send via the EzAJAX class. That is the value we need in the controller.

The Symfony controller

Now we need to be able to work with the values sent in the request.

public function checkEmailExists(Request $request)
{
    $json = array();

    if ($request->isXmlHttpRequest()) {
        //check if the email exists
        $email = $request->query->get('email');
        $email = DataSanitizer::sanitizeEmail($email);
        $exists = $this->getDoctrine()->getRepository(User::class)->checkEmailExists($email);
        if($exists){
            $loginUrl = $this->generateUrl('app_login');
            $linkAttr = array(
                AnchorTagAttributes::HREF_ATTRIBUTE => $loginUrl,
                GlobalHtmlAttributes::CLASS_ATTRIBUTE => 'h5'
            );
            $loginLink = AnchorTag::getAnchorTag('login', $linkAttr);
            $json['error'] = "A user with this email exists. Please $loginLink instead. Or use another email.";
        }
    } else {
        $json['error'] = $this->illegalRequest;
    }
    return new JsonResponse($json);
}

To get the value sent via AJAX in the controller I use:

$email = $request->query->get(’email’);

Which matches the value I sent in the data JSON object to this controller.

As you can see in the javascript I am passing a value for email and in the controller I am fetching that value. I didn’t add any mime type info to the request, so the default for jquery ajax is 'application/x-www-form-urlencoded; charset=UTF-8'. To change the MIME type set the contentType on the ajax method.

Also note that this way of working with an Ajax request in the controller is not secured. Anyone from any IP can send a request to this, which can flood the server or allow them access to things they shouldn’t be allowed to access.

Soon I will dig into how to best secure the Symfony Contoller routes and write an article about that.

Categories
rants

Why Amazon search sucks and how it can be fixed

Well, someone had to say it!

This is about more than just amazon search sucking. It is about the entire tech industry and the assbackwards hiring practices they are using. This started out as a simple Amazon search sucks article, until I decided to dig into their hiring process. I then discovered their hiring process to find experienced engineers is as assbackwards as assbackwards  can be. And this is something plaguing the entire industry, which is why shit doesn’t work anymore. Bank logins, shopping carts, paying bills online…. you name it.

Update: The search is so useless I’ve started using other websites to find things. It is like Amazon takes pride in Amazon Search sucking major ass so badly. Search is pretty much 100% FUBAR on Amazon at this point. I recently wanted a graphics card. I tried Amazon but the search is so beyond broken it is pathetic and useless. SO SCREW AMAZON.

Amazon search sucks! Why? Well a video is worth more than 1,000,000 words.

First off note the difference in result count, 690 vs 3000  when I choose to sort by price low to high. It seems like 3000 is a hard coded random number not an actual real result.

You will never ever be able to view 3,000 products, because this search is FAKE.

Also notice, once I switch to lowest to highest, all of the  sellers with their unrelated garbage pops up. So now you get page after page after page of non-relevant garbage results.

awesome sauce meme
Isn’t this just awesome

So if you don’t filter you get pages of randomly priced items in a random list. That is never helpful to me on any site EVER. That pretends like the price doesn’t matter. How can anyone find what they are looking for if they don’t compare prices? As prices go up, objects often get more features and are of higher quality. Plus what kind of sense does it make to have $38 next to or after $299 or vice versa?

Maybe I shouldn’t have tried the price range feature with the lower to higher filter? But why would I think that would work any better?

So now you get 7 pages of desiccant and humidifier parts instead of humidifiers.

amazon search meme
Amazons search algorithm is all like

Only 7 pages for you!!!

And don’t you just love how it stops at page 7? THAT IS 100% useless.  So now you get less results than if you didn’t filter, and the results you get are 100% UTTER SHIT. This is just bad programming and design.

Hell even if you don’t choose any of the filters, you get 7 pages of results. THAT IS IT. WTF? WOW REALLY GUYS?

Searching on Amazon is fun
How to fix this

I have no idea how Amazon is designed. It could be a technological mess of one piece added onto another in a patch quilt type manner. I am guessing that is the case after looking further into Amazons hiring practices.

It would be nice though if they could get these features working, since they have no real competition. I mean more than 7 pages of search results would be nice. You know show us all of the results.

Why show 3k search results but only allow pagination of 7 pages for a total of less than 400 products?

Makes sense right?

People have been complaining all over the internet about this, even in amazon forums. I have no idea why the hell they don’t address this BS.

Update

So a recruiter from amazon ( more like 10 LOL) contacted me on LinkedIn. No they didn’t know about this article, or my website even, nor did they care. They were looking for people with 10 to 15 years experience with various technologies and languages, like I have. I decided for shits and giggles to see what kind of BS they were doing to candidates during the hiring process.

OH WOW.

We can’t find any qualified employees

What an utter joke. I see why their search sucks and AWS products like CodeBuild give stupid errors like Error: 255 and no info.  The process Amazon is using, screens out experienced people.

What is the first part of the process for screening experienced candidates? Hackerrank bullshit brain teaser tests of course. Yeah you know the shit that not one soul on earth uses in a daily job FFS.

Hacjkerranks? Are you kidding me FFS?
In the real world people solve real problems!

Hackerrank might be OKish for finding new programmers right out of college. But it is absolutely horrible for finding experienced Software Engineers and developers. Why? Why you ask? Shouldn’t the experienced guy know all the stuff of the fresher and more? NO that is just not how the human brain works.  The human brain dumps information it doesn’t utilize on a daily basis.

If you don’t use something your brain dumps it. How many of you remember all the Algebra and other math from highschool? I bet you an 8th grader who makes good grades can kick your ass in Algebra  and basic math right now. Why? Because it is fresh in their minds because they are learning/using it and you are not.

So a Software Engineer with 15+ years experience is going to be 15 years removed from solving bullshit brain teasers. Furthermore to solve these utter horseshit brain teasers, one must be SUPER FAMILIAR with one single programming language to the point they can program any  moronic brain teaser algorithm with just the bare structures of the language, no libraries or frameworks and you can’t google or read any docs.

GTFO with that level of time wasting stupid.
GTFO with Hackerrank BS

Another reason an Experienced Engineer won’t be able to pass those moronic hackerrank bullshit brain teasers is, they have spent 15+ years doing real things, solving real problems. These real problems use different parts of a language and you often use libraries and frameworks to solve the problems easier. This further removes you from having to create algorithms on your own or even use parts of the language to do so.

For example. The Javascript code you need to use to solve the bullshit brain teasers, is nothing like the Javascript I write daily to listen for events and manipulate the DOM. Never once have I ever needed to search an array of 1 and -1’s to find a sub array that equals 1 when multiplied, then return the length of the sub array.Yeah not one  time in 16 years have I needed to do such useless moronic, college brain teaser  bullshit. Imagine that!!! I mean who knew that wasn’t some shit you need to do on a daily basis in web development or programming in general.

In short, you will never, ever, use most of that shit on Hackerrank in your life EVER.

sarcastic kid meme
DERP

And hackerrank teaches horrible coding practices.  All in one functions, instead of SRP. Short nonsensical variable names that don’t tell you shit of what they do. Instead of well, purposefully named variables that tell you WTF they do. I am sorry but a,b doesn’t tell me shit. How about length, width???? Hackerrank is teaching people to write horrible code, and others agree.

look a wizard meme
Am I a programming wizard?

Aha. Now I see why AWS CodeBuild returned horseshit instead of usable errors. And that is why I decided I had no use for AWS and started looking into the many other alternatives. I am not wasting my time with something that returns useless bullshit errors. That screams amateur programmer and that further screams “random bs bugs” But at least all their programmers can pass those Hackerranks they learned to program from FFS.

At least they can hackerranks

I can promise you that many software engineers, developers, programmers etc. with 10+ years experience have probably never even heard of Hackerrank, unless they have recently looked for a job. I hadn’t until this recruiter sent me to that horror site of bad coding practices. Why? Because I’ve spent the last 16 years working on projects, programming, web developing and having hobbies like learning electrical engineering, horticulture/gardening, living a life etc.  I don’t have time for that BS to be honest.

And most experienced Engineers, programmers, developers etc. Most people in all fields of employment, after 15 years have a busy life. So basically these hackerrank tests are a filter to filter out people who are over age 25 or who have a life. They have no other purpose than to discriminate.

One reason I can see that tech companies would do this is because young people fresh out of college usually have no life yet. Young people are naive and willing to work insane hours trying to get ahead or make more money. You people are willing to work for less than they are worth just for the sake of adding to their resume. Older people have a life. Older people don’t want to work way over 40 hours a week. Older people have families and hobbies and interests other than working themselves to death.

If you do the math, 150k a year isn’t worth the 80 to 100 hours  a week some of these companies will want you to work. The tech industry is one of the most horrible to work in due to this fact of them demanding more than 40 hours a week.

If you do the math,even if you make a whopping 150k a year, if you work 60 hours a week( 12 hours a day, 5 days a week) = 3120 hours a year, about $48 an hour.

Now compare this to self employment building web apps with WordPress. You can charge $60 an hour, work 40 hours a week and make about 125k a year. The trade off is you get more time to yourself to have a life, but only make a little less. Not only that, you can charge more an hour say $75 or $150 an hour depending on what you are doing.

And the best part is you don’t have to waste your time every few years when you want a new job, having to relearn all the algorithm BS and getting good at sites like Hackerrank etc. This means my knowledge continues to be valuable and  I can learn just what I need to make money. I can have more time for family, hobbies and life in general.

Even if I wanted a job, I wouldn’t stop all I am doing and spend many weeks learning Algorithms and playing on hackerrank learning very specific parts of one single language well enough to complete a brain teaser in a POS IDE that sucks major balls. I respect the effort and time it must have taken to create such an IDE in a browser, but it still sucks compared to our daily tools. Which leads to another point, the IDE.

Many of us who have been in the field of programming/web development etc. for more than 12 months PAY FOR OUR IDE.  We highly customize our IDE to each language we use, those of use who are poly programmers.

I have dyslexia my IDE tells me when I misspell words and even variable names. Because thisVaraible is not the same as thisVariable or thiVariable.

My IDE also helps me spot unmatching brackets and other language syntax errors and highlights that save me lots of time. Hell my IDE will even show me the documentation for a specific function I am about to use. Or even allow me to open up the code itself to view it myself. My IDE will even tell me when there are code conflicts such as ambiguity etc. These little things help and we get used to them.

mind blown

So basically asking a programmer who pays for an IDE to use some foreign, greatly inferior IDE, to perform a task they don’t do daily; is like hiring a carpenter who builds cabinets and trim work, to build you a wooden spiral staircase from memory, with a bunch of no quality, bottom end, Chinese tools you bought off ebay, that may or may not work properly, but hey they look the same.

The carpenter very well may have the skills to build the staircase, but may need to review the math and build templates first.  They will also have their very own, often very expensive set of tools to do the job.

Side note, I do carpentry too. LOL You should see the house I built my 3d printer. It even has framed little walls and door. It is 100% air tight, with an attached hose and fan that fit in a window to suck out the plastic fumes.

Programmer !== Software Engineer

Another reason most Senior Software Engineers probably won’t pass those tests is because they are not programmers. The Tech industry decided to mix up the concept of programmer and software engineer. A programmer programs. A Software Engineer can program, but probably spends most of their time reading. Reading Documentation about Software they are considering including in the project. Software engineers read things like tech RFCs like this.

Basically it is the job of a Software Engineer to understand system design overall and pick the parts the application will be built with, everything from the software like MySQL or Postgre to hosting and which language is picked for the project. The job of the programmers is to use the language that was picked by the Senior Engineer and use the softwares picked by the Senior Engineer etc. The Senior Engineer then also mentors and helps troubleshoot issues with the programmers.

The main value of a Senior Software engineer is not in their ability to perform valueless algorithm programming exercises. The value of a Senior Engineer is in their long time experience with technology and engineering software and all of the bugs, quirks and issues they have encountered and solved in their lifetime. That is the value of a Senior Software engineer and giving them bullshit hackerrank tests only eliminates them from your hiring pool MORONS.

Someone straight out of college might be able to quickly do a hackerrank, but will they recognize why a system is failing and be able to diagnose it? Only someone with prior experience with fighting Linux permissions would even know where to begin to even look for a solution. That shit is not taught on Hackerrank or in college.

I  iz serious admin. LOL

How to find experienced Engineers?

So how does one go about finding experienced Engineers and weeding out shitty programmers? Well to start with don’t funnel them through the same damn filter you are putting juniors/freshers through.

An experienced Engineer/developer will have previous projects they have worked on. Ask them about those projects. Read their resume. Visit their websites if they have any. Ask them how they would handle a theoretical situation like a server crash. Ask them to see some previous code they have in a github repository.  Ask them to refactor/rewrite some old code they have in a repository, this is probably the best way since you can see what it looked like, and how they upgraded it. Maybe even give a small on their free time project to complete. Something simple like fetch data from this api endpoint or send a request to the endpoint etc.

But finding Experienced Engineers/developers with 10, 20 or more years experience with Hackerrank is absolutely how not to find those very people.

This kind of BS with testing and working more than 40 hours a week etc. etc. etc. is why there is a shortage of Tech workers. More are realizing they can work for themselves. More are just switching to industries that suck less. Software Engineering/development sucks balls to be honest. Most of the tools, libraries, frameworks, software etc. suck and have shit documentation. Imagine fighting quirky software that isn’t documented properly daily, it really takes the fun out of programming/developing. Now imagine you are stuck in a job that forces you to work with a bunch of dysfunctional software for 12+ hours a day 6 days a week.

angry cat not going to happen
Lets work more than 40 hours a week.

The tech industry is literally one of the main industries that thinks it is normal to over work their employees more than 40 hours a week. GTFO with that toxic BS culture.