Yii2 with ReactJS

Hi folks,

I am in the process of creating an app in Yii2 and, once complete, I have the intention to move replace the frontend with a JS framework and I have been looking at using ReactJS.

Is there anything I should know before I attempt to do this? Will I run into any problems using ReactJS as my frontend and Yii2 as my backend? I am assuming I would be looking to implement a REST service on the backend.

Any and all thoughts/insight appreciated.

1 Like

I wouldn’t mess with making the frontend in yii if you intend on using react, you’d just be wasting time. We are currently converting our yii2 app to react for the frontend and backend. We only pretty much are only using yii for routing as is with a lot of custom services. We are using a pretty aws dependent architecture and our goals and what yii is don’t line up anymore.

Thank you for your input.

I was originally going to create the frontend in Yii2 simply so I had HTML/CSS to chop up and use in ReactJS but I will consider just doing the frontend in React.

Can I ask why you are only using Yii2 as a router now? Any reason why you are not using a model of a JS frontend with Yii2 as REST? It might help me better understand my needs.

Thanks you for your response. Very informative and has helped me zero in on the best solution for my needs.

Which elements of Yii2 have you retained in your apps? You’ve mentioned AR, routing & REST.

If you are implementing user-specific access control, are you still doing this inside of Yii2 or have you outsourced that to AWS too?

Are you implementing attack profiling such as OWASP/AppSensor?

Any particular reason you would remove the dependency on jQuery and Bootstrap?

BTW are you still maintaining a version of the yii\web\session as an application component modified to encapsulate the DynamoDB Session and it’s API?

1 Like

The session in dynamo is super easy to implement in Yii2 with the V3 AWS php-sdk.

Note we use IAM roles so the keys are not needed. You can change the namespaces and composer install the aws-php-sdk. Here is a nice little article about scaling sessions to over 1 million request per hour.

Main config




'components' => [

	'dynamoDb' => [

            'class' => 'app\components\aws\dynamodb\Connection'

        ],

        'session' => [

            'class' => 'app\components\aws\dynamodb\Session'

        ]

]



Connection File (‘dynamoDb’)





<?php


/**

 * DynamoDb Session class file.

 * @author Skworden

 */

namespace app\components\aws\dynamodb;


use Aws\DynamoDb\DynamoDbClient;


class Connection extends \yii\base\Component {


    /**

 	* Configuration for DynamoDB database.

 	*

 	* @var type string

 	* Default 'region'

 	*/

    public $region = 'us-east-1';


    /**

 	*

 	* @var type string

 	* Default 'latest'

 	*/

    public $version = 'latest';


    /**

 	* The DynamoDB database client.

 	*

 	* @var type instance of DynamoDbClient

 	* @var $db

 	*/

    protected $db;


    public function init() {

        $this->db = DynamoDbClient::factory(['region' => $this->region, 'version' => $this->version]);

    }


    /**

 	* @return DynamoDbClient

 	*/

    public function getDb() {

        return $this->db;

    }


}



Session - The create table is just there to make it easy. you could convert it to a public function or migration [size="2"]and call it[/size]




<?php




/**

 * DynamoDb Session class file.

 * @author Skworden

 */

namespace app\components\aws\dynamodb;


use Aws\DynamoDb\SessionHandler;


class Session extends \yii\web\Session {


    /**

 	* Name of the php session

 	*

 	* @var type string

 	* Default 'a'

 	*

 	* All sessions will be prefixed with this value.  This simply sets the

 	* php session_name() to this value;  It defaults to 'a' to save as much

 	* space as possilbe in the storage as well as the sending of data to and

 	* from AWS.  It's minimal, but every byte counts.

 	*/

    public $session_name = 'a';


    /**

 	* Name of the Yii flash parameter

 	*

 	* @var type string

 	* Default '__f'

 	*

 	* It defaults to 'f' to save as much space as possilbe in the storage as

 	* well as the sending of data to and from AWS.  It's minimal, but every

 	* byte counts.

 	*/

    public $flashParam = '__f';


    /**

 	* Name of table to store the sessions.

 	*

 	* @var string

 	* Default 'session'

 	*

 	* The name of the DynamoDB table in which to store the sessions.

 	*/

    public $table_name = 'session';


    /**

 	* Whether or not to create the session table.

 	*

 	* @var $createTable

 	* Default false

 	*

 	*/

