未加星标

Techniques to Battle Expensive PHP Constructors

字体大小 | |
[开发(php) 所属分类 开发(php) | 发布者 店小二04 | 时间 2019 | 作者 红领巾 ] 0人收藏点击收藏

Whether you’ve made the class yourself or you’re using a pre-made SDK, there are times when the construction of an object might be expensive. Expense, in this case, pertains to memory, time, CPU cycles, basically anything that is above baseline.

Why Care About Expensive Constructors

An argument shared a lot of the time is “so what if it’s expensive to construct, you only create it when you’re going to use it.” That’s not always the case. There are a couple scenarios where that isn’t true.

First, in certain testing scenarios, you might want to mock out a specific set of functionality, but retain the actual construction of the object - or the configuration of said object.

Another reason has to do with dependency injection configuration. Depending on the container and how loosely you’ve set it up, there might be scenarios where a class gets constructed but never used. This might happen if you’re doing something like constructor injection of a service, but forget that a few methods don’t actually use it.

So there are a number of reasons why we might need to not experience that pain - the above are just a few. Let’s solve this, though.

Ideas to Battle Expensive Construction

To illustrate our expensive object, we’re creating a service called DanceParty that has a constructor that is very expensive. (Think: it has to warm up the transistors in the amplifiers, yo!)

<php class DanceParty { public function __construct() { sleep(3); // that's how long it takes } public function doFunk() { $this->applyForPermit(); // whatever you do here... } public function doMoshPit() { $this->applyForPermit(); // metal!! } protected function applyForPermit() { // I know, I'm lame. } }

And it’s called like this:

$dp = new DanceParty(); $dp->doFunk();

In our case, we’ve got an expensive statement - the sleep(3) . Every time this class is created, we have to sleep 3 seconds, a very expensive proposition.

What methods can we use to help handle this slow construction - and only have it when we need it?

Very Explicit

First, let’s talk about the “everything’s a hammer/nail” method. Here, we’re going to offload our logic to a method, and then just force every other method to call it first.

<?php class DanceParty { protected $started = false; public function __construct() {} protected function start() { if (!$this->started) { sleep(3); $this->started = true; } } public function doFunk() { $this->start(); $this->applyForPermit(); // that 70's show called... } public function doMoshPit() { $this->start(); $this->applyForPermit(); // 7 string guitars ring out! } protected function applyForPermit() { // Follow the rules! } }

Don’t forget, we’re doing this:

$dp = new DanceParty(); $dp->doFunk();

Now, when the object constructs, it doesn’t have to do that expensive thing. Perhaps we’ll never actually need the transistors to warm up because we find out we can’t afford the dance party… Ok, but seriously, this way we can offload that expense till later, when it’s needed. We also make sure we don’t keep doing the expensive operation by tracking with the $this->started variable. We didn’t need to do that with the constructor version because you don’t construct the class more than once.

The problem with this approach is that you now have to remember to call $this->start() with every method that needs access to the expensive construction. It’s not hard to do but its easy to forget to do.

Very Magic

The next thing we can do is use the magic methods in PHP to intercept the calls to our methods and do the construction if need be. We’re going to use the magic method __call which is what gets called (if it exists) when a method is called on an object that doesn’t exist.

<?php class DanceParty { protected $started = false; public function __construct() {} public function __call($name, $arguments) { if (!$this->started) { sleep(3); $this->started = true; } return $this->$name(...$arguments); } protected function doFunk() { $this->applyForPermit(); // do things that can't stop the funk } protected function doMoshPit() { $this->applyForPermit(); // somehow devolve into ring around rosie } protected function applyForPermit() { // I <3 Paperwork } }

I did this:

$dp = new DanceParty(); $dp->doFunk();

Now, you’ll notice something different here. I had to change the methods for the dance party to protected. When they’re public, __call does not get executed because the method actually exists (publicly). So, I had to make them protected for this to work. The confusing part, now, will be that other developers will see what appears to be calls to protected methods as public methods. If you want to document this, too, you’ll probably have to use the @method PHPDoc annotation… for example:

/** * Class DanceParty * @method void doFunk() * @method void doMoshPit() */ class DanceParty

Another caveat, that is actually positive, is that the __call method won’t get called repeatedly on the other method calls because they’ll be ‘found’ once inside the context of the class.

Proxy Object

Using the Proxy Design Pattern , we can proxy the expensive object with another. This functionality is similar to the previous solution, has some of the benefits and drawbacks. However, this is very useful if you can’t alter the code (if you’re using a SDK) or someone has marked your class as final and you can’t alter the functionality at all.

In this example, we’re going to revert back to our original DanceParty and then create a proxy pattern below it.

<?php class DanceParty { public function __construct() { sleep(3); // that's how long it takes } public function doFunk() { $this->applyForPermit(); // whatever you do here... } public function doMoshPit() { $this->applyForPermit(); // metal!! } protected function applyForPermit() { // I know, I'm lame. } } class DancePartyProxy { protected $instance; public function __call($name, $arguments) { if (is_null($this->instance)) { $this->instance = new DanceParty(); } return $this->instance->$name(...$arguments); } }

I call it like so:

$i = new DancePartyProxy(); $i->doFunk();

Now, we work directly with the proxy. It handles knowing if we’ve constructed the expensive object. Then, it proxies forward our requests. Again, this suffers from the fact that there are no other public methods on this proxy class, so it might be hard to know what to call. But, it doesn’t require the original class to be modified at all.

Basically, you create a new instance of the proxy class. Then, whenever you call a method signature from the original class, the __call method intercepts it, tests for object creation, then passes it forward. Simple.

Final Thoughts

These are just a few examples for handling the expense during construction of an object if you’re not guaranteed to use it. I’m sure there are more. Even better, it’d be great to refactor your code in such a way that this wasn’t necessary. However, that’s not always possible.

本文开发(php)相关术语:php代码审计工具 php开发工程师 移动开发者大会 移动互联网开发 web开发工程师 软件开发流程 软件开发工程师

代码区博客精选文章
分页:12
转载请注明
本文标题:Techniques to Battle Expensive PHP Constructors
本站链接:https://www.codesec.net/view/627855.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(php) | 评论(0) | 阅读(82)