Friday, March 5, 2021

Ohhhh. So That's What Autowiring Is.

 I finally had that 'eureka' moment with respect to the Slim 4, PHP-DI and autowiring.

After roughing out the controllers for Mabel I realized I had a small problem in that the api didn't return a 404 response when a uri was sent that didn't exist.

Like, if the client asked for /foo/bar the api would return a 200 OK response. That's all sorts of wrong.

So I started to dig into the documentation a bit trying to figure out how to implement the correct behavior. As part of my research into that, I ran across the following blog post on error handling in Slim 4. 

That post contained a link to the slim-skeleton github repository, which I found pretty intriguing. In particular I really liked how it manages dependencies and routes. Up until now I'd put all the code loading up the container straight into docroot/index.php, but I've never liked that. Seeing an alternative approach that I did like, I just copied it.

I also really liked how each endpoint corresponded to a separate class with a single action() method.  I feel that writing unit tests will be more straightforward for code structured this way and I also like how the file structure of the project literally tells you where to find each method for each endpoint. So I copied that too.

I still wasn't fully understanding how the implementation worked though. Looking at the parent UserAction class wasn't much help. Looking at the parent Action class was, however, something of an eye opener. At some point Slim 4 must be executing the ListUsersAction class (or any other class that extends Action) as if it were a function. That was triggering the __invoke magic method, and when that happened the Request and Response objects and the request arguments were being passed in.

After I figured that out I assumed I'd go to app/routes.php and see where all the dependencies were being instantiated and passed to the constructors of the various action classes. Only that's not what was happening at all. Nothing was happening. The route was being defined and linked to a specific class that wasn't even instantiated. 

It was as if . . . ohhhhhhhhhhhhhh.

Slim 4 is using reflection to determine whether a defined route is being linked to a closure, a class or the method of a class. It's then executing that closure, class or method as a callable and passing along the same three parameters every single time. 

And if Slim 4 is doing that, it must also be using reflection to inspect the constructor of each class it's asked to instantiate and using the type hinting of the constructor parameters to search the container object for the corresponding closure that provides that type hinted class in its instantiated form.

So that's what auto-wiring is.



Thursday, March 4, 2021

Let's Add Some Controllers To Mabel

And now it's time to build out the actual api for Mabel.

Looking at the documentation for Slim 4, I'm going to allow Slim 4 to instantiate my controller classes for me. This allows me to write the controllers in a manner that's similar to what I've done in the past. 

Namely, I'm going to create controller classes that contain methods that are related. In other words, I'm going to create three controllers: Authorization.php, Accounts.php and Anonymous.php.

Then I'm going to add routing to docroot/index.php that explicitly links each URI path to a specific class and method that should be invoked.

Somehow, Slim 4 magically passes the Request and Response objects and the request arguments to each method in each controller. 

I'm also magically able to have the Container object passed into the constructors of the controllers and that's how I'll get the dependencies I'll be using in the controller. I don't really understand yet how any of this magic happens (which I'm never comfortable with) but I suppose that's not important right now.

There's something called auto-wiring that's being utilized, or at least it's an option, because I'm using php-di as my container. I don't understand what that is or why you'd use it. The php-di documentation really talks it up, but I prefer not to use something I don't yet understand.




A Couple Of Things I Learned About Unity

I learned a couple of simple things about Unity while putting together the basic pre-game UI.

1. Unity is very easy to use

It's VERY easy to construct simple game elements in Unity. Adding the visual game elements, writing code behind those elements and wiring the two up is very straightforward. I had expected that prototyping the pre-game UI would take a whole week. That I was able to do it in less than two was quite a shock. I suppose it helped that I've already worked through two tutorials that both took something like a full day of work to get through. I should point out that I haven't actually written the code that sends requests to the API and handles the responses from it. I expect that will take quite a bit of time.

2. Panel UI elements are your friend

When you need to show or hide groups of UI elements the recommended (according to a forum post I agreed with) way to do that is by containing elements in panels and showing or hiding the panels. I'd already sort of started putting the UI together like that when I came across the guidance. Grouping elements by panels also makes it a lot easier to structure the layout and ensure that it generally continues to work if the user operates at a different resolution than you do.

3. Unity is magical. Use that magic.