    public $create_table = false;


    /**

 	* Name of hash key in table.

 	*

 	* @var type string

 	* Default 'id'

 	*

 	* The name of the hash key in the DynamoDB sessions table.

 	* If you change this value from it's default you will need to override the

 	* parent class 'compose' function to reflect this change.

 	*/

    public $hash_key = 'id';


    /**

 	* Lifetime of inactive sessions expiration.

 	*

 	* @var type integer

 	* Default 3600

 	*

 	* The lifetime of an inactive session before it should be garbage

 	* collected. If it is not provided, then the actual lifetime value that

 	* will be used is ini_get('session.gc_maxlifetime').

 	*/

    public $session_lifetime = 3600;


    /**

 	* Whether or not to use consistent reads.

 	*

 	* @var type boolean

 	* Default true

 	*

 	* Whether or not the session handler should use consistent reads for the

 	* GetItem operation.

 	*/

    public $consistent_read = true;


    /**

 	* Whether or not to use session locking.

 	*

 	* @var type boolean

 	* Default false

 	*

 	* Locking is disabled by default, since it can drive up latencies and

 	* costs under high traffic. Only turn it on if you need it.

 	*/

    public $locking = false;


    /**

 	* The strategy used for doing session locking.

 	*

 	* @var type string

 	* Default null

 	*

 	* By default the handler uses the NullLockingStrategy, which means that

 	* session locking is not enabled (see the Session Locking section for more

 	* information). Valid values for this option include null, 'null',

 	* 'pessemistic', or an instance of NullLockingStrategy or

 	* PessimisticLockingStrategy.

 	*/

    public $locking_strategy = null;


    /**

 	* Batch options used for garbage collection.

 	*

 	* @var type array

 	*

 	* By far, the most expensive operation is garbage collection. Therefore, we

 	* encourage you to carefully consider your session garbage collection

 	* strategy. Note: the DynamoDB Session Handler does not allow garbage

 	* collection to be triggered randomly. You must run garbage collection

 	* manually or through other automated means using a cron job or similar

 	* scheduling technique.

 	*/

    public $batch_config = [];


    /**

 	* Max time (s) to wait for lock acquisition.

 	*

 	* @var type integer

 	* Default 10

 	*

 	* Maximum time (in seconds) that the session handler should wait to acquire

 	* a lock before giving up. This is only used with session locking.

 	*/

    public $max_lock_wait_time = 10;


    /**

 	* Min time (µs) to wait between lock attempts.

 	*

 	* @var type integer

 	* Default 5000

 	*

 	* Minimum time (in microseconds) that the session handler should wait

 	* between attempts to acquire a lock. This is only used with session

 	* locking.

 	*/

    public $min_lock_retry_microtime = 5000;


    /**

 	*

 	* Max time (µs) to wait between lock attempts.

 	*

 	* @var type integer

 	* Default 50000

 	*

 	* Maximum time (in microseconds) that the session handler should wait

 	* between attempts to acquire a lock. This is only used with session

 	* locking.

 	*/

    public $max_lock_retry_microtime = 50000;


    /**

 	* Initialize the client.

 	*/

    public function init() {

        session_name($this->session_name);

        SessionHandler::fromClient(\Yii::$app->dynamoDb->db, [

            'table_name' => $this->table_name,

            'hash_key' => $this->hash_key,

            'session_lifetime' => $this->session_lifetime,

            'consistent_read' => $this->consistent_read,

            'locking' => $this->locking,

            'locking_strategy' => $this->locking_strategy,

            'batch_config' => $this->batch_config,

            'max_lock_wait_time' => $this->max_lock_wait_time,

            'min_lock_retry_microtime' => $this->min_lock_retry_microtime,

            'max_lock_retry_microtime' => $this->max_lock_retry_microtime,

        ])->register();

    }


    /**

 	* Creates the database Table

 	* Make it public and you can call it.

 	*/

    private function createTable() {

        try {

            \Yii::$app->dynamoDb->db->createTable([

                'TableName' => $this->table_name,

                'AttributeDefinitions' => [

                    [

                        'AttributeName' => $this->id,

                        'AttributeType' => 'S'

                    ]

                ],

                'KeySchema' => [

                    [

                        'AttributeName' => $this->id,

                        'KeyType' => 'HASH'

                    ]

                ],

                'ProvisionedThroughput' => [

                    'ReadCapacityUnits' => 10,

                    'WriteCapacityUnits' => 20

                ]

            ]);

            echo "Successfully created the session table {$this->table_name}.  It may take a few minutes for the table to be ready to be used with session.";

        } catch (\Exception $e) {

            echo "The session table {$this->table_name} already exsist or could not be created.";

        }

    }

    


