Category: PHP

Moving from Vagrant to Docker in an easy way.

Docker - An open platform for distributed applications for developers and sysadmins.

 

 

 

 

Wow it has been a long time since I did a blogpost. However I had this one coming as I got asked about how I used Docker in my current workflow. I used to be – and still am – a big Vagrant fan, however Vagrant is not very suited for development. It is great for managing and provisioning vboxes. But a vbox is sluggish and a resource hog. Vboxes weren’t meant for development, they were meant to run a complete OS on top of yours in a contained fashion.
If you need to run your software on something other than linux, then I still hearty recommend Vagrant for managing those boxes.
If however your website is running on a linux system, then you can leverage the power of Docker.

This blogpost will show you how to quickly setup a Debian environment running Apache, PHP and a database (Postgresql).

Docker Debian Images

head over to https://github.com/NickBelhomme/DockerImages and download and extract the repo.
Alternatively for those who work with git you can

git clone git@github.com:NickBelhomme/DockerImages.git

Once you have the package extracted or cloned just step right into the directory and follow the README.md on how to build the images we need for this post. https://github.com/NickBelhomme/DockerImages/blob/master/README.md

Using the Docker images

You should now be the owner of 3 images.

nickbelhomme/postgres latest 3d09510dab44 2 hours ago 271.3 MB
nickbelhomme/apache latest f6eb10d32f1c 2 hours ago 223.6 MB
debian wheezy 29853cd4f422 42 hours ago 85.19 MB

You can ignore the hashes, these are random at build time.

So how do we use it?

The first thing we need is 1 additional project or projects if you are a busy bee. In this blog post I have included one demo project.

issue the following command:

cd application
docker build -t nickbelhomme/application .

you now end up with 4 images including a

nickbelhomme/application latest 7d4f26227028 33 minutes ago 223.6 MB

If you take a look at the Dockerfile you just issued a build on, you notice we use our previous nickbelhomme/apache image
as a start.
The only thing we apply on top of this image is an extra vhost setting and we enable it.

The Magic of Apache + PHP

Now that you can spawn as much images for each project you have, it is time to at least use one.

docker run --name test1 -P -d nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND

This will start a Docker container with the ports exposed and made public, in this case port 80.
Exposing a port makes it available for inter-container communication and making it public allows your host system to contact the container processes within. We have exposed port 80 in our main apache Dockerfile.

The -d tells the container to run daemonized, which just starts the process – in our case “/usr/sbin/apachectl -D FOREGROUND”
and puts it in the background. Docker -d itself needs to have foreground processes (not deamonized), hence us starting apache in the foreground.

You should just get back a hash. This means your docker container is running. Issue a

docker ps

to see on which port you can access your website.

 

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f718f791dd5e nickbelhomme/application:latest "/usr/sbin/apachectl 2 seconds ago Up 2 seconds 0.0.0.0:49159->80/tcp

 

this tells us the container its apache port is available through our host on 49159.

Before heading to the browser and issuing a http://local.application.com:49159 we need to add it to our /etc/hosts file.

to get the proper ip address to link to use a ifconfig and you will see the
Docker0 adapter with its IP address. Use that instead of 127.0.0.1.

On Mac or Windows you are using Boot2Docker, so you will need to use the IP of the virtualbox adapter.

Now head over to the browser http://local.application.com:49159 and you will see something such as:

Not Found

The requested URL / was not found on this server.

Apache/2.2.22 (Debian) Server at local.application.com Port 49159

Success!!! you reached Debian server.

But wait this is not what we wanted. Where is my frigging site???!!!!

Patience is the mother of all things.
Let’s issue ourselves a new container this time mounted with our site:

docker run --name test2 -d -v /home/nick/vhosts/Docker/application/app:/var/www/vhosts/local.application.com -P nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND

With the -v option we mount a local directory inside the container. You must provide absolute paths. The syntax is [localDir:containerDir]. In our application.conf we have set the document root directory to /var/www/vhosts/local.application.com/public so we mount it one level up.

Issuing a docker ps again will show us on which port to connect this time: http://local.application.com:49160

You will see:

SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host "localhost" (::1) and accepting TCP/IP connections on port 5432? could not connect to server: Connection refused Is the server running on host "localhost" (127.0.0.1) and accepting TCP/IP connections on port 5432?