I ran into some problems when I started to actually write the code that made specific elements show or hide (you're actually calling GameObject.IsActive(bool newState) method to do this).  There are methods in Unity for finding the child objects of UI elements by name but I wasn't looking forward to writing a bunch of "traverse tree structure for child element with name" code. Luckily, I came across a related question in the forums that finally made an aspect of Unity click for me in a way that it hadn't before.

In Unity, you can write a class in C# and then add that class as a component to a game element in the UI. If you add public properties to that class in code, those properties magically appear in the game design UI as fields of the component. You can then drag other game elements of the UI to those fields. References to the corresponding game objects then become available to the C# code. 

It's freaking magical.

As is Google searching.


Wednesday, March 3, 2021

Towards A More Advanced User Interface

Setting up the UI elements in Unity was pretty straightforward once I knew the structure of the API that the client was going to be communicating with.

Here it is in action. I should point out that the client isn't actually wired up to Mabel yet as the endpoints don't currently exist.

It's not pretty. I'm not an artist and I plan to generally ignore the aesthetic elements unless I'm inspired or have no other choice.

Defining The API Endpoints

After figuring out the basics of how to build the pre-game portion of the Unity application, I spent some time in front of the whiteboard (in reality it's a cork board I pin notecards into) figuring out what Mabel's API will look like.

These are, from my experience, the endpoints you need for a minimalist user account management api:

POST    /authorization/login        unauthorized only
POST    /authorization/logout       authorized only
GET     /accounts/view              authorized only
POST    /accounts/create            unauthorized only
POST    /accounts/update            authorized only
POST    /accounts/passwordupdate    authorized only
POST    /accounts/passwordreset     unauthorized only
GET     /anonymous/news             no authorization functionality

For all endpoints except /anonymous/news, the API expects the presence of a special header to be present in that request. 

When the request enters the API, that header value will be used to look up whether or not that value is authorized. 

If the authorization state does not match what's in the third column, the request will be rejected and an error response returned.

If no header value is present or the value doesn't correspond to anything looked up by Mabel, a new header value will be returned in the response, even if it's an error.

The /anonymous/news endpoint isn't part of the account management api (which is why it won't implement the authorization system) but I thought it would be cool to have the ability to display 'content' on the pre-game portion of the app. I'll probably try to figure out how to get it to return a formatted version of Mabel's git log.


Tuesday, March 2, 2021

Well That Was Easier Than I Expected

So yeah, I didn't expect to get this first iteration done so quickly. 

At a VERY rudimentary level I've managed to create a client application in Unity. This client application has a some input fields and a button. When you click the button, the values are read from the input fields and then written to the console. After that, a request is sent to Mabel. Once the response from Mabel is returned and if it was successful, the response body is printed to the console.

Initially I found a short youtube video that was very helpful in just getting me pointed in the right direction in terms of how to create the UI.

The Unity documentation was pretty well structured but I didn't really have to rely on that very much yet.

When it came to writing the C# that handled the remote api call, I was able to find an online reference that allowed me to put together some very rudimentary code that worked almost immediately.

I'm definitely going to need to get my hands on modern references for C# and related best practices. It's been a while since I've written anything in it.

In particular, it looks like the C# libraries for handling remote api requests are very heavily tilted towards being asynchronous. This is fine (and good), but not something I'm extremely familiar with. I had to deal with this years ago when I wrote a basic nodejs server application, so I'm comfortable with the concept. I just don't know how the C# syntax for this fits together and what the best practices are. Something new to learn, I suppose.

Next step is to flesh out in more detail what the client application needs to do with respect to starting the game for the first time (new account creation, etc.) and what that implies with respect to the C# code that supports it.

Monday, March 1, 2021

Connecting To Mysql 8

I actually instanced the MySQL 8 database I'm planning to use via RDS back in early November. The security groups for the DB are setup such that it cannot be accessed by anything outside of my VPC. Both the http server and the bastion host can connect to it, but any manual SQL should only be executed from the bastion host.

To get this all up and running there were a couple of things I needed to do.

  1. Update the service ini file so that it contains the correct credentials for connecting to the database.
  2. Install the myself command line shell on the bastion host so that I can manually execute SQL.
  3. Execute said SQL in order to create the necessary schema, user and table.

The first step was easy. I did run into a problem when I attempted to setup an alias for the DB host via the /etc/hosts file. For some reason that didn't work. Some quick googling suggested I check my file permissions, but as expected, the file is globally readable. Oh well. I just ditched that idea and moved on.

The second step was . . . more complicated. Amazon linux extras doesn't come with any mysql topics. So, I had to do some research to find a yum repository that contained what I needed and then figure out (as a yum n00b) how to actually make it so that the repository in question can be accessed.

First I found some AWS documentation that led me to believe that Amazon Linux 2 is roughly compatible with RHEL 7.

Then I found some mysql documentation that seemed to imply that if I downloaded and installed the correct rpm package, I'd be able to access a yum repository for MySQL software that's compatible with RHEL 7.

Once I did those two things, in theory I'd be able to find and install a version of the mysql client for MySQL 8 that would work on my bastion host.

And that turned out to be the case.

After that, I ran the SQL to setup the schema, user and table and VOILA my new API is fully functional (such as it is).

It doesn't do much yet, but I'll be able to use it to develop the foundation of the client application.