WsdlToPhp / PackageGenerator

Generates a PHP SDK based on a WSDL, simple and powerful, WSDL to PHP

Home Page:https://providr.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

minOccurs=0 not works

GroxExMachine opened this issue · comments

Hello

DailyBudget have minOccurs=0 and nillable.

When I created request without setting DailyBudget in actual XML data of request i found, that DailyBudget was set to nil. It should not appear in request. And all elements with minOccurs=0 should not appear until they was set.

Appearance of DailyBudget leads to an error, cause it can be set only an special cases depends on other settings:

Daily budget management is available if a manual display strategy is selected in the campaign, and if the Settings parameter returned by the get method has DAILY_BUDGET_ALLOWED with the value YES. Otherwise, an attempt to set the daily budget results in an error.
https://tech.yandex.com/direct/doc/ref-v5/campaigns/update-docpage/

I can't provide extended tests for this issue until Monday.

Hi,

Until now, I always handled it by overriding the SoapClient class::__doRequest method in order to remove each empty tag that would be nillable. Using your own SoapClient class is possible with the option --soapclient. So you'll then have to do the code by yourself within you SoapClient class in order to browse the generated XML request and remove each empty tag.

Not sending the properties means that the object passed to the final soap method should not contain the nillable properties that have a null value. This would mean that we could need to browse the parameters recursively and unset each nillable property with a null value. Am I wrong?
If it has to be handled within the generated classes, it would mean that before passing the parameters to the final soap method/operation in the Service class such as in $this->setResult(self::getSoapClient()->add($parameters));, it would require adding a method such as $this->setResult(self::getSoapClient()->add($this->cleanParameter($parameters)));. It would need to ba carreful with method that accept multiple parameters and so on.

Do you have any other idea?

For now I just unset unwanted property thanks to all of them are public.

The unsetting can be done in setter. Like in setCampaigns(...) we are validating is array contains only valid CampaignAddItem objects. So, there we can put unsetter for any property of CampaignAddItem that have minOccurs=0 and nillable. This will lead to immediate removing of unwanted properties.

What happends if the property is unset (on instanciation for example as each property is defined with an empty array) but after that the user wants to set the property with a new value? It recreates the property?

unset($campaignUpdateItem->DailyBudget);
$campaignUpdateItem->setDailyBudget($Campaign->getDailyBudget());

This works well.

The unsetting can be done in setter. Like in setCampaigns(...) we are validating is array contains only valid CampaignAddItem objects. So, there we can put unsetter for any property of CampaignAddItem that have minOccurs=0 and nillable. This will lead to immediate removing of unwanted properties.

If I do understand what you're saying, you propose to add the call to an unsetter method in the foreach

public function setCampaigns(array $campaigns = array())
{
    foreach($campaigns as $item) {
        if (!$item instanceof \StructType\CampaignUpdateItem) {
            throw new \InvalidArgumentException(sprintf('The Campaigns property can only contain items of \StructType\CampaignUpdateItem, "%s" given', is_object($item) ? get_class($item) : gettype($item)), __LINE__);
        }
    }
    $this->Campaigns = $campaigns;
    return $this;
}

that would clean the undefined properties (having value = null as value and that are nillable+minOccurs=0)? This would need to be recursive then (even if normally, unsetter method would be called when we create/update each object that contain a nillable property).

What about properties that are not array type? Do we need to add a test in the setter anytime in order to unset the property if the value is null? This would be along the validation we're discussing.

This behavior would be ok as soon as the user uses the setter. As you mentionned to me some time ago, properties are public so I saw many users not using both the setter and the getter. I personally always use these methods even if the properties are public. I say that to show that even if we had a unsetter, it may not be used if the user does not use the setter at first place. YOU will but how to say to users that it is advised to use the setter and the getter instead of accessing the properties directly?

If I do understand what you're saying, you propose to add the call to an unsetter method in the foreach

Yes.

This would need to be recursive then (even if normally, unsetter method would be called when we create/update each object that contain a nillable property)

My thoughts - it should not be recursive. We will use unsetter normally, so why do we need it to be recursive?

What about properties that are not array type? Do we need to add a test in the setter anytime in order to unset the property if the value is null? This would be along the validation we're discussing.

Will see it, when will use it extensively. Can provide more info later. But technically - yes, but it should works only for object setters.

This behavior would be ok as soon as the user uses the setter.... YOU will but how to say to users that it is advised to use the setter and the getter instead of accessing the properties directly?

The use of getter and setter is in best practices. If programmers do not follow it, they did not understand them, did not know them or just knows, what are they doing. In all these cases the loosing of some advanced features of generator stays on them.
And we can explain in manual, that properties are public because of XXXX (I asked you about it) and shouldn't be used directly. And this should be somewhere in the middle of manual and, additionally, in FAQ.

@GroxExMachine Ok, no recursivity indeed,

One last question, in which class (AdGroups or Campaigns WSDL? and which ComplexType?) does did you face the case where you had to unset the array property? I searched an element with maxOccurs="unbounded" and nillable="true" and minOccurs="0" but I did not find any... After reading again the previous comments from #48 (comment), I'm not sure of what you were talking about

Tell me if the way I implemented it works/is as you had imagined it,

thx

in which class (AdGroups or Campaigns WSDL? and which ComplexType?) does did you face the case where you had to unset the array property?

