Parent-driven determination that can end in class change - oop

I'm trying to make a use from Steam API data as I like to learn on live examples, and looking at the way various statistics are returned I began to think that OOP approach would suit me best in this case.
What I'm trying to achieve is to loop through all the results, and programatically populate an array with objects of type that corresponds to the actual type of the statistic. I've tried to build myself a basic class, called Statistic, and after instantiating an object determine wheter or not it's class should change (i.e. whether or not to cast an object of type that Statistic is parent to and if so, of what type). How to do that in PHP? My solution gives me no luck, all of the objects are of type Statistic with it's 'type' property being the object I want to store alone in the array. Code:
$data = file_get_contents($url);
$data = json_decode($data);
$data = $data->playerstats;
$data = $data->stats;
$array;
for($i=0;$i<165;$i++)
{
$array[$i] = new Statistic($data[$i]);
echo "<br/>";
}
var_dump($array[10]);
And the classes' code:
<?php
class Statistic
{
public function getProperties()
{
$array["name"] = $this->name;
$array["value"] = $this->value;
$array["type"] = $this->type;
$array["className"] = __CLASS__;
return json_encode($array);
}
public function setType($x)
{
$y = explode("_",$x->name);
if($y[0]=="total")
{
if(!isset($y[2]))
{
$this->type = "General";
}
else
{
if($y[1]=="wins")
{
$this->type = new Map($x);
$this->__deconstruct();
}
if($y[1]=="kills")
{
$this->type = new Weapon($x);
$this->__deconstruct();
}
else $this->type="Other";
}
}
else $this->type = "Other";
}
function __construct($obj)
{
$this->name = $obj->name;
$this->value = $obj->value;
$this->setType($obj);
}
function __deconstruct()
{
echo "deconstructing <br/>";
return $this->type;
}
}
class Weapon extends Statistic
{
public function setType($x)
{
$y = explode("_",$x);
if($y[1]=="kills")
{
$this->type = "kills";
}
else if($y[1]=="shots")
{
$this->type = "shots";
}
else if($y[1]=="hits")
{
$this->type = "hits";
}
}
function __construct($x)
{
$name = explode("_",$x->name);
$this->name = $name[2];
$this->value = $x->value;
$this->setType($x->name);
}
function __deconstruct()
{
}
}
class Map extends Statistic
{
public function setType($x)
{
if($x[1]=="wins")
{
$this->type = "wins";
}
if($x[1]=="rounds")
{
$this->type = "rounds";
}
}
public function setName($name)
{
if(isset($name[3]))
{
if(isset($name[4]))
{
return $name[3] + " " + $name[4];
}
else return $name[3];
}
else return $name[2];
}
function __construct($x)
{
$name = explode("_",$x->name);
$this->name = $this->setName($name);
$this->value = $x->value;
$this->setType($name);
}
function __deconstruct()
{
}
}
Gives the result:
object(Statistic)#223 (3) {
["name"]=> string(18) "total_kills_deagle"
["value"]=> int(33)
["type"]=> object(Weapon)#222 (3) {
["name"]=> string(6) "deagle"
["value"]=> int(33)
["type"]=> string(5) "kills" }
}
Should that determination be driven from the loop itself, the whole advantage of having a set of functions that does everything for me and returns a ready-to-serve data is gone, since I would really have to cast different objects that aren't connected to each other, which is not the case here. How can I achieve returning objects of different type than the object itself is?

For answer your question How can I achieve returning objects of different type than the object itself is?
"Casting to change the object's type is not possible in PHP (without using a nasty extension)"
For more info: Cast the current object ($this) to a descendent class
So you can't change the class type of an instance with type of a derived class. In other world can't change instance of Static with instance of Weapon.

Related

Exclude specific products from Product Indexer in Shopware 6

