CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Benefits of S.E.O

As a marketer that works with web developers daily, I know that content may not be top priority on their long list of to-do’s. However, for the success of a company, it is essential. If your team doesn’t have a designated marketing person or team, and you’re not a seasoned content creator, I have 3 letters for you to learn: S.E.O.   So what is SEO? It stands for search engine optimization. Basically this represents guidelines for gaining traffic to your website (or one you’ve been hired to create), and doing it organically. What is organic traffic? This is the results people see without being targeted, no paid ads, no cold calling - just desired results because your company offers something that they are interested in.    Today’s market is extremely competitive, so it is important to take every step in making sure that your webpage stands out and is easy to find. Think about how you find information daily… how to make fried chicken? Where to get your car fixed? Or even when a new movie is being released? You search for it, online, right? 9 times out of 10, you’re probably going to run a search for it on a site like Google, right? Then, most likely, you’re going to click on one of the first couple results that pop up on your screen, because they include keywords relevant to the search that you performed. This is an example of SEO. You search for a term that is relevant or appears on a company’s website, and Google recognizes that term/phrase and yields the webpage to you as a result. Thus, increasing traffic for the website, and a lot of times, without any cost to them.    Utilizing this idea, or service, is essential for any marketing department. Actually, according to a recent survey done by HubSpot, about 64% of marketers actively invest time in search engine optimization. So if you're not, you're falling behind.     Now that you have a basic understanding of what SEO is, we can talk about some of the benefits.   

1. Better PR

  The better your SEO is, the more people that will see your company name pop up on their search engine results. While most companies, like CakeDC, offer specific services (and input specific keywords into ads), we have very detailed SEO streaming from our website so that we do not miss a potential client. It is the goal to be in the top 3 results of a search engine like Google.   Even if someone is searching for something your company doesn’t offer, they may see you, remember you, and use you in the future. win-win. Start building your reputation! For example if you wanted to search for CakePHP web development, you may see:    

2. Increased Traffic 

  This is a no brainer. The more keywords that trigger your webpage at the top of consumer results, the more people that will click. This generates better quality leads, in my opinion. Things like cold calling, or cold emailing, while still effective, have become outdated. Inbound marketing compared to these avenues has produced better return, without becoming annoying to your potential leads. When you aim your focus on specific consumers looking for services that are related to your business, you can build better relationships.   

3. FREE

  Perhaps the best benefit of SEO? The cost… or lack of cost. SEO gets clicks, and sales (we hope!) for the wonderful price of $0 per month. How? Well, Google’s organic rankings come from their algorithm, which determines which webpage’s information relates closely to the “searcher’s” inquiry. This can result from keywords coming from your web pages that may not be imputed on any sort of ads you may be running (we will talk about paid ads in a future blog).    This doesn’t mean that getting a perfect SEO score (yes, you can test it) comes for free. It is important to have good content, detailed content, in all areas on your webpage. Make sure each page describes services you provide, or products that you sell, in great detail. Have pages for each option, and make it accessible like this:       It’s time to beat out the competition, are you ready? I first recommend seeing where you stand. There are a few tools you can use to test your SEO score. One example is: Woorank. I like this site because it gives you your score, shows the good, the bad, and the ugly. The tool also shows you what you can and should do to improve your SEO. You can try most tools for free, or get extra optimization help by paying a premium.    Once you run a report, it’s time to get to work. Fix the issues, and constantly monitor your information. I do think it’s important to mention - don’t expect results to happen overnight, it can take up to 6 months for SEO strategies to yield return. If you’re like me, and impatient, just chill. Put the work in, and get the reward.  

Unit Testing with CakeDC DB Test

The only way to go fast, is to go well, my Uncle Bob always said. Research has shown that development with TDD evolves 10% faster than work without TDD. [See here] CakePHP comes with comprehensive testing support built-in with integration for PHPUnit. It also offers some additional features to make testing easier. This article will cover how to write Unit Tests with CakePHP and using the CakeDC DbTest plugin.
First, let's bake a new project: composer create-project --prefer-dist cakephp/app:4.* Now, we need to think  about a model so we can create it and test it. I guess everybody has written a Products model before, our model would looks like this:

  • Name (string)
  • Slug (string, unique)
  • Description (text)
  • Price (decimal)
