{"id":12148,"date":"2010-02-17T20:52:31","date_gmt":"2010-02-17T18:52:31","guid":{"rendered":"https:\/\/mamchenkov.net\/wordpress\/?p=12148"},"modified":"2010-02-17T20:52:31","modified_gmt":"2010-02-17T18:52:31","slug":"cakephp-building-factories-with-models-and-behaviors","status":"publish","type":"post","link":"https:\/\/mamchenkov.net\/wordpress\/2010\/02\/17\/cakephp-building-factories-with-models-and-behaviors\/","title":{"rendered":"CakePHP : Building factories with models and behaviors"},"content":{"rendered":"<!-- google_ad_section_start -->\n<p><a href=\"http:\/\/cakephp.org\">CakePHP<\/a> is a wonderful framework. \u00a0 Recently I proved it to myself once again (not that I need much of that proof anyway). \u00a0The problem that we had at work was a whole lot of code in once place and no obvious way of how to break that code into smaller, more manageable pieces. \u00a0MVC is a fine architecture, but it wasn&#8217;t obvious to me how to apply it to larger projects.<\/p>\n<p>In our particular case, we happen to have several data types, which are very similar to each other, yet should be treated differently. \u00a0Two examples are:<\/p>\n<ol>\n<li>Client account registrations. \u00a0 Our application supports different types of accounts and each type has its own processing, forms, validations, etc. \u00a0However, each type of account is still an account registration.<\/li>\n<li>Financial transactions. \u00a0Our clients can send us money via a number of payment methods &#8211; credit cards, PayPal, bank wires, etc. \u00a0Each type of the transaction has its own processing, forms, validations, etc. \u00a0However, each type of the transaction is still a financial transaction of money coming in.<\/li>\n<\/ol>\n<p>Having a separate model for each type of account or for each type of transaction seems\u00a0excessive. \u00a0There are differences between each type, but not enough to justify a separate model. \u00a0Having a single model though means that it&#8217;ll continue to grow with each and every difference that needs to be coded in. \u00a0Something like a class factory design pattern would solve the problem nicely, but the question is how to fit it into an existing MVC architecture. \u00a0Read the rest of this post for a demonstration.<\/p>\n<p><!--more--><\/p>\n<p>CakePHP supports a notion of behaviors, which are a sort of modifier for a model. \u00a0Things that bloat a model can easily be moved out into a behavior. \u00a0So, for each type of our financial transaction we can create a separate behavior. \u00a0That would help, but how would we keep behaviors consistent and how can we share common code between them? \u00a0With a base model. \u00a0And furthermore, if we want to make sure that each behavior actually implements certain methods, we can force an interface constraint upon the behavior. \u00a0Enough of this blah blah blah. \u00a0Here comes the code.<\/p>\n<p>First, let&#8217;s create a table for our transactions with some sample data.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nDROP TABLE transactions;\r\nCREATE TABLE transactions (\r\n\tid INTEGER UNIQUE AUTO_INCREMENT PRIMARY KEY,\r\n\tmethod CHAR(2) NOT NULL,\r\n\tamount DECIMAL(10,2) NOT NULL DEFAULT 0,\r\n\tcurrency CHAR(3) NOT NULL);\r\nINSERT INTO transactions SET method='CC', amount=100, currency='USD';\r\nINSERT INTO transactions SET method='BW', amount=1500, currency='GBP';\r\nINSERT INTO transactions SET method='PP', amount=21.50, currency='EUR';\r\n<\/pre>\n<p>Needless to say, I am trying to simplify things, hence such a short table definition. \u00a0Method here is a two character description of the payment method (&#8220;CC&#8221; for credit card, &#8220;PP&#8221; for PayPal, &#8220;BW&#8221; for bank wire, etc).<\/p>\n<p>Second, let&#8217;s create a couple of views, to get them out of the way.  One to add a transaction (in app\/views\/transactions\/add.ctp) with the following content:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;h1&gt;Add transaction&lt;\/h1&gt;\r\n&lt;?php\r\necho $form-&gt;create('Transaction');\r\necho $form-&gt;input('method');\r\necho $form-&gt;input('amount');\r\necho $form-&gt;input('currency');\r\necho $form-&gt;end('Save Transaction');\r\n?&gt;\r\n<\/pre>\n<p>And another one to view all transactions (in app\/views\/transactions\/index.ctp) with the following content:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;h1&gt;Transactions&lt;\/h1&gt;\r\n&lt;a href=&quot;\/transactions\/add&quot;&gt;Add transaction&lt;\/a&gt;\r\n&lt;table&gt;\r\n\t&lt;tr&gt;\r\n\t\t&lt;th&gt;ID&lt;\/th&gt;\r\n\t\t&lt;th&gt;method&lt;\/th&gt;\r\n\t\t&lt;th&gt;amount&lt;\/th&gt;\r\n\t\t&lt;th&gt;currency&lt;\/th&gt;\r\n\t&lt;\/tr&gt;\r\n\t&lt;?php\r\n\t\tforeach ($transactions as $transaction) {\r\n\t\t\techo &quot;&lt;tr&gt;&quot;;\t\r\n\t\t\techo &quot;&lt;td&gt;{$transaction&#x5B;'Transaction']&#x5B;'id']}&lt;\/td&gt;&quot;;\r\n\t\t\techo &quot;&lt;td&gt;{$transaction&#x5B;'Transaction']&#x5B;'method']}&lt;\/td&gt;&quot;;\r\n\t\t\techo &quot;&lt;td&gt;{$transaction&#x5B;'Transaction']&#x5B;'amount']}&lt;\/td&gt;&quot;;\r\n\t\t\techo &quot;&lt;td&gt;{$transaction&#x5B;'Transaction']&#x5B;'currency']}&lt;\/td&gt;&quot;;\r\n\t\t\techo &quot;&lt;\/tr&gt;&quot;;\t\r\n\t\t}\r\n\t?&gt;\r\n&lt;\/table&gt;\r\n<\/pre>\n<p>Nothing fancy, just good enough for us to see what we have in the database.<\/p>\n<p>The next simplest thing is the transactions controller.  Again, nothing fancy in here, just two actions for the above views.  Here is the code for app\/controllers\/transactions_controller.php :<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nclass TransactionsController extends AppController {\r\n\tvar $name = 'Transactions';\r\n\r\n\tfunction index() {\r\n\t\t$this-&gt;set('transactions', $this-&gt;Transaction-&gt;find('all'));\r\n\t}\r\n\r\n\tfunction add() {\r\n\t\tif (!empty($this-&gt;data)) {\r\n\t\t\tif ($this-&gt;Transaction-&gt;save($this-&gt;data)) {\r\n\t\t\t\t$processed = $this-&gt;Transaction-&gt;process();\r\n\t\t\t\tif ($processed) {\r\n\t\t\t\t\t$this-&gt;Session-&gt;setFlash(&quot;Your transaction has been saved and processed ($processed)&quot;);\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\t$this-&gt;Session-&gt;setFlash(&quot;Your transaction has been saved but failed to process ($processed)&quot;);\r\n\t\t\t\t}\r\n\t\t\t\t$this-&gt;redirect(array('action'=&gt;'index'));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n<\/pre>\n<p>Simplest possible listing of items in the database.  As for the addition of the new ones, we want to validate the form submission, save data in the database, and then, if everything was OK, call a process() method, so that an appropriate processing logic is triggered.  For the simplicity of the example, our transaction processing logic will just return a string &#8211; a different one for each transaction type.  And we&#8217;ll display that string in the flash message.<\/p>\n<p>Now, for the main course &#8211; transaction model (app\/models\/transaction.php):<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nclass Transaction extends AppModel {\r\n\tconst BEHAVIOR_PREFIX = 'Trnx';\r\n\r\n\tvar $allowedMethods = array('CC','PP','BW');\r\n\tvar $allowedCurrencies = array('USD','EUR','GBP','CHF','JPY');\r\n\r\n\tvar $name = 'Transaction';\r\n\tvar $validate =  array(\r\n\t\t\t\t\t\t'method' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t'notEmpty' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; 'notEmpty',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t\t'allowedMethod' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; array('checkAllowedMethod'),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'message' =&gt; 'This method is not allowed',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t'amount' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t'notEmpty' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; 'notEmpty',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t\t'allowedAmount' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; array('checkAllowedAmount'),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'message' =&gt; 'This amount is invalid',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t'currency' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t'notEmpty' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; 'notEmpty',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t\t\t'allowedCurrency' =&gt; array(\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'rule' =&gt; array('checkAllowedCurrency'),\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'message' =&gt; 'This currency is not allowed',\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\r\n\r\n\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t);\r\n\r\n\tfunction beforeValidate() {\r\n\t\t$method = '';\r\n\t\tif (!empty($this-&gt;data&#x5B;'Transaction']&#x5B;'method'])) {\r\n\t\t\t$method = $this-&gt;data&#x5B;'Transaction']&#x5B;'method'];\r\n\t\t}\r\n\t\tif (in_array($method, $this-&gt;allowedMethods)) {\r\n\t\t\t$this-&gt;pushBehavior($method);\r\n\t\t}\r\n\t\treturn true;\r\n\t}\r\n\r\n\tfunction nameBehavior($method) {\r\n\t\treturn self::BEHAVIOR_PREFIX . strtolower($method);\r\n\t}\r\n\r\n\tfunction pushBehavior($method) {\r\n\t\t$behavior = $this-&gt;nameBehavior($method);\r\n\t\t$this-&gt;Behaviors-&gt;attach($behavior);\r\n\t\t$this-&gt;Behaviors-&gt;enable($behavior);\r\n\t}\r\n\r\n\tfunction popBehavior($method) {\r\n\t\t$behavior = $this-&gt;nameBehavior($method);\r\n\t\t$this-&gt;Behaviors-&gt;disable($behavior);\r\n\t\t$this-&gt;Behaviors-&gt;detach($behavior);\r\n\t}\r\n\r\n\tfunction checkAllowedMethod($check) {\r\n\t\t$allowed =  in_array($check&#x5B;'method'], $this-&gt;allowedMethods);\r\n\t\treturn $allowed;\r\n\t}\r\n\r\n\tfunction checkAllowedAmount($check) {\r\n\t\t$result = false;\r\n\r\n\t\tif (method_exists($this, 'get_max_amount')) {\r\n\t\t\t$maxAmount = $this-&gt;get_max_amount();\r\n\t\t\tdebug(&quot;maxAmount = $maxAmount&quot;);\r\n\t\t\tif (is_numeric($check&#x5B;'amount']) &amp;&amp; $check&#x5B;'amount'] &gt; 0 &amp;&amp; $check&#x5B;'amount'] &lt;= $maxAmount) {\r\n\t\t\t\t$result = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn $result;\r\n\t}\r\n\r\n\tfunction checkAllowedCurrency($check) {\r\n\t\treturn in_array($check&#x5B;'currency'], $this-&gt;allowedCurrencies);\r\n\t}\r\n}\r\n\r\n?&gt;\r\n<\/pre>\n<p>So, what do we have here?  Let&#8217;s take a closer look.<\/p>\n<ol>\n<li>Class constant BEHAVIOR_PREFIX.  We&#8217;ll use it to help us with the naming convention.  Nothing more.<\/li>\n<li>Arrays with valid options.  The simplest possible data for the validation process.<\/li>\n<li>Validation rules.  We&#8217;ll just check that specified transaction currency and transaction methods are sensible, and that transaction amount is between zero and maximum allowed amount.  Here is a tricky bit: we want a different maximum amount limit for each different payment method.  Stay tuned.<\/li>\n<li>beforeValidate() . That&#8217;s an important bit.  CakePHP will call this method before the form validation kicks in.  Which will kick in before the data is actually saved to the database.  And that&#8217;s precisely the place where we want to identify transaction method and load an appropriate behavior.  Note that this method should always return true.<\/li>\n<li>nameBehavior() . That&#8217;s a little helper method I use to keep construct the name of the behavior from a specified parameter, in this case &#8211; a transaction method.<\/li>\n<li>pushBehavior() and popBehavior() are two methods which, for the lack of better naming, attach\/enable and disable\/detach a specific behavior to\/from the model.  While pushBehavior() is called automatically from beforeValidate(), I can always use these methods manually from other places in the model or even from the controller.  Handy.<\/li>\n<li>checkAllowedMethod() and checkAllowedCurrency() are two simple validation checks that compare a given value against a valid items list.<\/li>\n<li>checkAllowedAmount() &#8211; that&#8217;s the part where we check that specified amount is within appropriate limits.  To get a higher limit we call getMaxAmount() method, which is common for all transaction types (stay tuned for definition).  That method returns a number, which is different for each type of the transaction.  And we add some extra logic to check if getmaxAmount() method actually exists, because if an invalid transaction method will be specified, then beforeValidation() will fail to load any sensible behavior and we&#8217;ll get a PHP complain here, when it will try to call an undefined method.<\/li>\n<\/ol>\n<p>Now, let&#8217;s see the behaviors.  Behaviors should extend the ModelBehavior class of CakePHP and they should have a setup() method that will be called when they are loaded.  But since we need a number of behaviors that behave the same way, what we do instead is create a base transaction behavior, which the rest of the behaviors extend.  Here is the base behavior as defined in app\/models\/behaviors\/trnxbase.php :<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nrequire_once dirname(__FILE__) . DS . 'interfaces' . DS . 'transaction.php';\r\n\r\nabstract class TrnxbaseBehavior extends ModelBehavior implements iTransaction {\r\n\r\n\tvar $settings;\r\n\tvar $max_amount = 500;\r\n\r\n\tfunction setup(&amp;$Model, $settings) {\r\n\t\tif (!isset($this-&gt;settings&#x5B;$Model-&gt;alias])) {\r\n\t\t\t$this-&gt;settings&#x5B;$Model-&gt;alias] = array();\r\n\t\t}\r\n\t\t$this-&gt;settings&#x5B;$Model-&gt;alias] = array_merge($this-&gt;settings&#x5B;$Model-&gt;alias], (array) $settings);\r\n\t}\r\n\r\n\tfunction getMaxAmount(&amp;$Model) {\r\n\t\treturn $this-&gt;max_amount;\r\n\t}\r\n}\r\n?&gt;\r\n<\/pre>\n<p>Important things to see here:<\/p>\n<ol>\n<li>We use an abstract class here.  We won&#8217;t have a &#8220;base&#8221; transaction method and nobody should really instantiate the TrnxbaseBehavior class.<\/li>\n<li>We also implement an iTransaction interface, which we also require.  This is a very convenient bit which helps us to make sure that all behaviors that extend the base also implement all required methods.<\/li>\n<li>We set a default value for our maximum amount validation.  This value will be used in case one of the behaviors will not override it.<\/li>\n<li>setup() method is common to all transaction behaviors, so we put it here.<\/li>\n<li>getMaxAmount() is common to all transactions as well.<\/li>\n<\/ol>\n<p>Before I forget it, here is the contents of the app\/models\/behaviors\/interfaces\/transaction.php &#8211; we only require each transaction behavior to define the process() method.<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\r\ninterface iTransaction {\r\n\tpublic function process();\r\n}\r\n?&gt;\r\n<\/pre>\n<p>So, now all we need to do is create our behaviors &#8211; one for each transaction method.  Here&#8217;s one for the credit cards (app\/models\/behaviors\/trnxcc.php):<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nrequire_once dirname(__FILE__) . DS . 'trnxbase.php';\r\n\r\nclass TrnxccBehavior extends TrnxbaseBehavior {\r\n\r\n\tvar $max_amount = 2500;\r\n\r\n\tfunction process() {\r\n\t\treturn 'Charging the credit card';\r\n\t}\r\n}\r\n?&gt;\r\n<\/pre>\n<p>Notice how we require and extend a base transaction behavior, how we set a different limit for the maximum amount limit, and how we have to define the process() method.  If we forget to define the process() method, PHP will fail with a lot of noise.  Try it at home.<\/p>\n<p>In the meantime, here is a very similar behavior for a PayPal transaction (app\/models\/behaviors\/trnxpp.php):<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nrequire_once dirname(__FILE__) . DS . 'trnxbase.php';\r\n\r\nclass TrnxppBehavior extends TrnxbaseBehavior {\r\n\r\n\tvar $max_amount = 10000;\r\n\r\n\tfunction process() {\r\n\t\treturn 'Processing PayPal transaction';\r\n\t}\r\n}\r\n?&gt;\r\n<\/pre>\n<p>And then another one for bank wires (app\/models\/behaviors\/trnxbw.php):<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nrequire_once dirname(__FILE__) . DS . 'trnxbase.php';\r\n\r\nclass TrnxbwBehavior extends TrnxbaseBehavior {\r\n\r\n\tvar $max_amount = 100000;\r\n\r\n\tfunction process() {\r\n\t\treturn 'Generating bank wire instructions PDF';\r\n\t}\r\n}\r\n?&gt;\r\n<\/pre>\n<p>And that&#8217;s it.  So just to review the above, here is what we get:<\/p>\n<ul>\n<li>Simplified model and controller, which only cover generic, high-level overview functionality with no  details.<\/li>\n<li>A transaction specific behavior, which provides for a single place for all custom logic handling.<\/li>\n<li>A base transaction behavior, which simplifies sharing of common functionality between behaviors.<\/li>\n<li>A transaction interface super-imposed on all behaviors extending a base on.  This will guarantee that our generalized model and controller will work the same no matter which behavior is loaded.<\/li>\n<li>Business logic (such as how to process each specific transaction) is decoupled from the CakePHP framework itself.<\/li>\n<li>An evolution-friendly architecture.  Even if you already have a whole lot of code, splitting it up into manageable chunks and migrating to the above code organization is rather trivial and won&#8217;t require any drastic changes.  For example, you can leave the interface definition completely blank for the period of migration and refactoring.<\/li>\n<\/ul>\n<p>Enjoy!<\/p>\n<!-- google_ad_section_end -->\n","protected":false},"excerpt":{"rendered":"<!-- google_ad_section_start -->\n<p>CakePHP is a wonderful framework. \u00a0 Recently I proved it to myself once again (not that I need much of that proof anyway). \u00a0The problem that we had at work was a whole lot of code in once place and no obvious way of how to break that code into smaller, more manageable pieces. \u00a0MVC &hellip; <a href=\"https:\/\/mamchenkov.net\/wordpress\/2010\/02\/17\/cakephp-building-factories-with-models-and-behaviors\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">CakePHP : Building factories with models and behaviors<\/span><\/a><\/p>\n<!-- google_ad_section_end -->\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_links_to":"","_links_to_target":""},"categories":[1,18],"tags":[1537,38],"keyring_services":[],"class_list":["post-12148","post","type-post","status-publish","format-standard","hentry","category-general","category-programming","tag-cakephp","tag-php"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":14838,"url":"https:\/\/mamchenkov.net\/wordpress\/2011\/05\/08\/cakephp-graphviz-models\/","url_meta":{"origin":12148,"position":0},"title":"CakePHP GraphViz Models","author":"Leonid Mamchenkov","date":"May 8, 2011","format":false,"excerpt":"I have completely and totally rewritten my old script that generates a graph of CakePHP models and their relationships. \u00a0Instead of pasting the code in here, I pushed all of its development to GitHub where it now enjoys a new repository. \u00a0Please have a look, try it out, and let\u2026","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":24952,"url":"https:\/\/mamchenkov.net\/wordpress\/2015\/11\/10\/cakephp-3-cheatsheet\/","url_meta":{"origin":12148,"position":1},"title":"CakePHP 3 Cheatsheet","author":"Leonid Mamchenkov","date":"November 10, 2015","format":false,"excerpt":"CakePHP 3 Cheatsheet - a handy collection of code snippets for anyone new to CakePHP 3 framework and all the changes that went into this version.","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":27717,"url":"https:\/\/mamchenkov.net\/wordpress\/2017\/06\/22\/phinx-joins-cakephp\/","url_meta":{"origin":12148,"position":2},"title":"Phinx joins CakePHP!","author":"Leonid Mamchenkov","date":"June 22, 2017","format":false,"excerpt":"These are some really good news - Phinx joins CakePHP family! \u00a0If you are from a different technology stack and not familiar with these, Phinx is an excellent database migrations tool, which has been used by CakePHP framework for a while now. \u00a0The two worked great together. \u00a0Now that they\u2026","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":26051,"url":"https:\/\/mamchenkov.net\/wordpress\/2016\/04\/21\/adventure-in-composer-private-repositories\/","url_meta":{"origin":12148,"position":3},"title":"Adventure in composer private repositories","author":"Leonid Mamchenkov","date":"April 21, 2016","format":false,"excerpt":"First of all, I would like to take this opportunity and wish composer a happy birthday and many more years to come. \u00a0It's been five years, and the world of PHP has changed so drastically that not many people remember how it used to be before. I would have completely\u2026","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":15740,"url":"https:\/\/mamchenkov.net\/wordpress\/2011\/10\/18\/cakephp-2-0-released\/","url_meta":{"origin":12148,"position":4},"title":"CakePHP 2.0 released!","author":"Leonid Mamchenkov","date":"October 18, 2011","format":false,"excerpt":"I've been a bit all over the place these last few days, but I knew that this was coming shortly - CakePHP team released the new and much improved version 2.0 a couple of days ago. There are a lot of changes. And I do mean a lot. Here are\u2026","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":22103,"url":"https:\/\/mamchenkov.net\/wordpress\/2014\/07\/01\/cakephp-3-here-we-go-again\/","url_meta":{"origin":12148,"position":5},"title":"CakePHP 3, here we go again.","author":"Leonid Mamchenkov","date":"July 1, 2014","format":false,"excerpt":"As some of you might know, I'm a big fan of CakePHP framework. \u00a0I've used it on numerous projects since the beginning of times. \u00a0I've built projects small and large, migrated existing native PHP codebases to CakePHP and even survived a few major CakePHP upgrades - 1.2 to 2.0 comes\u2026","rel":"","context":"In &quot;All&quot;","block_context":{"text":"All","link":"https:\/\/mamchenkov.net\/wordpress\/category\/general\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_sharing_enabled":true,"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/posts\/12148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/comments?post=12148"}],"version-history":[{"count":0,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/posts\/12148\/revisions"}],"wp:attachment":[{"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/media?parent=12148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/categories?post=12148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/tags?post=12148"},{"taxonomy":"keyring_services","embeddable":true,"href":"https:\/\/mamchenkov.net\/wordpress\/wp-json\/wp\/v2\/keyring_services?post=12148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}