This is normal as we haven’t spawned our database container yet.

Lets do that now!

The magic of the Database Postgresql

docker run -d -P --name postgres nickbelhomme/postgres

As you saw we didn’t have to specify a foregound process with this docker run. That is because we have specified the default command to run when starting the container in our postgres Dockerfile.

once this is up and running we can use it to connect it to our application container

docker run --name application -d --link postgres:postgres -v /home/nick/vhosts/Docker/application/app:/var/www/vhosts/local.application.com -P nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND

 

We connect 2 containers with the –link param. [containerName:alias]
Now you see why we use the –name param when doing a docker run. More use cases you’ll see later.

Going to the browser again on the new port we will see:

SQLSTATE[08006] [7] FATAL: password authentication failed for user "nick" FATAL: password authentication failed for user "nick"

 

Which is rather awesome because that means you made a connection. We didn’t setup the user nor the database yet but that we can arrange soon.

Let me first explain why the connection worked out of the box.
Docker uses exposed ports for inter communication it also uses ip addresses between containers. These settings are made available through the –link [containerName:alias] system. The linked container now has access to ALIAS ENVS such as:

POSTGRES_PORT=tcp://10.66.33.4:5432
POSTGRES_PORT_5432_TCP=tcp://10.66.33.4:5432
POSTGRES_PORT_5432_TCP_ADDR=10.66.33.4
POSTGRES_PORT_5432_TCP_PORT=5432
POSTGRES_PORT_5432_TCP_PROTO=tcp
POSTGRES_NAME=/grave_elion/postgres

and you can use them in your application. You can find such envs by issuing a

docker run --rm --link postgres:postgres -P nickbelhomme/application env

here you are not running a container in deamonized mode but just issue an env command against the container and when finished you
throw the container away (–rm)

We however want to go inside a postgres container so we can create a user and database.

 

docker run --rm -t -i --link postgres:postgres nickbelhomme/postgres /bin/bash

 

This opens an interactive (-i) / pseudo-tty (-t) bash in container throw away mode

Inside we can issue postgresql specific commands

createdb nickdb -h $POSTGRES_PORT_5432_TCP_ADDR -p $POSTGRES_PORT_5432_TCP_PORT -U docker

 

psql -h $POSTGRES_PORT_5432_TCP_ADDR -p $POSTGRES_PORT_5432_TCP_PORT -d docker -U docker --password

 

CREATE USER nick WITH PASSWORD '1234';
ALTER database nickdb owner to nick;

Again here you can see the env variables being used. You can also type them yourself, but as long as you use the postgres alias the environment variables naming will remain the same.

The postgresql password for the user docker is docker. We have set this up in our postgres Dockerfile.

You can leave this terminal by leaving psql by typing

\q

and then the terminal by

exit

 

After you have created the DB and granted the user access it is time to visit the application again.
go to your browser tab of the container named application and refresh.

“no couples table” is displayed on the screen.
Awesome.

Lets populate it.
head over to
http::local.application.com:[application PORT]/install.php
and then go back to http::local.application.com:[application PORT]

you should see:

male: nick woman: chanie
male: pixel woman: cookie

Stop working

You do not need to remove the images nor the containers.

To stop working we simply stop the running containers to safe resources.

docker stop postgres
docker stop application

or simply

docker stop postgres application

Start working on the project again

docker start postgres
docker start application

or simply

docker start postgres application

Environment vars

each time you stop and start a container you will get new values for POSTGRES_PORT_5432_* so
if you hardcoded the ip instead of using the getenv technique you have to use the trick I showed you on how to get the env variables.

 

Removing Images and Containers

When you do not need a project or container anymore you can remove containers by name or hash

docker rm postgres
docker rm application

Or the image

docker rmi nickbelhomme/application
docker rmi nickbelhomme/apache
docker rmi nickbelhomme/debian
docker rmi nickbelhomme/postgresql

when you remove all the postgres containers you also remove all database data. To see where the data is stored issue a docker inspect postgres and see the volumes part.

 

Creating a clean slate

 

remove all containers and all images

docker rm $(docker ps -aq)
docker rmi $(docker images -q)

 

Windows or OSX

Mac and windows use boot2docker.