    /**

 	* @inheritdoc

 	*/

    public function getUseCustomStorage() {

        return true;

    }

}



Mate, I can’t thank you enough for your input. You are clearly on the same road as me, just many steps further down it. A few years back, it was so much easier to understand needs as there was a lot less technologies to choose from. Now, it’s not so easy but you have definitely made a hell of a lot clearer for me.

I would certainly never roll out a site with a full implementation of Bootstrap as obviously most cites wouldn’t make use of much of the code. I am split up Bootstrap with .scss before and ran Gulp to compile but the leaner I can go with CSS, the better.

An interesting observation about jQuery, and certainly something I am going to pay attention to when I am implementing JS.

Regarding your last paragraph… “good enough”. But in certainly in this app I am working on, it won’t be good enough unless it’s is very fast, secure and scales well.

I’ve played around with ReactJS before and understand it quite well I think on a technical level but I really need to get my head down and start developing in it.

Thanks for the code examples too. Something I will be referring to as I role out the development.

I’ll go off now and fill in any gaps in my knowledge regarding the sort of stack you are using

You’re welcome. We are currently implementing a layer on top of our rest using graphql. Qraphql is pretty awesome. We are currently working with Apolo and it seems to be the right fit for us. The reason for us using Apolo and not just straigt react is because it has implemented a few rfcs and features that react is proposing, until they are given the go ahead it won’t be in react. For example subscriptions… Facebook already uses them but they aren’t approved for the public release yet. You should check them out as they can save TONS of network traffic and resources which is vital when paying for a third party hosting that you pay based on bandwidth/calls.

I’ll be the first to admit but go a little overboard but we save money wherever we can even if it’s fractions of pennies. Like in the session example above we name flash __f instead of __flash (default yii). But when sending __flash instead of __f on every single request for every single user it adds up eventually. We use a lot of different servers so the traffic goes through our Route53 -> load balancer -> s3 servers / dynamoDb which we get charged for at each level for the same request. [size=“2”]Technically we removed the flash param from ours completely because we don’t use it but you get the idea.[/size]

One last recommendation is to use Nginx with google pagespeed module.

Lots for me to get stuck in there. Thanks again. Lots to learn but very exciting.

Can I ask if you are implementing a full duplex/WebSockets bidirectional system and, if so, how you are doing it?

re: Bootstrap. I might have missed this, but what are you using for .css? I was still considering Bootstrap CSS & JS stripped down to the bare bones of what I need.

We normally used Skeleton Framework, Pure CSS, or a custom one. Both of those are smaller than any stripped version of bootstrap we used.

As far as websockets we have only just did some prototyping with them. we were going to use them for a messaging system but it appears the graphql subscriptions might be a better fit for us.

Here is my company’s site i did, it uses skeleton and yii2. It’s all vanilla js except the contact page, i also use barba js instead of yii’s pjax. [size=2] I don’t know where you’re [/size]located[size=2] but it loads pretty fast in the USA [/size]initially[size=2] and on page transitions…it’s deployed on AWS only in their [/size]Northern[size=2] Virginia data center. [/size]opready.com

Skeleton looks like the perfect CSS framework for me as all I really need is a basic grid and a few buttons.

Nice website… I am in the UK and it loads fast here.

Thanks again… you have been a massive help in my decision making regards this new app.

Lots for me to get stuck in there. Thanks again. Lots to learn but very exciting.

The link below is a talk from the 2015 PHP UK Conference about using ReactJS as a the view component in a PHP MVC.

https://www.youtube.com/watch?v=YYt9u4uUetU

skworden,

Can I ask how you set up routing/config with your AWS/ReactJS/Yii2 REST apps? As mentioned here I have built a ReactJS app which I am now attempting to connect to a Yii2 backend as REST.

Thanks.