Mikaël it is here:
Campaigns WSDL - StructType\AddRequest() and StructType\UpdateRequest().
They are getting StructType\CampaignAddItem() and StructType\CampaignUpdateItem().

I checked implementation in branch-48.

First of all I found that CampaignAddItem contain in \Campaigns\StructType\TimeTargetingAdd. And it was not there earlier. But it was in my documents (can't check previous WSDL docs) so, I assume, that some bug may be fixed.

Tell me if the way I implemented it works/is as you had imagined it,

No. I will explain on examples.

    /**
     * Set WeeklySpendLimit value
     * This property is removable from request (nillable=true+minOccurs=0), therefore
     * if the value assigned to this property is null, it is removed from this object
     * @param int $weeklySpendLimit
     * @return
     * \AppBundle\Providers\YandexDirect\Campaigns\StructType\StrategyAverageCpa
     */
    public function setWeeklySpendLimit($weeklySpendLimit = null)
    {
        if (is_null($weeklySpendLimit)) {
            unset($this->weeklySpendLimit);
        } else {
            $this->WeeklySpendLimit = $weeklySpendLimit;
        }
        return $this;
    }

Here we are setting WeeklySpendLimit that is not an object. It can be setted several times, before will come to request.
My suggestion was about to check an objects. If an object contain unnecessary properties - remove it.
Example.

In my case I do not need and I am not allowed to have DailyBudget in request. CampaignUpdateItem->DailyBudget.

So i use unset($campaignAddItem->DailyBudget);

When I think there should be checking and unsetting? In AddRequest->setCampaigns()

$request = new YandexDirect\Campaigns\StructType\AddRequest();
$request->setCampaigns($Campaigns);

Here, when we checking if $campaigns contains only proper type, we should check every item in array and unset unwanted(==null + nillable="true" + minOccurs="0") values

/**
     * Set Campaigns value
     * @throws \InvalidArgumentException
     * @param \AppBundle\Providers\YandexDirect\Campaigns\StructType\CampaignAddItem[]
     * $campaigns
     * @return \AppBundle\Providers\YandexDirect\Campaigns\StructType\AddRequest
     */
    public function setCampaigns(array $campaigns = array())
    {
        foreach ($campaigns as $addRequestCampaignsItem) {
            if (!$addRequestCampaignsItem instanceof \AppBundle\Providers\YandexDirect\Campaigns\StructType\CampaignAddItem) {
                throw new \InvalidArgumentException(sprintf('The Campaigns property can only contain items of \AppBundle\Providers\YandexDirect\Campaigns\StructType\CampaignAddItem, "%s" given', is_object($addRequestCampaignsItem) ? get_class($addRequestCampaignsItem) : gettype($addRequestCampaignsItem)), __LINE__);
            }
// iterate all properties in $addRequestCampaignsItem, check for null+nillable+minOccurs=0 and unset it 
        }
        $this->Campaigns = $campaigns;
        return $this;
    }

And in any case we setting an object we should do same.

From what I understand and from what I've done, I think this should be working.

Here's my understanding:

  • you create seveval CampaignAddItem objects
  • you create an array containnig the CampaignAddItem objects
  • you call the setCampaigns method: this is where you would like to call something like $addRequestCampaignsItem->unsetEmptyProperties(), right? In order to remove properties from the CampaignAddItem object that are assigned a null value and are nillable="true" + minOccurs=0, right?

This is where I wrongly expressed myself by saying that it would need a recursive method, in fact it would need a method that acts afterward instead of before all. If I well understood, it would do what is done by the modifications I've done.

Indeed, if you look to the generated method (with a bug I just saw "unset($this->dailyBudget)" instead of "unset($this->DailyBudget)"):

/**
 * Set DailyBudget value
 * This property is removable from request (nillable=true+minOccurs=0), therefore
 * if the value assigned to this property is null, it is removed from this object
 * @param \StructType\DailyBudget $dailyBudget
 * @return \StructType\CampaignUpdateItem
 */
public function setDailyBudget(\StructType\DailyBudget $dailyBudget = null)
{
    if (is_null($dailyBudget)) {
        unset($this->dailyBudget);
    } else {
        $this->DailyBudget = $dailyBudget;
    }
    return $this;
}

called at the object instanciation such as

public function __construct(
    $id = null,
    $name = null,
    $startDate = null,
    \StructType\DailyBudget $dailyBudget = null,
    $endDate = null,
    \ArrayType\ArrayOfString $negativeKeywords = null,
    \ArrayType\ArrayOfString $blockedIps = null,
    \ArrayType\ArrayOfString $excludedSites = null,
    \StructType\TextCampaignUpdateItem $textCampaign = null,
    \StructType\MobileAppCampaignUpdateItem $mobileAppCampaign = null,
    \StructType\DynamicTextCampaignUpdateItem $dynamicTextCampaign = null)

The property DailyBudget is removed from the object at its instanciation as the default value is null. So if you decide to set a valid value, it will be added back. Otherwise, the property won't exist and should not be sent in the request, am I wrong?

Let me know

You understood me right.

propery is removed from the object at its instanciation as the default value is null. So if you decide to set a valid value, it will be added back.

It is a good way, better, than I proposed. I really like it.
Will test it later.

@GroxExMachine any feedback? 😄

@mikaelcom just wait. My project is in active development :)