As stated above in the post, you need to use instead of the docker0 adapter the ip used for the virtualbox machine which is 192.168.59.103. So put that in your hosts file.

Ever since 8 September 2014 guest box additions are included in the Boot2Docker machine so you can find your users folder in /Users for mac and /c/Users for windows.
If you want to mount different folders as well you can use the virtualbox gui to mount additional folders. (vbox/settings/shared folders). Once you have mounted the folders you need you can use the vbox path (docker run -v /c/Users:somePath) to mount it to the container.

 

Happy Dockering ,

Nick Belhomme

My PHP Traits talk at PHPBenelux Conference 2012

This weekend (27-28 January 2012) I spoke again at the PHPBenelux Conference and what a sweet conference this is.
An excellent line-up, great organizing and a wonderful networking opportunity. You should attend in 2013, if you are still doubting here are the reasons what made 2012 so special.

Purpose of the entry

Writing about this conference makes my heart jump and grin from ear to ear. I am obliged to write about this event just to vent all the positive feelings I have about this conference. I hope I will be able to give you just a taste of what I am feeling.

Overview

  • What is #phpbnl12?
  • My talk PHP traits, treat or threat
  • Who was attending?
  • Pros and Cons of this year
  • Conclusion

What is #phpbnl12?

The PHPBenelux Conference is a two day event organized by PHPBenelux which is a non profit PHP usergroup oranization for the Benelux. They organize meetings and conferences for PHP developers and companies using PHP. These events contain technical talks, workshops and best-practice sessions to share and improve the knowledge among developers. And that is exactly what this yearly conference is about: workshops, best-practices and sharing knowledge among developers.
This year was the third installment of many more to come and was perfectly organized by the PHPBenelux Crew (professionalism for the win).
They managed to get together an excellent schedule full with talks ranging from beginner to advanced. Found some killer sponsors which in turn threw some spectacular after socials. Ibuildings organized a Belgian beer tasting – without the spit-bucket, so you can imagine how that went 😀 – and combined that with real Belgian fries. They tasted soooooo good. You had the opportunity to play the Kinect but also could get your hands dirty on some good old Bowling. I kicked the ass of Derick Rethans, David Zülke, Stefano Oldeman and Jeroen Keppens. Unfortunately got my ass kicked by (the lucky cheating bastard ;)) Juozas Kaziukenas in the last round. That was so exciting 123 – 116. Maybe next year dear reader you can challenge me!
The other great sponsor was Engine Yard (Orchestra) and they arranged a BBQ. Those guys know how to entertain and luckily for me they also thought about the vegetarians. I scored a free Engine Yard scarf – which was needed because it was a blistering cold – and scored couple of drinks from some awesome people.
Talking about these social events is important because a conference is soo much more than learning. Hell there is tons to learn but also tons of interesting people to meet.

My talk PHP traits, treat or threat

This talk is an invitation for you to finally embrace PHP5.4. Normally you should already master all the 5.3 features. Heck it has been around for ages to play around with in production. The stable release of PHP5.4 is just around the corner so you should already have been testing all your apps against it. If you haven’t come and see this talk why. It will throw in some new PHP5.4 features / syntax but will of course mainly focus on traits.

The talk in itself was packed with information based on real world examples. This made the slidedeck sometimes spill with code, which I will filter down to a more readable and thus understanding level (got some complaints about too much code confusing it all). The reason why real world examples are chosen is simple: the attendee gets immediate feedback on how and potentially where to use it. He/She sees the possibilities of the feature instead of some academic foo bar examples, for which they can read the manual on php.net. However even if they are more into academic examples they found my slides and presentation far exceeding that. Lets be honest the manual is lacking a lot of information on what is possible with traits. For this you need to really invest in the mailing lists and talk to the PHP core team. That information is in the slides. The talk doesn’t say traits are the holy grail nor that it is evil, I let the attendee decide. I show what is possible with this new language feature without any judgement, but do focus on some best practices and pitfalls.

Some quotes:

  • I think Nick did a great job and I learnt a lot of new stuff.
  • I liked the fact that it was “real code” and not just hello world examples.
  • I really enjoyed this talk. Really looking forward to give 5.4 a try 🙂
  • This was a talk I was really looking forward to. Nick did a good job at explaining traits.
  • Very interesting talk; I learned a lot of new stuff and I’m definitely going to play around with traits a bit more