If you are not familiar with Slug, Slug is the part of a URL that identifies a page in a human-readable way, usually for pages with friendly urls. It will be the target of our tests. bin/cake bake migration CreateProducts name:string slug:string:unique price:decimal[5,2] description:text created modified Pay attention, for slug, It was created with a unique index. Meanwhile our goal will be to have urls like: /slug-of-product and this way, the slug needs to be unique.
Let's run the migrations for database: bin/cake migrations migrate At this point, our database is ready with the `products` table and we can start coding and writing the tests. * Note: some points were abstracted, such as installation, project configuration, and shell commands, because that's not the goal of the article. You can find all information on these in the cookbook.
Let's bake the models, controller, and templates for Product: bin/cake bake all Products
Now that we have all the Classes we can start writing the unit tests. Let's start with ProductsController, writing one test for add Product: tests/TestCase/Controller/ProductsControllerTest.php public function testAdd(): void     {         $this->enableCsrfToken();         $this->enableRetainFlashMessages();         $this->post('products/add', [             'name' => 'iPhone 11',             'slug' => 'iphone-11',             'price' => 699,             'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',         ]);         $this->assertResponseSuccess();         $this->assertFlashMessage(__('The product has been saved.'));         $this->assertRedirect('products');     } Let's write another test that tries to add a duplicated product. First, we need to update the fixture, then write the test: tests/Fixture/ProductsFixture.php     public function init(): void     {         $this->records = [             [                 'id' => 1,                 'name' => 'iPhone SE',                 'slug' => 'iphone-se',                 'price' => 399,                 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',                 'created' => '2020-04-23 13:12:58',                 'modified' => '2020-04-23 13:12:58',             ],         ];         parent::init();     } tests/TestCase/Controller/ProductsControllerTest.php public function testAddDuplicated(): void     {         $this->enableCsrfToken();         $this->enableRetainFlashMessages();         $this->post('products/add', [             'name' => 'iPhone SE',             'slug' => 'iphone-se',             'price' => 399,             'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat.',         ]);         $this->assertResponseSuccess();         $this->assertFlashMessage(__('The product could not be saved. Please, try again.'));         $this->assertNoRedirect();     }   With these tests, we know the work is complete when the acceptance criteria (the slug of product must be unique) of the tests is passed.
That's all? No, this article it's not only about tests, this article is about the CakeDC DbTest plugin and how it can be advantageous.

CakeDC DB Test

Maintaining fixtures on real applications can be hard, a big headache. Imagine writing +1k products on ProductFixture, and adding a relationship like Product belongs to Category, then having to write new fixtures and keep them in sync. Real applications usually have features like authentication with ACL, where each User has one Role, and each Role can access many features. Administrator has full rights, Manager has many rights, and so on.
Keeping all of this information in our fixtures is painful. Most of the frameworks have plugins to help with that issue. Thanks to the CakeDC team, we can easily let the DbTest to do the "dirty" work for us: Let's install and load the plugin: composer require cakedc/cakephp-db-test:dev-2.next bin/cake plugin load CakeDC/DbTest
Then configure the plugin on project:
  1. Copy/replace the phpunit.xml: https://github.com/CakeDC/cakephp-db-test/blob/2.next/phpunit.xml.dbtest
  2. Configure test_template datasource on config/app.php:
'Datasources' => [     // ...     'test_template' => [         'className' => Connection::class,         'driver' => Mysql::class,         'persistent' => false,         'timezone' => 'UTC',         //'encoding' => 'utf8mb4',         'flags' => [],         'cacheMetadata' => true,         'quoteIdentifiers' => false,         'log' => false,         //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],     ],     // ...
Now, we can delete our fixture and generate the dump of our database for using on tests: // migrate the database for template bin/cake migrations migrate -c test_template // import fixtures bin/cake fixture_import dump // generate dump /bin/cake db_test -i   Finally, we can see some advantages of CakeDC DbTest:
  • Speed.
  • Maintain fixtures with your regular database tool.
  • Run migrations to your dbtest db too.
  • Copy data from your live db to reproduce bugs.
     
That's all, bakers. Now we have test_db.sql, and you can see how our fixtures will come from this data. You can check the code source of this article on this repository: https://github.com/rafaelqueiroz/cakephp-db-test-sample    

CakeFest Is Going Digital!

What a strange unprecedented time we are living in, right? Thanks, COVID-19. CakeFest, just like many other conferences and events, has been up in the air. Our team spent weeks discussing the possibility of moving forward as planned, having backup plans, or ways out of contracts should the emergency continue. After negotiations with pending sponsors, venues, vendors, etc., we toyed with the idea of going virtual and following suit of many others. After discussions with the core team, it was decided that this was the way to go.    This virus infused world is uncharted territory for billions of people, but one thing remains the same among us all…. the safety and health of our clients, families, and teams is the number one concern and priority. I’m sure everyone has encountered those who don’t understand these types of decisions, but allow me to put some numbers into perspective: Currently, there are 1,696,588 cases of COVID-19 worldwide, and 105,952 deaths have been reported  (as of April 12, 2020 via World Health Organization). According to hopkinsmedicine.org: “The virus can spread between people interacting in close proximity—for example, speaking, coughing, or sneezing—even if those people are not exhibiting symptoms. In light of this evidence, CDC recommends wearing cloth face coverings in public settings where other social distancing measures are difficult to maintain (e.g., grocery stores and pharmacies) especially in areas of significant community-based transmission.”    So what I am reading is that someone who has it, may not even know they are carrying the virus, and can spread the germ by just being in the same area as a peer. This is even more frightening due to the fact that there’s no research to this new virus, therefore no cure or vaccine to combat its effects on those infected.    With the statistics and facts about this virus, we made the difficult decision to go digital for CakeFest 2020. We understand that our intended dates in October are still far away, but without knowing how regulations and orders will change from government officials, it is hard to plan such an important event. Hopefully after learning more about how we came to this decision, you will understand and support us. We were extremely excited to bring the Cake to Los Angeles, but not at the cost of anyone’s health. We also hope to still deliver the knowledge, information, and possibly some ElePHPants to our virtual attendees this year (while also keeping you safe). The good news is, we can host in LA next year! And look at it this way, you can watch and learn in your pajamas!    So what will CakeFest look like this year? We are still in the planning phases, and allowing for sponsors. After a lot of research we hope to find the best platform for our needs, and the best speakers to share the knowledge. Speaking of which, if you have tips or suggestions for hosting a virtual conference, we’d love to hear from you! You can email us.  Tickets will be sold (for less cost, there's a silver lining) for full access to all videos - before and after the event. We will conduct the conference as normal, but the difference is, we won’t be face to face   To keep up with the planning process or any changes, make sure you follow us on Twitter, or Facebook. You can always stay up to date at CakeFest.org, too.   

Improving your mental health while wor...

We’ve previously covered tips, tricks and resources to improve your working at home productivity - today we chat about how to improve your mental health!  There are currently more and more people switching over to remote working - perhaps your company has changed policies, or maybe you’ve been forced to work at home due to a stay-at-home order - whatever the case, mental health while working at home is vitally important. Some of us are made to work at home - working from an office, may not be your jam, and the peace and serenity that a home office offers completes the mood.  However, there are some of us that enjoy the daily routine, the commute to the office and the morning catch up around the coffee machine!  So have you been stuck lately feeling a little more down than usual? Here are some tips to increase your mental health during this time.

Keep your morning routine

Even though you are working from home, and technically can get away with wearing your pajamas all day - you definitely shouldn’t! A morning routine helps set the mood for the day. Perhaps this includes getting ready, dressed or going for a morning run. 

Exercise and eat well

We aren’t talking about a full gym session - merely taking some time to move around. A walk around the block or a bit of stretching will help get those endorphins going. A good diet also goes a long way to helping you feel better - unfortunately too much sugar and too many carbs can have a negative impact on your mental health.

Focus on the now

Practice mindfulness - the ability to focus in on the here and now helps us to drop the negative or difficult emotions that weigh us down. 

Open up to someone

You are valued by others - knowing that is important. Finding a safe space to chat about things goes a long way to lifting the mood. Not sure where that safe space is? Check out https://osmihelp.org/ for some great resources and information.

Take a break

Working from home can sometimes feel endless - some of us struggle to log off at “home time” because, well, you are at home. But this important step is essential to your mental health. Take moments throughout the day to step away - just a couple of minutes to grab some water and take a break. If you haven’t already, please sign up and donate to OSMI - Open Sourcing Mental Illness. We’ve previously chatted with Ed Finkler, OSMI’s founder. CakeDC fully supports OSMI and we love opening up the conversation around mental health - Do you?

Introduction to CakeDC Api plugin

 

The CakeDC API plugin was created with the goal to prepare abstract solutions that solve generic tasks -in case of creating a rest API. It provides such features like automatic rest API generation based on db schema, support nested routes. It also allows the use of different formats like json or xml, and easily adds their own custom format. It helps to solve generic tasks appearing in development of any API, like pagination, data validation, adding common data to response, or building metadata, about data objects.

Dependencies

The CakeDC API plugin hardly depends on the CakeDC Users Plugin. For authentication it is highly recommended to use CakePHP Authentication plugin configured as middleware.

Installation

You can install this plugin into your CakePHP application using composer. The recommended way to install composer packages is: composer require cakedc/cakephp-api  

 Load the Plugin

Ensure  The CakeDC API Plugin is loaded in your src/Aplication.php in bootstrap method. php     $this->addPlugin(\CakeDC\Users\Plugin::class);     $this->addPlugin(\CakeDC\Api\Plugin::class, ['bootstrap' => true, 'routes' => true]);

Configuration

Minimal configuration to allow non authorized requests require you to copy file: ./vendor/cakedc/cakephp-api/config/api_permissions.php.default to ./config/api_permissions.php

Examples

Lets bake table blogs with two fields id and name. After that, the next requests would be possible to perform to api. Requests would be performed using curl. Request: curl http://localhost:8765/api/blogs Response: {     "status": "success",     "data": [         {             "id": 1,             "name": "blog001"         }     ],     "pagination": {         "page": 1,         "limit": 20,         "pages": 1,         "count": 1     },     "links": [         {             "name": "self",             "href": "http:\/\/localhost:8765\/api\/blogs",             "rel": "\/api\/blogs",             "method": "GET"         },         {             "name": "blogs:add",             "href": "http:\/\/localhost:8765\/api\/blogs",             "rel": "\/api\/blogs",             "method": "POST"         }     ] } Request: curl -d "name=blog001" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://localhost:8765/api/blogs Response: {     "status": "success",     "data": {         "name": "blog001",         "id": 1     },     "links": [] } Request: curl -d "name=blog002" -H "Content-Type: application/x-www-form-urlencoded" -X PUT http://localhost:8765/api/blogs/1 Response: {     "status": "success",     "data": {         "id": 1,         "name": "blog002"     },     "links": [] } Request: curl -X DELETE http://localhost:8765/api/blogs/1 Response: {     "status": "success",     "data": true,     "links": [] } For more complex features about plugin initialization and configuration based on routes middlewares, we plan to create an additional article.

Services and Actions

In the REST recommendations documents names defined as a noun. Here, services come into play. It describes business entities. From other side actions define the verbs that describe the operations that should be performed on the actions. Common and difference between controller classes and services. The common part is the service is the managing the choosing action to execute. The primary difference is that service could be nested, if this is defined by request url. Common and difference between controller actions and service actions. The common part is the action defined logic of the request. The primary is that each service’s action is defined as a separate class. This means that generic actions could be defined as common class and reused in many services. From the other side, an action class is able to extend if the system has slightly different actions. This way it is possible to build actions hierarchy. Both service and actions define an event during this execution flow.  Main service events: * Service.beforeDispatch * Service.beforeProcess * Service.afterDispatch Main action events: * Action.beforeProcess * Action.onAuth * Action.beforeValidate * Action.beforeValidateStopped * Action.validationFailed * Action.beforeExecute * Action.beforeExecuteStopped * Action.afterProcess Crud actions define events that depend on the type of action, and more details could be checked in documentation. * Action.Crud.onPatchEntity * Action.Crud.onFindEntities * Action.Crud.afterFindEntities  * Action.Crud.onFindEntity

Nested services

Consider we have request with method POST /blogs/1/posts with data like {"title": "...", "body": "..."} As it is possible to see there is nothing in the given data about the blog_id to which the newly created post should belong to. In the case of controllers we should define custom logic to parse a route, and to consume the blog_id from url. For nested service all checks and records updates are automatically executed. This will happen for any crud operations, when detected by the route parent service. So for example: GET /blogs/1/posts, will return only posts for the blog with id 1. Logical checks are also performed, so for request: DELETE /blogs/1/posts/2, a user gets an error if the post with id 2 belongs to the blog with id 2.

Action inheritance

As each action can be defined as a separate class, it is possible to use class inheritance to define common logic. For example:  Add and Edit actions.

Extending services and actions with shared functionality

The alternative way for defining common logic actions is using action extensions. Action extension is a more powerful feature and could be used for global tasks like search or pagination. It is also possible to create service level extensions. Those extensions work on the top level of the execution process, and could be used for things like adding cors feature, or to append some counter into response.

Add service actions from service::initialize

This is a recommended way to register non crud actions. The mapAction uses the Router class syntax for parsing routes. So on any special use cases well described in cakephp core.     public function initialize()     {         parent::initialize();         $this->mapAction('view_edit', ViewEditAction::class, [             'method' => ['GET'],             'path' => 'view_edit/:id'         ]);     }

Configure actions using action class map.

Each action class uses $_actionsClassMap for defining a map between crud (and non crud) actions on the name of the action class. Non crud actions should be additionally mapped, which is described in the previous step. use App\Service\Protocols\IndexAction; class ProtocolsService extends AppFallbackService {     /**      * Actions classes map.      *      * @var array      */     protected $_actionsClassMap = [         'index' => IndexAction::class,     ];

Configure service and action in config file

Service options are defined in the config/api.php in Api.Service section. Let's consider configuration options for ArticlesService. Configuration are hierarchical in the next sense: 
  • define default options for any service within the application in the Api.Service.default.options section.
  • define options for any service within the application in Api.Service.articles.options section.
All defined options are overridden from up to down in described order. This allows common service settings, and the ability to overwrite them in bottom level.
  •  Api.Service.classMap - defines name map, that allows defining services action classes with custom location logic.
    Any action, that could be loaded as default action defined in fallback class, or specific action class could be configured using configuration file.
    Let's consider how one can configure options for IndexAction of ArticlesService.
    Configuration are hierarchical in the next sense: 
  • one can define default options for any action for all services in the application in the Api.Service.default.Action.default section.
  • one can define default options for index action for all services in the application in the Api.Service.default.Action.index section.
  • one can define options for any action in the specific (articles) service in the Api.Service.articles.Action.default section.
  • one can define options for index action in the specific (articles) service in the  Api.Service.articles.Action.index section.

Crud and non crud methods. Mapping non-crud actions.

Crud services mapped automatically in two levels routing by FallbackService.

Index and view. Formatting output

The CakeDC Api Plugin is flexible and provides multiple ways to prepare result data for the response objects. There is a list of main options.

Use Entity serialization

The most trivial way to convert data is using entity serialization. When converting an entity to a JSON, the virtual and hidden field lists are applied.  Entities are recursively converted to JSON as well.  This means that if you eager, and loading entities and their associations, CakePHP will correctly handle converting the associated data into the correct format. Additional fields could be defined using Entity::$_virtual and hidden using Entity::$$_hidden.

Build object manually from Action::execute

In this case users manually perform mapping of requests received from model layer to output array. public function process() {     $entity = $this->getTable()->get($this->getId());     return [         'id' => $entity->id,         'name' => $entity->name,     ]; }

Use Query::formatResults in model layer

The request could be formatted in model layer using: Query::formatResults. So in this case, the process action just calls for a needed finder from the model layer and returns the result. public function findApiFormat(Query $query, array $options) {     return $query         ->select(['id', 'body', 'created', 'modified', 'author_id'])         ->formatResults(function ($results) use ($options) {             return $results->map(function ($row) use ($options) {                 $row['author'] = $this->Authors->getFormatted($row['author_id']);                 unset($row['author_id']);                   return $row;             });         });

Use Action extensions to format output

In index action defined callback Action.Crud.afterFindEntities, which called after data fetched,  could be used to extend or overload results coming from the database. Callbacks are catch-in-action extensions and could be applied to multiple endpoints. For view action defined Action.Crud.afterFindEntity, which called after single record fetched.

Use Action extensions to append additional data to output

Sometimes there is some additional information needed to be presented in some group of endpoints. In this case it is possible to implement an action extension to append additional data. For example, pagination provides information about number of pages, records count, and current page number. Another example for additional data is some execution statistics about the query. Here you see main parts of appending such data from extension. class PaginateExtension extends Extension implements EventListenerInterface {     public function implementedEvents(): array     {         return [             'Action.Crud.afterFindEntities' => 'afterFind',         ];     } ...     public function afterFind(EventInterface $event): void     {         ...         $pagination = [             'page' => $this->_page($action),             'limit' => $limit,             'pages' => ceil($count / $limit),             'count' => $count,         ];         $result->appendPayload('pagination', $pagination);     }     The renderer class describes how to handle payload data. For example in JSend renderer, all payload records appended to the root of the resulting json object.

Rendering output. Renderers.

Renderers perform final mapping of response records to output format.  Such formats like xml, json, or file are provided by  The CakeDC API plugin. JSend is the json extension with some additional agreements about returning results.    

We Bake with CakePHP