Just wondering what your thoughts on AWS API Gateway are? I am nearing completion stage for the ReactJS app and have been studying a lot of AWS. As per your recommendation, I am looking to integrate CloudFront, Cognito, DynamoDB sessions but I am not also considering removing as much of the REST/API functionality from Yii2 as I can and outsourcing it AWS API Gateway where possible. The idea of outsourcing the likes of verb filtering etc to AWS appeals to me as I have already reduced the core Yii2 framework I am using considerably and I would like to end up with as lean an application/framework as I can by the time we go to production.

react library and framework not many while vue have powerful quasar framework and more library than react

Sean,

Following advice you gave me earlier, I too moved over to the AWS stack you mentioned there. Moving from PHP to a full JS stack was incredibly hard work - as you mentioned, a very steep learning curve (very steep indeed!) but it was worth it in the end.

As I had mentioned to you earlier on PM, I had originally started with Redux but moved to Apollo client following your advice and I am ever so pleased that I did. Redux seemed at the time as such an amazing piece of technology then Apollo comes along and suddenly Redux is obsolete (local state, automatic caching/cache updating, graphQL ease of use).

ReactJS + Apollo + AWS is a Godsend for me. A HELL of a lot to learn… many a late night and I was on the verge of actually pulling my hair out many a time but so worth it in the end. The beauty of being able to put together a JS app where both the clientside and server app can use the same code for validation etc just makes life so easy. Example: -

You have a model with a field that has a max length. You can just set a constant somewhere in your app and import it as required for html maxlength input fields, client-side validation, client-side error messages, server-side model validation and server-side error messages. To change all those things, you just need to change that 1 const and you are good to go (providing of course you server-side model and database (if an active record) reflect this.

I ended up creating a stripped-down CSS framework with basic padding, margins, font sizes etc as well as using sanitize.css. I use styled-components exclusively aside from simple css classes for color, background-color, padding, margins, font-weight etc. Einstein said “The definition of genius is taking the complex and making it simple.”. Well, I don’t know that I am Einstein and it took a took a lot of work but I ended up with a very simplified system where I can make rapid changes through highly-reusable DRY coding using simplified CSS and styled-components extended styles etc. As an example, I have a simple Badge component to act as a standard badge/label, which is then inherited by my Button component to add Button-specific styling.

import styled from "styled-components"

const Badge = styled.span`
  display: inline-block;
  font-weight: ${props => props.theme.font.weight.normal};
  padding: ${props => props.theme.components.forms.padding};
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  border: 1px solid transparent;  
`;

export default Badge

import { Badge } from '_components'
import styled from "styled-components"

const Button = styled(Badge.withComponent('button'))`

  user-select: none;
  cursor: pointer;
  outline: 0px;

  &.disabled, &:disabled {
    opacity: .65;
    cursor: not-allowed;
  }
  transition: all .15s ease-in-out;
`
export default Button

I can then use the Button component anywhere in the app, styling the color and background with the few CSS classes I use (injected via styled-components).

<Button className="bg-blue bg-hover cl-white" type="submit">Submit</Button>

import ThemeStyles from "_css/themes/main/ThemeStyles"
<ThemeProvider theme={MainTheme}>

import colors from "./variables/colors"
import styled from 'styled-components'

const ThemeStyles = styled.div`

  // BACKGROUNDS 
  .bg-blue {
    background-color: ${colors.blue}; 

    &.bg-hover{
      &:not([disabled]):not(.disabled):hover{
        background-color: ${colors.blueHover};
        border-color: ${colors.blueHoverBorder};
      }
    }
  }

  // COLORS
  .cl-white{color: ${colors.white}}
`

export default ThemeStyles

Many of my base components are based on Bootstrap 4 but, as mentioned above, I have a very minimal CSS implementation. I have a similar implementation for Input and TextArea that both inherit from a common FormControl parent, while (obviously) changing the html element type.

I still have a soft spot for PHP as it was my first love haha but I have almost no need to use it now. Actually, I only use PHP in Yii2 migrations for my current project which otherwise is entirely JS. I found JS based migrations (Sequelize) to be a PITA so I just used Yii2 but I will probably eventually move all that over to JS, too.

Sean - thanks again for the advice you previously gave me both on this thread and on PM, it was a game-changer for me. Boy oh boy was that learning curve steep and (at times) INCREDIBLY hard work but very, very worth it in the end!!! It’s just a joy to be able to develop at this speed, all using a single language (Javascript).

2 Likes