Who was attending?

Approximately 300 attendees with the “open source state of mind” at heart joined the conference. People who love to learn or lecture on everything programming related. It is about so much more than PHP. I urge you to join even if you are programming into a different language like .net or java. There is a wealth on information at this event and you feel the positive vibe of each and every attendee. The sponsors gave away some cool gifts too. ibuildings gave several tickets to DPC12 -another high rated conference- and an iPad2. Enrise raffled several Zend exam vouchers and an Apple Macbook Air (how f* awesome is that??).

Pros and cons this year

Pro:

  • As always excellent venue
  • lots to learn at great lectures
  • lots of awesome people who are happy to meet you
  • sponsored conference social drinks at the hotel => FREE DRINKS for everyone.
  • the food really went up a notch this year. This year warm meals were also available and they were Yummie.

Con:

  • 2 small rooms and 1 main room doesn’t cut it anymore for this conference. At times some of the small rooms were packed. I had to stand at some presentations and during my talk people litterly sat on the floor. I do not know the proportions, but if the room at a seat capacity of 100, then at least 40 extra sat on the floor in front of the chairs.

Closing notes

Great value for money and in general a conference you should attend, no excuses.
See you next year at The PHPBenelux Conference 2013

Thx for reading and feel free to comment,
Nick Belhomme

Zend Framework 2.0 Dependency Injection

Dearest Zend Framework and PHP Addict,

You know I always try to keep a close watch on the developments of Zend Framework 1.* and Zend Framework 2.0. Today I will blog about the Dependency Injection Component that is going to ship with ZF2.0.

What is dependency Injection?

You have been using dependency injection from the first time you started using classes. After this article you will have an ah-aaaah moment and you will finally be able to name the practice you have been doing all along. Almost each class has dependencies to other classes. You could instantiate these dependencies inside the class itself, making sure the dependency is always met, also avoiding the overhead of injecting it each time. While this seems a good idea in the beginning, it also tightly couples your application units. Also the quality is harder to guarantee because during unit testing your code has dependencies which are instantiated from inside the class to be tested. This makes it very hard to manipulate the state the dependency might have. The solution is to never instantiate the class from inside the class itself but from outside and let it be set as a parameter.
A good PHP resource on why you need dependency injection in PHP can be found at Ralph Schindlers blog article: Learning about Dependency Injection and PHP

What is a dependency Container?

Having to instantiate and pass the dependency all the time can be troublesome to say the least.
The first PHP solutions were to have a registry which would hold all the instantiated objects and only this registry would be tightly coupled in your architecture. This went a long way but still tightly coupled your architecture.
To remedy this people started looking into a good component which could really hold dependency definitions – a configuration which holds all classes / methods and their dependencies. And would also hold an instantiator, and container. The Instantiator would instantiate the objects for you according to the definition and a container would hold previous instantiated objects.
A good article written by Padraic Brady and published in 2008 pens down this idea and proposes a Zend Framework Dependency Injection Component, or simply Zend_Di: The Zend Framework Dependency Injection And Zend_Di

The Current Day: Zend\Di

Zend Framework 2.0 is currently under full development, several milestones have been completed. Probably the most important milestone of ZF2.0 is the MVC. To be fully compliant to current standards a good Dependency Injection (DI) component had to be developed.

_Why_ does ZF2 need DI for the MVC?
The reason is that it’s a good potential answer to the question of “how do controllers gain access to their dependencies?” In ZF1, the answer was either indirectly via Zend_Registry or the Bootstrap together with getInvokeArg()), or via direct instantiation within the controller. The latter is problematic as you can’t easily provide substitutions, and if the class instantiated needs to interact with resources — say, a web API, the database, etc. — this becomes difficult to mock when testing. In the former cases, you’re introducing undocumented dependencies in the object — looking simply at the controller’s API, you cannot easily guess what other objects it uses.

DI helps answer this. The prototype Zend Framework Core Team started building assumes that a DI container is composed into the front controller, and controllers are themselves defined in the DI container. When a controller is matched by the router, the front controller then pulls the matched controller from the DI container — along with all of its dependencies. This helps solve the initial question, but also answers another one: how to reduce the number of objects necessary for each given request. In ZF1, every single application resource is instantiated on every request, regardless of whether or not a controller uses it; the DI approach largely removes this, though at the expense of an in-memory graph of object definitions. However, the latter is usually smaller over-all, and easily cached in memory.