We have four specific products with a massive amount of variants. When running the Product Indexer we run out of memory because of these products.
So we want to exclude these specific products from the Product Indexer Job.
My first approach was to use the ProductIndexerEvent, but the event is dispatched at the end of the handle() method :
(vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php:187),
which is probably too late.
What is the best approach to implement that behaviour?
I would advise against excluding products from being indexed. There's business logic relying on the data being indexed.
If you're confident in what you're doing and know about the consequences, you could decorate the ProductIndexer service.
<service id="Foo\MyPlugin\ProductIndexerDecorator" decorates="Shopware\Core\Content\Product\DataAbstractionLayer\ProductIndexer">
<argument type="service" id="Foo\MyPlugin\ProductIndexerDecorator.inner"/>
</service>
In the decorator you would have to deconstruct the original event, filter the WriteResult instances by excluded IDs and then pass the reconstructed event to the decorated service.
class ProductIndexerDecorator extends EntityIndexer
{
const FILTERED_IDS = ['9b180c61ddef4dad89e9f3b9fa13f3be'];
private EntityIndexer $decorated;
public function __construct(EntityIndexer $decorated)
{
$this->decorated = $decorated;
}
public function getDecorated(): EntityIndexer
{
return $this->decorated;
}
public function getName(): string
{
return $this->getDecorated()->getName();
}
public function iterate($offset): ?EntityIndexingMessage
{
return $this->getDecorated()->iterate($offset);
}
public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
{
$originalEvents = clone $event->getEvents();
if (!$originalEvents) {
return $this->getDecorated()->update($event);
}
$event->getEvents()->clear();
/** #var EntityWrittenEvent $writtenEvent */
foreach ($originalEvents as $writtenEvent) {
if ($writtenEvent->getEntityName() !== 'product') {
$event->getEvents()->add($writtenEvent);
continue;
}
$results = [];
foreach ($writtenEvent->getWriteResults() as $result) {
if (\in_array($result->getPrimaryKey(), self::FILTERED_IDS, true)) {
continue;
}
$results[] = $result;
}
$event->getEvents()->add(new EntityWrittenEvent('product', $results, $event->getContext()));
}
return $this->getDecorated()->update($event);
}
public function handle(EntityIndexingMessage $message): void
{
$data = array_diff($message->getData(), self::FILTERED_IDS);
$newMessage = new ProductIndexingMessage($data, $message->getOffset(), $message->getContext(), $message->forceQueue());
$this->getDecorated()->handle($newMessage);
}
public function getTotal(): int
{
return $this->getDecorated()->getTotal();
}
public function getOptions(): array
{
return $this->getDecorated()->getOptions();
}
}

Moving from hard-coded to SOLID principles in PHP

