The sample project to generate the shapes.
Create a .env
file and put these data into it:
DATABASE=mysql
MYSQL_HOST=localhost
MYSQL_USERNAME=root
MYSQL_PASSWORD=
MYSQL_PORT=3306
MYSQL_DATABASE=fox
APP_DEBUG=true
For more information please refer to: Environment variables
This project will not use the database. But for running the application they must be defined.
Install the composer:
composer install
In two ways you can generate the shapes. First generating via web pages and second using the console.
To generate the shape on the web page open below URL and follow the page.
http://localhost/christmas/generator
Simply run this command:
php console shape:generate tree
Note: This command accepts two arguments. The first and required one is
shapeName
. For now, there are two options here:tree
andstar
. The second and optional argument is theheight
of the shape. The available options for height are:5
,7
,11
,15
The whole code contains 3 part:
- The Fox mini framework
- The project
- The generator logic
The Fox mini framework is a framework that provides the basic utilities for any web-based project. It is not a commercial project. It is just created to run simple projects like this.
You can see the detail of this mini framework here:
A project that creates the basic settings for the Fox mini framework. It set up the folder structure, create a sample env file, and some examples to get familiar with the framework.
To see more about the project go to:
The most important section for this project lies here. The main duty of this project is to generate the shapes in web pages and console. You can create any shape that you want, but for this step, there are just two shapes. A Star
and a Tree
.
But, how the project works. Before we talk about the project, let's know more about the basic concepts.
There are 3 main concepts. The Shape
, the RowsGenerator
, and the RowsRenderer
.
This class holds all data of the shapes. Like shape height
, the visible character(s)
per row, and some other stuffs. No one else knows about visible character(s)
of rows unless the Shape
itself.
At this moment we have two Shape
. Star
and Tree
.
So as a sample for the Star
shape with the height
of 5
, the rows will be something like this:
+ // The first row contains just ["+"]
* // The second row contains just ["*"]
+*****+ // The third row contains ["+","*","*","*","*","*","+"]
* // The fourth row contains just ["*"]
+ // The fifth row contains just ["+"]
// The pattern of rows:
// [["+"],["*"],["+","*","*","*","*","*","+"]["*"],["+"]]
Does anybody else know how a Star
must be rendered? Absolutely no!
Another question appears here. Is the Shape
violating the Single Responsibility Principle
? No, because the Single Responsibility Principle
says:
Every class must have only one job to do.
Or
A class must have one reason to change.
Now, The only job that the Shape
is doing is holding the data. Holds the height
of shape, and the character(s)
of rows. Nothing else. So it doesn't violet the Single Responsibility Principle
.
You can define your own shapes as many as you want.
Ok, we have the visible character of the rows, but not the entire row with visible and invisible characters. The duty of the RowsGenerator
is creating the rows that contains visible and invisible (spaces) characters. We can generate rows in many ways! First, let's start with the basic one for the Star
shape.
The Star
shape must be aligned center. So the AlignCenterRowsGenerator
will generate rows with visible characters in center surrounded by the spaces.
Imagine a 5 row Star
shape. So the biggest row size is 7
(number of columns). The first row has just one character
which is +
. The AlignCenterRowsGenerator
will add 3
spaces before +
and after it to make sure the size of the first row is 7
and the +
is in the middle of the characters.
By this scenario, all the rows will build and all of them will be aligned in the center.
[
[" "," "," ","+"," "," "," "],
[" "," "," ","*"," "," "," "],
["+","*","*","*","*","*","+"],
[" "," "," ","*"," "," "," "],
[" "," "," ","+"," "," "," "]
]
The question is here, The Star
shape always should be rendered as center-aligned shape, why we separated the RowsGenerator
from Shape
? Two reasons exist here. First Single Responsibility Principle
and second as a sample what if you what each row rendered twice? like this:
[
[" "," "," ","+"," "," "," "],
[" "," "," ","+"," "," "," "],
[" "," "," ","*"," "," "," "],
[" "," "," ","*"," "," "," "],
["+","*","*","*","*","*","+"],
["+","*","*","*","*","*","+"],
[" "," "," ","*"," "," "," "],
[" "," "," ","*"," "," "," "],
[" "," "," ","+"," "," "," "],
[" "," "," ","+"," "," "," "]
]
If the RowsGenerator
exists inside the Shape
you have to open the shape and then edit it will cause violate the Open Close Principle
. Of course, there is another way to do this that is Dependency Injection
. But by using this concept, you will violet the Single Responsibility Principle
. Why? Becuase the shape now do more than one job, First holding the data and second generating the rows.
Ok, now we generated the rows. It's time to render it everywhere we want! On the web page or in a console. So we need a new concept. ShapeRenderer
is our new concept. The ShapreRenderer
is abstract, and some other classes will extend it.
At this moment we have HtmlRowsRenderer
and ConsoleRowsRenderer
as concrete classes of RowsRenderer
. What they do, it's not our concern. We just expect them to convert rows to some printable formats.
We have the Shape
and the RowsGenerator
. Both of them are abstracts and have some concreate
classes. Let's draw the class diagram.
Ok as we can see, both the Shape
and RowsGenerator
as abstract classes have two concrete classes and the RowsGenerator
has a Shape
object. Super! It is a Bridge Design Pattern
. It reduces the number of classes that we might have by bypassing the Cartesian product
. Learn more about it here.
As a final step, now we have the generated rows, we will send rows to RowsRenderer
based on the environment, and it will convert the rows to a printable format. We can send the rows to HtmlRowsRenderer
to render as an HTML
structure or inject it to ConsoleRowsRenderer
to render it as console style. You can define your own RowsRenderer
too.