Ralph Schindler (Part of the Zend Framework Core Team) takes the lead on this component and listened to the community. He poured all those ideas in a component that can take prior configured definitions, detect dependencies at runtime and a tool for static dependencies analysis.
The approach that has been taken is that different environments will behave differently from a DI perspective. The solution is to consider anything but your local development machine production. Application in production should be stable from a code perspective and unchanging code means the deploy tool should be able to statically parse your code and create a definition file from it. This can be done automatically on each deploy. Once a definition file is ready – this will be in PHP format for maximum efficiency – your application doesn’t need to analyze your code on every request, thus gaining in huge performance benefits and without the hassle of always manually configuring all dependencies. Of course some dependencies will have to be manually configured, but Zend\Di offers tools for that.

Let’s take a closer look at the tools Zend\Di offers.

Zend\Di\DependencyInjector

This class will be instantiated most of the time. It will be used to retrieve instances configured by a definition.
For this you will need to set the definition object or manually configure everything piece per piece.

$di = new Zend\Di\DependencyInjector();
$di->getInstanceManager()->setParameters(
    'My\A',
    array(
        'password' =>'bar',
        'username' =>'foo',
    )
);
$c = $di->get('My\C');

In this example class C is instantiated. Class C has a dependency on class A, This is automatically behind the scenes detected by Zend\Di\Definition\RuntimeDefinition. Yet class A has constructor dependencies also, which can never be automatically injected so for this we need to set these parameters. This means that we need to aggregate all dependencies which cannot be automatically instantiated into a definition file. Even automatically detectable dependencies at runtime would benefit from a definition because compiling shouldn’t be done at runtime — it’s expensive. You will typically compile once, and then create a class extending ArrayDefinition that uses the results of compilation.

Zend\Di\Definition\Compiler

Creating a definition file asks a lot from a developer so it would be best that it could be automated as much as possible. For this a static compiler is implemented. Which has stated before should not be done at runtime during production.

$compiler = new Zend\Di\Definition\Compiler();
$compiler->addCodeScannerDirectory(new Zend\Code\Scanner\DirectoryScanner(__DIR__ . '/Game/'));
$definition = $compiler->compile();
$di->setDefinition($definition);
$commandParser = $di->get('Game\CommandParser');

$definition will be a complete Zend\Di\Definition\ArrayDefinition which accepts and outputs a PHP array based definition.
You can only have one definition at a time – hence the setDefinition and not an addDefinition api – so calling setDefinition() and passing an ArrayDefinition will result in no more Runtime lookups. If you want to combine the two strategies, you need to use an AggregateDefinition. An AggregateDefinition acts as a queue, querying each attached definition until it finds a match (or doesn’t!). As such, you would attach any compiled ArrayDefinition objects first, and a RuntimeDefinition object last.

Combinations of a definition and setParameters are also possible:

$di->setDefinition($definition);
$di->getInstanceManager()->setParameters(
    'Game\Grid',
    array(
        'gridSizeX' =>  2,
        'gridSizeY' => 5,
    )
);
$commandParser = $di->get('Game\CommandParser');

Parameters set through options or through the setParameters method are not only used for the __construct method but also for the available setters. This makes your code a bit hard to understand because you have no idea which parameter is used for what method. So parameters are nice, but the current incarnation also allows specifying per-method configuration for parameters that should be called at instantation (i.e. setter injection). In fact, it’s the preferred method for specifying configuration even for the constructor.

$di->getInstanceManager()->setMethods(
    'Game\Grid',
    array(
	'__construct' => array(
		'gridSizeX' =>  2,
		'gridSizeY' => 5,
	),
        'setPosition' => array(
		'x' => 1,
		'y' => 3,
	),
    )
);
$commandParser = $di->get('Game\CommandParser');

In the case where CommandParser needs to automatically accept a Grid object as a constructor parameter, you can even pass the constructor params for Grid directly to the get method of the main class:

$commandParser = $di->get(
    'Game\CommandParser',
    array(
        'grid' => array(
            'gridSizeX' =>  2,
            'gridSizeY' => 5,
        )
    )
);