I am actually reading theory about clean code and SOLID principles. I know understand well that we should program to an interface and not to an implementation.
So, I actually try to apply those principles to a little part of my code. I would like to have your advice or point of view so I can know if I am going in the good direction. I'll show you my previous code and my actual so you can visualize the evolution.
To start, i had a method in my controller to check some requirements for every step of an order process (4 steps that the user have to follow in the right order => 1 then 2 then 3 and then 4)
This is my old code :
private function isAuthorizedStep($stepNumber)
{
$isStepAccessAuthorized = TRUE;
switch($stepNumber) {
case self::ORDER_STEP_TWO: // ORDER_STEP_TWO = 2
if (!($_SESSION['actualOrderStep'] >= ORDER_STEP_ONE)) {
$isStepAccessAuthorized = FALSE;
}
break;
case self::ORDER_STEP_THREE:
if (!($_SESSION['actualOrderStep'] >= ORDER_STEP_TWO)) {
$isStepAccessAuthorized = FALSE;
}
break;
...
}
return $isStepAccessAuthorized;
}
public function orderStepTwo()
{
if ($this->isAuthorizedStep(self::ORDER_STEP_TWO) {
return;
}
... // do some stuff
// after all the verifications:
$_SESSION['actualOrderStep'] = ORDER_STEP_TWO
}
Trying to fit to SOLID principles, I splited my code following this logic:
Extracting hard-coded logic from controllers to put it in classes (reusability)
Using Dependency Injection and abstraction
interface RuleInterface {
public function matches($int);
}
class StepAccessControl
{
protected $rules;
public function __construct(array $rules)
{
foreach($rules as $key => $rule) {
$this->addRule($key, $rule);
}
}
public isAccessGranted($actualOrderStep)
{
$isAccessGranted = TRUE;
foreach($this->rules as $rule) {
if (!$rule->matches($actualOrderStep) {
$isAccessGranted = FALSE;
}
}
return $isAccessGranted;
}
public function addRule($key, RuleInterface $rule)
{
$this->rules[$key] = $rule;
}
}
class OrderStepTwoRule implements RuleInterface
{
public function matches($actualStep)
{
$matches = TRUE;
if (!($actualStep >= 1)) {
$isStepAccessAuthorized = FALSE;
}
return $matches;
}
}
class StepAccessControlFactory
{
public function build($stepNumber)
{
if ($stepNumber == 1) {
...
} elseif ($stepNumber == 2) {
$orderStepTwoRule = new OrderStepTwoRule();
return new StepAcessControl($orderStepTwoRule);
}...
}
}
and then in the controller :
public function stepTwoAction()
{
$stepAccessControlFactory = new StepAccessControlFactory();
$stepTwoAccessControl = $stepAccessControlFactory(2);
if (!$stepTwoAccessControl->isAccesGranted($_SESSION['actualOrderStep'])) {
return FALSE;
}
}
I would like to know if I get the spirit and if I am on the good way :)

Using complex types in RedisTypedClient (ServiceStack Redis)

I have an example where I want to store an object into Redis.
class CyPoint
{
// Fields...
private bool _Done;
private string _Color;
private string _Position;
private long _Id;
public long Id
{
get { return _Id; }
set
{
_Id = value;
}
}
public string Position
{
get { return _Position; }
set
{
_Position = value;
}
}
public string Color
{
get { return _Color; }
set
{
_Color = value;
}
}
public bool Done
{
get { return _Done; }
set
{
_Done = value;
}
}
}
I am using this code to store the data
var redisCyPoint = redis.As<CyPoint>();
var cpt = new CyPoint
{
Id = redisCyPoint.GetNextSequence(),
Position = "new Vector3(200, 300, 0)",
Color = "new Vector3(.5f, .7f, .3f)",
};
redisCyPoint.Store(cpt);
This works as I am storing strings. But when I change position and color to Vector3 (which is: float, float, float) it only saves 0's. It seems that the Store will not work with complex types. Is this a limitation or is there a way to do this?
Struct's are serialized as a single scalar string value as returned by ToString(). You can implement custom support for Structs by implementing a constructor Vector3(string) that can populate itself from its ToString() value, or implement a static ParseJson(string) method.
Otherwise you can specify custom serializer to handle the serialization, e.g:
JsConfig<Vector3>.SerializeFn = v => "{0},{1},{2}".Fmt(v.X,v.Y,v.Z);
JsConfig<Vector3>.DeSerializeFn = s => {
var parts = s.Split(',');
return new Vector3(parts[0],parts[1],parts[2]);
};

what is the correct way to extract two sets of data from a method class

I am new to OOP and still a bit confused by the concepts
I created a class` method that will extract two sets of data from a Zend_Session_Namespace. my problem now is that I don't know how to extract these data when its pulled into another method.
It might be best if I show you what I mean:
Public function rememberLastProductSearched()
{
$session = new Zend_Session_Namespace(searchedproducts);
if ($this->getRequest()->getParam('product-searched')) {
$session->ProductSearched = $this->getRequest()->getParam('product-searched');
return " $session->ProductSearched";
} else {
if ($session->ProductSearched) {
return " $session->ProductSearched ";
}
}
if ($this->getRequest()->getParam('search-term')) {
$session->SearchTerm = $this->getRequest()->getParam('search-term');
return " $session->SearchTerm";
} else {
if ($session->SearchTerm) {
return " $session->SearchTerm ";
}
}
This method should obtain two sets of data i.e the
$session->SearchTerm
$session->ProductSearched
my confusion is this; how do I now extract both sets of data in another method call (that is within the same class).i.e
Above is my attempt to extract the information- but it did not work.
Alternatively, should I have placed the information into an array- if so, can somebody please tell me how I could have done this.
It looks like what you're trying to do is use the product-searched and search-terms from params and store them in the session if they're set, otherwise access previously saved values. It would help a bit to see how you're calling this method, but I would probably modify your code slightly to return the session namespace object instead, since that then contains the two values, regardless of whether they came from params or were there already:
public function rememberLastProductSearched()
{
$searchedProducts = new Zend_Session_Namespace('searchedproducts');
if ($this->getRequest()->getParam('product-searched')) {
$searchedProducts->ProductSearched = $this->getRequest()->getParam('product-searched');
}
if ($this->getRequest()->getParam('search-term')) {
$searchedProducts->SearchTerm = $this->getRequest()->getParam('search-term');
}
return $searchedProducts;
}
I'm assuming you have this method in a controller class, so you'd call it like this:
public function searchAction()
{
$searchedProducts = $this->rememberLastProductSearched();
// do something with the values here
}
you'll then have the two values in $searchedProducts->ProductSearched and $searchedProducts->SearchTerm.
The line "return $something;" will stop the code execution and return the value. If you want to return more than one value, you will need to either return an array or use two separate functions to return the values. If you want to return an array, you could do it this way:
public function rememberLastProductSearched() {
$returnArray = array();
$session = new Zend_Session_Namespace(searchedproducts);
if ($this->getRequest()->getParam('product-searched')) {
$session->ProductSearched = $this->getRequest()->getParam('product-searched');
$returnArray['productSearched'] = $session->ProductSearched;
} else {
if ($session->ProductSearched) {
$returnArray['productSearched'] = $session->ProductSearched;
}
}
if ($this->getRequest()->getParam('search-term')) {
$session->SearchTerm = $this->getRequest()->getParam('search-term');
$returnArray['searchTerm'] = $session->SearchTerm;
} else {
if ($session->SearchTerm) {
$returnArray['searchTerm'] = $session->SearchTerm;
}
}
return $returnArray;
}
In your controller or wherever you wanted to check for those values:
$lastSearch = $this->rememberLastProductSearched();
echo $lastSearch['productSearched']; // Product Searched
echo $lastSearch['searchTerm']; // Search terms
But it might be cleaner to use two function
public function getLastProductSearched() {
$session = new Zend_Session_Namespace(searchedproducts);
if ($this->getRequest()->getParam('product-searched')) {
$session->ProductSearched = $this->getRequest()->getParam('product-searched');
$returnValue = $session->ProductSearched;
} else {
if ($session->ProductSearched) {
$returnValue = $session->ProductSearched;
}
}
return $returnValue;
}
public function getLastSearchTerms() {
$session = new Zend_Session_Namespace(searchedproducts);
if ($this->getRequest()->getParam('search-term')) {
$session->SearchTerm= $this->getRequest()->getParam('search-term');
$returnValue = $session->SearchTerm;
} else {
if ($session->SearchTerm) {
$returnValue = $session->SearchTerm;
}
}
return $returnValue;
}
And you could use them like this:
echo $this->getLastProductSearched(); // Product Searched
echo $this->getLastSearchTerms(); // Search terms
It will make your code easier to read and debug later on. A few more notes on your code. You could avoid using nested ifs by using ||.
if ($this->getRequest()->getParam('product-searched') || $session->ProductSearched) {
$returnValue = $this->getRequest()->getParam('product-searched') || $session->ProductSearched;
}
will achieve the same thing as :
if ($this->getRequest()->getParam('product-searched')) {
$session->ProductSearched = $this->getRequest()->getParam('product-searched');
$returnArray['productSearched'] = $session->ProductSearched;
} else {
if ($session->ProductSearched) {
$returnArray['productSearched'] = $session->ProductSearched;
}
}
Hope this helps !

PHP static objects giving a fatal error

I have the following PHP code;
<?php
component_customer_init();
component_customer_go();
function component_customer_init()
{
$customer = Customer::getInstance();
$customer->set(1);
}
function component_customer_go()
{
$customer = Customer::getInstance();
$customer->get();
}
class Customer
{
public $id;
static $class = false;
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
else
{
return self::$class;
}
}
public function set($id)
{
$this->id = $id;
}
public function get()
{
print $this->id;
}
}
?>
I get the following error;
Fatal error: Call to a member function set() on a non-object in /.../classes/customer.php on line 9
Can anyone tell me why I get this error? I know this code might look strange, but it's based on a component system that I'm writing for a CMS. The aim is to be able to replace HTML tags in the template e.g.;
<!-- component:customer-login -->
with;
<?php component_customer_login(); ?>
I also need to call pre-render methods of the "Customer" class to validate forms before output is made etc.
If anyone can think of a better way, please let me know but in the first instance, I'd like to know why I get the "Fatal error" mentioned above.
Well, I think your Customer::getInstance() method is flawed. It should look like this:
...
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
return self::$class; // ADDED!!
}
else
{
return self::$class;
}
}
....
In the if(self::$class == false) branch you are creating the instance of the class, but you dont return it.
You could also rewrite it as such:
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
To make it a bit shorter.
DRY: Don't Repeat Yourself
static function getInstance()
{
if(self::$class == false)
{
self::$class = new Customer;
}
return self::$class;
}
And for Sinlgetons it is also important to prevent __clone() from being used. Making it private should solve that problem:
private function __clone() {}