The whole team here at CakeDC are big supporters and contributors of the CakePHP community. For this month, I decided to do “one CakePHP project per day” to share with the community. Here are some of my projects so far:
TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP
Written by Rafael on June 28, 2021 •
8754 VIEWS
The log of applications is gold. It's an important part of the software, they represent the health of the application. By default, CakePHP will use the FileLog adapter which will write to /logs/ folder.
It's hard to track the live issues, and by hard I mean you will need to connect to the server, open the file on /logs/ and look at the issue which you want to investigate.
What do you think if your application sends the error directly to your team communication (Slack, Teams, RocketChat) application? Will be easier to know about a new error after some deployment? This error is sneaky, and can be in command applications. Often, we only look at the errors when the users report it.
For this sample I will use Slack, but this approach can be implemented for any application.
All we need is to create a Log adapter and configure it. So…let’s bake that:
Now we may get errors like this:
That’s all bakers! I hope this article can be useful and you can improve your logs.
Written by Jorge on June 07, 2021 •
11737 VIEWS
We sponsor a monthly CakePHP training session (register here https://training.cakephp.org ) where we cover different topics about the framework. One of our sessions, the "Getting Started with CakePHP 4" is aimed to help developers starting a new project quickly and following the best practices. Our previous "recommended" quick setting for a CakePHP development environment was using a vagrant box. See details here: https://www.cakedc.com/jorge_gonzalez/2018/01/17/using-a-vagrant-box-as-quick-environment-for-the-getting-started-with-cakephp-training-session. However, we've switched internally to use docker as our primary development environment and also we started using docker in our training sessions. Here's a quick overview of a simple docker based development environment for CakePHP.
composer create-project cakephp/app myproject
A new folder "myproject" will be created with a CakePHP project skeleton inside. Go to this new directory and proceed.
version: '3'
services:
mysql8:
image: mysql:8
restart: always
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: my_app
MYSQL_USER: my_app
MYSQL_PASSWORD: secret
volumes:
- ./:/application
ports:
- '9306:3306'
cakephp:
image: webdevops/php-apache:8.0
container_name: cakephp
working_dir: /application/webroot
volumes:
- ./:/application
environment:
- WEB_DOCUMENT_ROOT=/application/webroot
- DATABASE_URL=mysql://my_app:secret@mysql/my_app
ports:
- "8099:80"
mysql and cakephp - check the docker-compose configuration to see default database and users created in the mysql container, and the same environment params passed to the cakephp container via DATABASE_URL to allow the cakephp container to connect with the mysql database.
NOTE: the ports exposed are 9306 for mysql and 8099 for cakephp webserver. You can list them using docker-compose ps.
mysql --port 9306 -umy_app -psecret my_app
To restore a database dump for example, you can use the command:
curl -L https://raw.githubusercontent.com/CakeDC/cakephp4-getting-started-session/master/my_app.sql |mysql --port 9306 -umy_app -psecret my_app
You can also configure any database tool to access the database in: localhost:9306
docker exec -it --user application cakephp bash
You'll go to the webroot folder, so in order to run the cake shell you'll need to:
cd ..
bin/cake
Written by Yevgeny on May 24, 2021 •
8899 VIEWS
One reason to migrate from CakePHP 2.x to newer versions, is the very powerful ORM system that was introduced in CakePHP 3.x.
public $belongsTo = [
'Profile' => [
'className' => 'Profile',
'foreignKey' => 'profile_id',
]
];
In CakePHP 3.x and 4.x associations are declared in the initialize method. This gives much more flexibility in association configuration.
public function initialize(): void
{
$this->belongsTo('Profile', [
'className' => 'Profile',
'foreignKey' => 'profile_id',
]);
}
Or using setters syntax, it could be done this way:
public function initialize(): void
{
$this->belongsTo('Profile')
->setForeignKey('profile_id')
}
public $actsAs = [
'Sluggable' => [
'label' => 'name',
],
];
In CakePHP 3.x and 4.x, behaviors are configured in the initialize method. This gives much more flexibility in configuration, as in params it's possible to pass anonymous functions.
public function initialize(): void
{
$this->addBehavior('Sluggable', [
'label' => 'name',
]);
}
public $validation = [
'title' => [
'notBlank' => [
'rule' => ['notBlank'],
'allowEmpty' => false,
'required' => true,
'message' => 'Title is required'
],
],
];
In CakePHP 3.x and 4.x, validation is defined in validationDefault method which builds validation rules.
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('title')
->requirePresence('title', 'create')
->notEmptyString('title');
return $validator;
}
Additionally, CakePHP introduced the buildRules method, which is where described foreign keys constraints, uniqueness, or business level rules.
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->isUnique(['username'], __('username should be unique')));
return $rules;
}
protected function _findIndex($state, $query, $results = array()) {
if ($state == 'before') {
$query['contain'] = ['User'];
} else {
// ...
}
}
In CakePHP 3.x, custom finder method accepts query object and some options passed from client code and returns an updated query. This allows for combining multiple finder methods in the same call, and has better grained finder logic.
public function findIndex(Query $query, array $options): Query
{
return $query->contain(['Users']);
}
The afterFind method could be implemented with the Query::formatResults method, which accepts an anonymous function to map each collection item.
Written by Ajibarra on May 10, 2021 •
8615 VIEWS
Nowadays people are not concerned about how large their database is in terms of MB. Storage is cheap. Even getting cheap SSD storage is not a big deal.
However, this is true if we are talking about hundreds of MB or even several GB, but sometimes we get into a situation where we have massive amounts of data (i.e Several tables with lots of longtext columns). At this point it becomes a concern because we need to increase the hard disk size, and find ourselves checking to see if the hard disk is full several times per day or week, etc.
Now, if you have faced a situation like this before, it's time to talk about database compression.
Compression is a technique, developed theoretically back in the 1940s but actually implemented in the 1970s. For this post we will focus on MySQL compression, which is performed using the open-source ZLib library. This library implements the LZ77 dictionary-based compression algorithm.
Before going into MySQL compression details, lets name some of the main DBMS and their compression techniques:
ROW_FORMAT=COMPRESSED
These are the basics but you may find more useful information in MySQL manual.
You can also take a look at Percona which implements a Column level compression. This is interesting if you have a table with a lot of small fields and one large column, or if you have to optimize your database as much as possible. [6]
Finally, just say that even that storage is cheaper than ever, the amount of information has increased as well and we are now using and processing an incredible amount of data... so it looks like compression will always be a requirement.
I hope you find this information useful and please let me know if you have any questions or suggestions below in the comments section.Written by Rochamarcelo on April 26, 2021 •
9090 VIEWS
The CsrfComponent was deprecated since CakePHP version 3.5.0. On CakePHP 4, we now have a new middleware to help us protect applications against Cross Site Request Forgery attacks. In this article, we are going to show the different ways to enable and disable Cross Site Request Forgery between the controller and the new middleware.
$middlewareQueue->add(new CsrfProtectionMiddleware());
$this->loadComponent('Csrf') from your controllers.
cookieName, expiry, secure and field are also available in the middleware. If you used any of these, you should be able to continue using the middleware.
skipCheckCallback to disable Csrf based on a custom logic:
Written by Jorge on April 12, 2021 •
9175 VIEWS
Let's imagine you have a huge application in CakePHP 2.x (or 1.x) and you're planning to upgrade to the latest CakePHP 4.x.
After doing some estimations, you realize the upgrade process is out of your scope, because you don't have the budget or developer availability to do it in 1 shot. At this point, some companies would abort the upgrade and keep working on 2.x for "some more time" until "this last release is delivered" or until "budget is available next fall", digging deeper and deeper into the rabbit hole…
There's an alternative you could follow if this is your case: proceed with the upgrade of a smaller portion of your application and let the 2 versions coexist for some time.
Warning: This is NOT for every project or company. Please carefully think about this decision as it has overhead you'll need to handle.
So, if your application has a portion that could be extracted, with a small set of dependencies from other areas of your application, or if you are creating a new feature with a limited set of dependencies with the rest of your application, this approach would be good for you.
In order to allow both applications to coexist, we are going to keep the CakePHP 1.x application as the main one, and use CakePHP 4.x as a subfolder inside of the first one. It's important to note that in order to share sessions between both applications you'll need to use a storage you can actually share, like database or cache based sessions (redis, etc). Then, you can use a configuration like this one (see below) to add a new upstream to handle your new application. Note: the upstream could be located in another server of your network, using a different PHP version etc.
We've used nginx as an example, but you can use the same approach in other web servers like Apache.
In our example we're going to use all paths starting with /api to be managed by our new CakePHP 4.x application.
upstream cake4 {
# Note this could be any server/port in your network where the cake4 application is installed
server 127.0.0.1:9090;
}
# This is our CakePHP 2.x server
server {
server_name example.com;
root /var/virtual/example.com/app/webroot;
index index.php;
# All requests /api are forwarded to our CakePHP 4.x application
location /api {
proxy_pass http://cake4;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# This is our CakePHP 4.x server
server {
listen 9090;
server_name example.com;
root /var/virtual/cake4-example.com/webroot;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
As you can see, we have 3 blocks defined in our configuration file:
Written by Rafael on March 29, 2021 •
11938 VIEWS
Dependency Injection is some of the bigger buzzwords in PHP frameworks. Historically, CakePHP application logic didn’t support that, until the version 4.2 was released last December. You can do that on your own and have a few plugins for that. This is a new chapter of the framework, let's see how to bake it.
$ ./bin/cake bake controller Address --actions index
Now our app requests /address.json will return an empty JSON.
3. Let’s bake (manually) the Address Service:
Basically I’m using Cake\Http\Client to make the API request. Also I read Geocode.key from Cake\Core\Configure, we don't want to expose our key on public requests (add the key on config/boostrap.php).
4. Let’s rewrite our Controller:
5. Finally, let’s add our Service on Application.php:
That’s all bakers! Now our endpoint /address.json will support query parameters and return the result of the API request.
Written by Yelitza on March 15, 2021 •
8474 VIEWS
Having a successful upgrade implies not only upgrading the code itself, but also identifying the different tasks that will be part of the Upgrade Plan. Making a good plan for an upgrade requires identifying the current status of the application. A good plan is based upon clear, well-defined, and easily understood objectives. After years of experience with CakeDC making upgrades, migrating applications from CakePHP 1 to CakePHP 4 in all possible combinations, we have noticed there are a set of elements or characteristics that are useful to evaluate and identify before starting the upgrade. Having a clear understanding of these elements will be helpful to define the different tasks that will be included in the Upgrade Plan, and reduce any risk while upgrading and delivering. Imagine that you want to run a marathon - but before starting any of the thousands of plans you can find on the internet about “How to run a Marathon”, you must know where you are. You could ask yourself: How many miles per week are you currently running? What is the base training needed to start this program? What is the distance of your longest run in the past 3 weeks? How many days per week do you have available to exercise?, etc. This will help you to choose the plan that better fits you. It’s important to identify where you are, where you want to get and how to get where you want. Wondering how to evaluate where you are for the Upgrade? Evaluate the status of your application. You could consider the following points as reference:
Written by Rafael on March 01, 2021 •
8512 VIEWS
Since the version 2.x of CakePHP has a requests and response objects, they are provided an abstraction around HTTP Requests and responses.
If you are not familiar with CakePHP, it’s a common step when you bake and use $this->request->data instead of $_POST.
Most of the time, we need to modify and append values on our Request data. This can be an identifier of logged user or values for any fields. In many situations, implementations will like this:
Data can be modified easily - you just need to call withData or withParsedBody method and after set the Request (you can also modify the query calling withQueryParams):
Both implementations will generate the same value, but if you have a keen eye, you will see that the second implementation is more clear. Also, our Request has these values on data.
This information can be useful when you use Components from Plugins, as they will expect the values on Requests.
That’s all bakers! I hope this content is useful for you and will improve your requests!
Written by Amanda on February 15, 2021 •
8489 VIEWS
Marketing is an essential part of your business’ success. I don’t just say this because I myself am a marketing connoisseur... or maybe I do. But either way, I’m going to shoot you some quick knowledge about getting your name out there... specifically into the cyber world.
Written by Amanda on February 01, 2021 •
10068 VIEWS
Let’s talk more about upgrading your CakePHP application. In last week’s blog, we talked about why you should hire externally for all of your upgrade needs. You may have been thinking… but why would I (or my company) even need to spend the time, effort, and money on upgrading our projects. Well… for many reasons. You need better security, compatibility, functionality. You’ve seen our posts, you know what needs to be done. Your old CakePHP application needs to be updated. I want to remind you about some of the benefits that there is to upgrading.
Written by Amanda on January 25, 2021 •
8853 VIEWS
There are many reasons that CakePHP recommends upgrading. But should you, really? Contrary to what you would expect, I am here to tell you that maybe you should not upgrade your application. WHAAAAAAT? I said what I said. The answer is no… but yes, the application should be upgraded. What do I mean? I mean that SOMEONE should upgrade to CakePHP 4, but that someone should not necessarily be you or your team. Let’s be real… upgrading takes time. It will take priority and focus. Is extra time something your team has? Usually not. This is why I suggest outsourcing for any upgrading.
Written by Amanda on January 19, 2021 •
8620 VIEWS
Well… 2021 is already feeling a little 2020ish to me, what about you? While I had high hopes of things being back to normal as far as travel, events, etc. It seems as though we still have a ways to go in that department.
Written by Amanda on January 11, 2021 •
8210 VIEWS
After the year we had… our new motto should be work smarter, not harder, in 2021? Am I right? Luckily, CakePHP community and core members are extremely helpful and constantly working to make baking easier. Here are some things that will assist you with your CakePHP projects….
Written by Amanda on December 21, 2020 •
7852 VIEWS
I never thought that I would be so excited to say… the year is coming to an end. What a year it has been. Let’s focus on some good things that happened for us this year.
Written by Yevgeny on December 08, 2020 •
9040 VIEWS
#[Attribute] attribute.
In the following example we define an attribute class which allows us to specify what methods are allowed for controller actions.
Take note that in PHP 8 you can declare properties directly in class constructors, which is really handy for data classes.getAttributes() methods to fetch a list of associated attributes.
The following example shows how we can add permissions checks for controller actions using attributes. In this example, the code is stored in beforeFilter of the controller class itself, but it could be an AppController or more generic solution.
Written by Yelitza on December 01, 2020 •
14517 VIEWS
CakePHP’s database Query Builder provides a simple to use fluent interface for creating and operating database queries. It can be used to accomplish most database operations in your application, and works on all supported database systems.
Query builders allow us to create database queries that work with any supported database, without having to worry about differences between SQL implementations in the different database systems.
The CakePHP query builder uses PDO parameter binding to protect your application against SQL injection attacks. There is no need to clean strings being passed as bindings.
For creating a Query object in CakePHP, the easiest way is using find() from a Table object. In CakePHP, queries are lazily evaluated, which means that they are not evaluated until any of the following actions happens: the query is iterated through a foreach, it’s called first(), all(), toList(), toArray().
You can check all the SQL queries that CakePHP is generating, you just need to enable the Database Logging. See here: https://book.cakephp.org/4/en/orm/database-basics.html#query-logging.
Let’s do a few samples using the Query Builder - this is the ER diagram of the database that we will be using for the queries. We have Orders, Products, Users and Items that will store the products sold in each order and the quantity sold.
Let’s create some queries using the Query Builder SQL Functions: https://book.cakephp.org/4/en/orm/query-builder.html#using-sql-functions. SQL Functions as part of the Query Builder are abstractions of some commonly used SQL functions, and they allow the ORM to choose the specific implementation your application needs based on the Database that is being used. For example, CONCAT is implemented in a different way in MySQL and Postgres, using concat() function will work if you use MySQL or Postgres
Imagine we want to build a report of the products sold, including the following:
$query = Table->find();.
We want to get a report of the products sold and the current stock. Initially, we would need to build a subquery using ItemsTable where the information related to the products sold is present. Don’t forget to use identifier() when referencing any column. This will tell us the items sold per product. $itemsQuery = $this->Items->find() ->where(['Items.product_id' => $query->identifier('Products.id')]);$query->select([
'display_name' => $query->func()->concat([
$query->identifier('Products.name'), ' - ', $query->identifier('Products.description')]),
]);
$query->select([
'quantity_sold' => $itemsQuery->select(['sum' => $itemsQuery->func()->sum($query->identifier('Items.quantity'))]),
]);
$query->select([
'income' => $itemsQuery->select(['sum' => $itemsQuery->func()->sum($query->newExpr('Items.quantity * Items.unit_price'))
])]);
$products = $query->cleanCopy()->select([
'id' => 'ProductDetails.id',
'price' => 'ProductDetails.price',
'quantity' => 'ProductDetails.quantity',
'display_name' => 'ProductDetails.displaye_name',
'quantity_sold' => 'ProductDetails.quantity_sold',
'income' => 'ProductDetails.income',
'stock' => $query->newExpr('ProductDetails.quantity - ProductDetails.quantity_sold'),
])->from([
'ProductDetails' => $query->cleanCopy()->select([
'id' => 'Products.id',
'price' => 'Products.price',
'quantity' => 'Products.quantity',
'display_name' => $query->func()->concat([$query->identifier('Products.name'), ' - ', $query->identifier('Products.description')]),
'quantity_sold' => $itemsQuery->select(['sum' => $itemsQuery->func()->sum($query->identifier('Items.quantity'))]),
'income' => $itemsQuery->cleanCopy()->select(['sum' => $itemsQuery->func()->sum($query->newExpr('Items.quantity * Items.unit_price'))]), ])
]);
SELECT ProductDetails.id AS id,
ProductDetails.price AS price,
ProductDetails.quantity AS quantity,
ProductDetails.display_name AS display_name,
ProductDetails.quantity_sold AS quantity_sold,
ProductDetails.income AS income,
(ProductDetails.quantity - ProductDetails.quantity_sold) AS stock
FROM
(SELECT Products.id AS id,
Products.price AS price,
Products.quantity AS quantity,
(CONCAT(Products.name, :param0, Products.description)) AS display_name,
(SELECT (SUM(Items.quantity)) AS SUM
FROM items Items
WHERE Items.product_id = (Products.id)) AS quantity_sold,
(SELECT (SUM(Items.quantity * Items.unit_price)) AS SUM
FROM items Items
WHERE Items.product_id = (Products.id)) AS income
FROM products Products) ProductDetails
Written by Ajibarra on November 24, 2020 •
10881 VIEWS
In just two days we will get a new PHP release, PHP 8. It’s been almost 5 years since PHP 7 was born and now we are about to enjoy the new major version, which will include some breaking changes and performance improvements. It comes with a lot of new features, including:
static return type
mixed type
DateTime objects from interface
Written by Amanda on November 16, 2020 •
7458 VIEWS
Listen, although 2020 felt like it lasted 25 years, it’s still hard to believe that we are wrapping up this roller coaster of 12 months.
As companies prepare for 2021, crossing their fingers and wishing for a better Q1… it is important to start thinking about marketing strategies and plans for the next year. Without ideas and a solid goal for your company, it is very unlikely that things will change.
Reasons that making a marketing plan is important:
1. It organizes your goals and provides clear plans of actions to achieve them
2. It keeps everyone on track and on the same page
3. Promotes motivation and accountability
I know making a marketing plan can sound time consuming, and a little complex, but it doesn’t have to be! I am going to walk you through the steps of making clear cut goals and plans for 2021 (with some actual examples!).