The Injector will inspect the constructor params for CommandParser and if it doesn’t have a $grid param, than it will save the param for creating a params instance that might need it. In this case the Game\Grid $grid. This way you can pass params all the way down to the last dependency needed for instantiating the class you asked.

Or to make it even a bit more flexible you can even create aliasses and pass them as the second parameter.

$di->getInstanceManager()->addAlias(
    'my-Grid',
    'Game\Grid',
    array(
        'gridSizeX' =>  2,
        'gridSizeY' => 5,
    )
);
$commandParser = $di->get('Game\CommandParser', array('grid' => 'my-Grid'));

Aliases are intended to make it simpler to specify dependencies and/or substitutions. As an example, I might alias Game\Grid to “my-Grid“, allowing me to call $di->get(‘my-Grid’) and retrieve that class. This means that I could potentially configure the container differently, and get an object of a different class later — which is very useful for testing, or having different adapters for different environments.

So this is basically the standard usage of the Dependency Injector. Of course the DI is still BETA, and it shows.

Zend\Di leans heavily on Zend\Code\Scanner and here are still some caveats. At the time of writing the MethodScanner or ParameterScanner needs to be fixed to allow for the method signature:

public function __construct(Request $request, Response $response, Grid $grid, Inventory $personalInventory)

At the moment this signature breaks because of the start of the tokens for each Parameter.
It does however accept:

public function __construct(Request $request,Response $response,Grid $grid,Inventory $personalInventory)

As you can see the whitespaces between the parameters have been removed. Bottom line the Scanner should not be so error prone.

For my own testing I have forked ZF2 and fixed this issue in the branch https://github.com/NickBelhomme/zf2/tree/bugfix_tokentrim

As you can see all is still under major development, but that doesn’t mean you cannot play with it. So if you are interested in learning something about Dependency Injection and maybe even ZF2 in general I suggest you take a look at it.

I had a lot of fun inspecting the component and a lot of banging my head into the walls for comprehending the stuff described above. And honestly I couldn’t have completely grasped it without the excellent help and feedback of two special people deeply involved in this project: Matthew Weier O’Phinney and Ralph Schindler. Thank you both for the feedback which helped me in writing this blog post and spreading the knowledge.

A good code resource to mention is a forked and brought up to date repo with the current ZF state: https://github.com/NickBelhomme/ZF2ByExample

Sunny Greetings From Belgium and happy Zend Frameworking!

Nick Belhomme

Quality Assurance in PHP projects

Hello my dearest Unit tester,

My profession is assuring quality in IT projects. For this I implement systems at my client their work environments so that development teams can deliver the best code possible. I also give workshops and recently I gave one at PHPBenelux on Quality Assurance in PHP Projects. It was a 4 hour workshop and got very good feedback.
The slides are here: http://www.slideshare.net/NickBelhomme/php-quality-assurance-workshop-phpbenelux

One of the feedback I got was also related to the PHPUnit training course excerpt I released some time ago to the community.
The reason why I did this is because I felt Quality in software projects can and should be assured. Quality assurance (QA) is not only lacking in most PHP projects but often – most of the time – projects in other languages such as Java or .Net also lack them. Operating systems, application, games and sites are often released followed immediately with a continuous stream of bug fix patches. This means quality assurance is not a language specific problem, but a project problem. Because independent from the language you are using , as soon as you do not have a quality assurance system in place you will sooner or later face a problem. After receiving a lot of requests asking for the demo project from the excerpt I have decided to put it on-line on Github.

It is on-line under a creative common license, meaning you can contribute freely to the project.
The only thing you need to do is create a Github account, fork the project, implement your improvements and or feature requests and then do a pull request.

The project is dependent on PHP 5.3 or higher and uses namespaces, SPL Exceptions, Closures. In short the works for you to play with all these latest programming technique introduced to the PHP community 2 years ago with the launch of PHP 5.3

A big thanks to Zend for hosting the project at http://nickbelhomme.projectx.zend.com.
They make certain all the necessary tools are available and updated to the latest versions.
This way you can see the project in action. It is a text-adventure game which accepts only text input.
Generally you will need to use commands such as “look”, “take”, “open”, “go to”, “use” and many others to advance in the game.

Have fun,
Nick Belhomme