WorkflowInstance   F
last analyzed

Complexity

Total Complexity 91

Size/Duplication

Total Lines 681
Duplicated Lines 1.91 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 3
Bugs 3 Features 0
Metric Value
wmc 91
c 3
b 3
f 0
lcom 1
cbo 22
dl 13
loc 681
rs 1.3231

29 Methods

Rating   Name   Duplication   Size   Complexity  
C getCMSFields() 0 44 7
A fieldLabels() 0 9 1
A onBeforeWrite() 0 11 2
B updateWorkflow() 0 24 6
A getTarget() 0 11 4
A Target() 0 3 1
C beginWorkflow() 0 28 7
C execute() 0 44 7
A checkTransitions() 0 8 3
B performTransition() 0 29 2
A getAssignedMembers() 13 13 2
A canView() 0 17 4
A canEdit() 0 6 2
A canDelete() 0 9 3
C userHasAccess() 0 24 9
A canViewTarget() 0 7 2
A validTransitions() 0 10 1
A getWorkflowFields() 0 15 1
A getFrontEndWorkflowFields() 0 8 1
B getFrontEndWorkflowActions() 0 34 5
A getFrontEndDataObject() 0 6 1
A getFrontEndRequiredFields() 0 6 1
A setFrontendFormRequirements() 0 4 1
A doFrontEndAction() 0 4 1
A getVersionedConnection() 0 12 2
A getCurrentAction() 0 11 2
C getMostRecentActionForUser() 0 28 7
A canEditTarget() 0 5 3
A canPublishTarget() 0 5 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WorkflowInstance often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WorkflowInstance, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * A WorkflowInstance is created whenever a user 'starts' a workflow.
4
 *
5
 * This 'start' is triggered automatically when the user clicks the relevant
6
 * button (eg 'apply for approval'). This creates a standalone object
7
 * that maintains the state of the workflow process.
8
 *
9
 * @method WorkflowDefinition Definition()
10
 * @method WorkflowActionInstance CurrentAction()
11
 * @method Member Initiator()
12
 *
13
 * @author  [email protected]
14
 * @license BSD License (http://silverstripe.org/bsd-license/)
15
 * @package advancedworkflow
16
 */
17
class WorkflowInstance extends DataObject {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
18
19
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
20
		'Title'				=> 'Varchar(128)',
21
		'WorkflowStatus'	=> "Enum('Active,Paused,Complete,Cancelled','Active')",
22
		'TargetClass'		=> 'Varchar(64)',
23
		'TargetID'			=> 'Int',
24
	);
25
26
	private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
27
		'Definition'    => 'WorkflowDefinition',
28
		'CurrentAction' => 'WorkflowActionInstance',
29
		'Initiator'		=> 'Member',
30
	);
31
32
	private static $has_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
		'Actions'		=> 'WorkflowActionInstance',
34
	);
35
36
	/**
37
	 * The list of users who are responsible for performing the current WorkflowAction
38
	 *
39
	 * @var array
40
	 */
41
	private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
42
		'Users'			=> 'Member',
43
		'Groups'		=> 'Group'
44
	);
45
46
	private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
47
		'Title',
48
		'WorkflowStatus',
49
		'Created'
50
	);
51
52
	/**
53
	 * If set to true, actions that cannot be executed by the user will not show
54
	 * on the frontend (just like the backend). 
55
	 *
56
	 * @var boolean
57
	 */
58
	private static $hide_disabled_actions_on_frontend = false;
0 ignored issues
show
Unused Code introduced by
The property $hide_disabled_actions_on_frontend is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
59
60
	/**
61
	 * Get the CMS view of the instance. This is used to display the log of
62
	 * this workflow, and options to reassign if the workflow hasn't been
63
	 * finished yet
64
	 *
65
	 * @return \FieldList
66
	 */
67
	public function getCMSFields() {
68
		$fields = new FieldList();
69
70
		if (Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
71
			if ($this->WorkflowStatus == 'Paused' || $this->WorkflowStatus == 'Active') {
0 ignored issues
show
Documentation introduced by
The property WorkflowStatus does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
72
				$cmsUsers = Member::mapInCMSGroups();
73
74
				$fields->push(new HiddenField('DirectUpdate', '', 1));
75
76
				$fields->push(new HeaderField('InstanceReassignHeader',_t('WorkflowInstance.REASSIGN_HEADER', 'Reassign workflow')));
77
				$fields->push(new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers));
78
				$fields->push(new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), 'Group'));
79
80
			}
81
		}
82
83
		if ($this->canEdit()) {
84
			$action = $this->CurrentAction();
85
			if ($action->exists()) {
86
				$actionFields = $this->getWorkflowFields();
87
				$fields->merge($actionFields);
88
89
				$transitions = $action->getValidTransitions();
90
				if ($transitions) {
91
					$fields->replaceField('TransitionID', DropdownField::create("TransitionID", "Next action", $transitions->map()));
92
				}
93
			}
94
		}
95
96
		$items = WorkflowActionInstance::get()->filter(array(
97
			'Finished'		=> 1,
98
			'WorkflowID'	=> $this->ID
99
		));
100
101
		$grid = new GridField(
102
			'Actions',
103
			_t('WorkflowInstance.ActionLogTitle','Log'),
104
			$items
105
		);
106
107
		$fields->push($grid);
108
109
		return $fields;
110
	}
111
112
	public function fieldLabels($includerelations = true) {
113
		$labels = parent::fieldLabels($includerelations);
114
		$labels['Title'] = _t('WorkflowInstance.TitleLabel', 'Title');
115
		$labels['WorkflowStatus'] = _t('WorkflowInstance.WorkflowStatusLabel', 'Workflow Status');
116
		$labels['TargetClass'] = _t('WorkflowInstance.TargetClassLabel', 'Target Class');
117
		$labels['TargetID'] = _t('WorkflowInstance.TargetIDLabel', 'Target');
118
119
		return $labels;
120
	}
121
122
	/**
123
	 * See if we've been saved in context of managing the workflow directly
124
	 */
125
	public function onBeforeWrite() {
126
		parent::onBeforeWrite();
127
128
		$vars = $this->record;
129
130
		if (isset($vars['DirectUpdate'])) {
131
			// Unset now so that we don't end up in an infinite loop!
132
			unset($this->record['DirectUpdate']);
133
			$this->updateWorkflow($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<string,?,{"DirectUpdate":"?"}>, but the function expects a object<type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
134
		}
135
	}
136
137
	/**
138
	 * Update the current state of the workflow
139
	 *
140
	 * Typically, this is triggered by someone modifiying the workflow instance via the modeladmin form
141
	 * side of things when administering things, such as re-assigning or manually approving a stuck workflow
142
	 *
143
	 * Note that this is VERY similar to AdvancedWorkflowExtension::updateworkflow
144
	 * but without the formy bits. These two implementations should PROBABLY
145
	 * be merged
146
	 *
147
	 * @todo refactor with AdvancedWorkflowExtension
148
	 *
149
	 * @param type $data
150
	 * @return
151
	 */
152
	public function updateWorkflow($data) {
153
		$action = $this->CurrentAction();
154
155
		if (!$this->getTarget() || !$this->getTarget()->canEditWorkflow()) {
156
			return;
157
		}
158
159
		$allowedFields = $this->getWorkflowFields()->saveableFields();
160
		unset($allowedFields['TransitionID']);
161
		foreach ($allowedFields as $field) {
162
			$fieldName = $field->getName();
163
			$action->$fieldName = $data[$fieldName];
164
		}
165
		$action->write();
166
167
		$svc = singleton('WorkflowService');
168
		if (isset($data['TransitionID']) && $data['TransitionID']) {
169
			$svc->executeTransition($this->getTarget(), $data['TransitionID']);
170
		} else {
171
			// otherwise, just try to execute the current workflow to see if it
172
			// can now proceed based on user input
173
			$this->execute();
174
		}
175
	}
176
177
	/**
178
	 * Get the target-object that this WorkflowInstance "points" to.
179
	 *
180
	 * Workflows are not restricted to being active on SiteTree objects,
181
	 * so we need to account for being attached to anything.
182
	 *
183
	 * Sets Versioned::set_reading_mode() to allow fetching of Draft _and_ Published
184
	 * content.
185
	 *
186
	 * @return (null | DataObject)
0 ignored issues
show
Documentation introduced by
The doc-type (null could not be parsed: Expected ")" at position 2, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
187
	 */
188
	public function getTarget() {
189
		if($this->TargetID && $this->TargetClass) {
0 ignored issues
show
Documentation introduced by
The property TargetID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property TargetClass does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
190
			$versionable = Injector::inst()->get($this->TargetClass)->has_extension('Versioned');
0 ignored issues
show
Documentation introduced by
The property TargetClass does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
191
			if($versionable) {
192
				Versioned::set_reading_mode("Stage.Stage");
193
			}
194
195
			// Default
196
			return DataObject::get_by_id($this->TargetClass, $this->TargetID);
0 ignored issues
show
Documentation introduced by
The property TargetClass does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property TargetID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
197
		}
198
	}
199
200
	/**
201
	 *
202
	 * @see {@link {$this->getTarget()}
203
	 * @return (null | DataObject)
0 ignored issues
show
Documentation introduced by
The doc-type (null could not be parsed: Expected ")" at position 2, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
204
	 */
205
	public function Target() {
206
		return $this->getTarget();
207
	}
208
209
	/**
210
	 * Start a workflow based on a particular definition for a particular object.
211
	 *
212
	 * The object is optional; if not specified, it is assumed that this workflow
213
	 * is simply a task based checklist type of workflow.
214
	 *
215
	 * @param WorkflowDefinition $definition
216
	 * @param DataObject $for
217
	 */
218
	public function beginWorkflow(WorkflowDefinition $definition, DataObject $for=null) {
219
		if(!$this->ID) {
220
			$this->write();
221
		}
222
223
		if ($for && ($for->hasExtension('WorkflowApplicable') || $for->hasExtension('FileWorkflowApplicable'))) {
224
			$this->TargetClass = ClassInfo::baseDataClass($for);
0 ignored issues
show
Documentation introduced by
The property TargetClass does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
225
			$this->TargetID = $for->ID;
0 ignored issues
show
Documentation introduced by
The property TargetID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
226
		}
227
228
		// lets create the first WorkflowActionInstance.
229
		$action = $definition->getInitialAction()->getInstanceForWorkflow();
230
		$action->WorkflowID   = $this->ID;
0 ignored issues
show
Documentation introduced by
The property WorkflowID does not exist on object<WorkflowActionInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
231
		$action->write();
232
233
		$title = $for && $for->hasField('Title') ?
234
				sprintf(_t('WorkflowInstance.TITLE_FOR_DO', '%s - %s'), $definition->Title, $for->Title) :
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<WorkflowDefinition>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
235
				sprintf(_t('WorkflowInstance.TITLE_STUB', 'Instance #%s of %s'), $this->ID, $definition->Title);
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<WorkflowDefinition>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
236
237
		$this->Title		   = $title;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
238
		$this->DefinitionID    = $definition->ID;
0 ignored issues
show
Documentation introduced by
The property DefinitionID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
239
		$this->CurrentActionID = $action->ID;
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
240
		$this->InitiatorID     = Member::currentUserID();
0 ignored issues
show
Documentation introduced by
The property InitiatorID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
241
		$this->write();
242
243
		$this->Users()->addMany($definition->Users());
0 ignored issues
show
Documentation Bug introduced by
The method Users does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method Users does not exist on object<WorkflowDefinition>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
244
		$this->Groups()->addMany($definition->Groups());
0 ignored issues
show
Documentation Bug introduced by
The method Groups does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method Groups does not exist on object<WorkflowDefinition>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
245
	}
246
247
	/**
248
	 * Execute this workflow. In rare cases this will actually execute all actions,
249
	 * but typically, it will stop and wait for the user to input something
250
	 *
251
	 * The basic process is to get the current action, and see whether it has been finished
252
	 * by some process, if not it attempts to execute it.
253
	 *
254
	 * If it has been finished, we check to see if there's some transitions to follow. If there's
255
	 * only one transition, then we execute that immediately.
256
	 *
257
	 * If there's multiple transitions, we just stop and wait for the user to manually
258
	 * trigger a transition.
259
	 *
260
	 * If there's no transitions, we make the assumption that we've finished the workflow and
261
	 * mark it as such.
262
	 *
263
	 *
264
	 */
265
	public function execute() {
266
		if (!$this->CurrentActionID) {
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
267
			throw new Exception(
268
				sprintf(_t('WorkflowInstance.EXECUTE_EXCEPTION', 'Attempted to start an invalid workflow instance #%s!'), $this->ID)
269
			);
270
		}
271
272
		$action     = $this->CurrentAction();
273
		$transition = false;
274
275
		// if the action has already finished, it means it has either multiple (or no
276
		// transitions at the time), so a subsequent check should be run.
277
		if($action->Finished) {
0 ignored issues
show
Documentation introduced by
The property Finished does not exist on object<WorkflowActionInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
278
			$transition = $this->checkTransitions($action);
279
		} else {
280
			$result = $action->BaseAction()->execute($this);
0 ignored issues
show
Documentation Bug introduced by
The method BaseAction does not exist on object<WorkflowActionInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
281
282
			// if the action was successful, then the action has finished running and
283
			// next transition should be run (if only one).
284
			// input.
285
			if($result) {
286
				$action->MemberID = Member::currentUserID();
0 ignored issues
show
Documentation introduced by
The property MemberID does not exist on object<WorkflowActionInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
287
				$action->Finished = true;
0 ignored issues
show
Documentation introduced by
The property Finished does not exist on object<WorkflowActionInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
288
				$action->write();
289
				$transition = $this->checkTransitions($action);
290
			}
291
		}
292
293
		// if the action finished, and there's only one available transition then
294
		// move onto that step - otherwise check if the workflow has finished.
295
		if($transition) {
296
			$this->performTransition($transition);
297
		} else {
298
			// see if there are any transitions available, even if they are not valid.
299
			if($action->Finished && !count($action->BaseAction()->Transitions())) {
0 ignored issues
show
Documentation introduced by
The property Finished does not exist on object<WorkflowActionInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation Bug introduced by
The method BaseAction does not exist on object<WorkflowActionInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
300
				$this->WorkflowStatus  = 'Complete';
0 ignored issues
show
Documentation introduced by
The property WorkflowStatus does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
301
				$this->CurrentActionID = 0;
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
302
			} else {
303
				$this->WorkflowStatus = 'Paused';
0 ignored issues
show
Documentation introduced by
The property WorkflowStatus does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
304
			}
305
306
			$this->write();
307
		}
308
	}
309
310
	/**
311
	 * Evaluate all the transitions of an action and determine whether we should
312
	 * follow any of them yet.
313
	 *
314
	 * @param  WorkflowActionInstance $action
315
	 * @return WorkflowTransition
316
	 */
317
	protected function checkTransitions(WorkflowActionInstance $action) {
318
		$transitions = $action->getValidTransitions();
319
		// if there's JUST ONE transition, then we need should
320
		// immediately follow it.
321
		if ($transitions && $transitions->count() == 1) {
322
			return $transitions->First();
323
		}
324
	}
325
326
	/**
327
	 * Transitions a workflow to the next step defined by the given transition.
328
	 *
329
	 * After transitioning, the action is 'executed', and next steps
330
	 * determined.
331
	 *
332
	 * @param WorkflowTransition $transition
333
	 */
334
	public function performTransition(WorkflowTransition $transition) {
335
		// first make sure that the transition is valid to execute!
336
		$action          = $this->CurrentAction();
337
		$allTransitions  = $action->BaseAction()->Transitions();
0 ignored issues
show
Documentation Bug introduced by
The method BaseAction does not exist on object<WorkflowActionInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
338
339
		$valid = $allTransitions->find('ID', $transition->ID);
340
		if (!$valid) {
341
			throw new Exception (
342
				sprintf(_t('WorkflowInstance.WORKFLOW_TRANSITION_EXCEPTION', 'Invalid transition state for action #%s'), $action->ID)
343
			);
344
345
		}
346
347
		$action->actionComplete($transition);
348
349
		$definition = DataObject::get_by_id('WorkflowAction', $transition->NextActionID);
0 ignored issues
show
Documentation introduced by
The property NextActionID does not exist on object<WorkflowTransition>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
350
		$action = $definition->getInstanceForWorkflow();
351
		$action->WorkflowID   = $this->ID;
352
		$action->write();
353
354
		$this->CurrentActionID = $action->ID;
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
355
		$this->write();
356
		$this->components = array(); // manually clear the has_one cache
357
358
		$action->actionStart($transition);
359
360
		$transition->extend('onTransition');
361
		$this->execute();
362
	}
363
364
	/**
365
	 * Returns a list of all Members that are assigned to this instance, either directly or via a group.
366
	 *
367
	 * @todo   This could be made more efficient.
368
	 * @return ArrayList
369
	 */
370 View Code Duplication
	public function getAssignedMembers() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
371
		$list    = new ArrayList();
372
		$groups  = $this->Groups();
0 ignored issues
show
Documentation Bug introduced by
The method Groups does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
373
374
		$list->merge($this->Users());
0 ignored issues
show
Documentation Bug introduced by
The method Users does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
375
376
		foreach($groups as $group) {
377
			$list->merge($group->Members());
378
		}
379
380
		$list->removeDuplicates();
381
		return $list;
382
	}
383
384
	/**
385
	 *
386
	 * @param \Member $member
387
	 * @return boolean
388
	 */
389
	public function canView($member=null) {
390
		$extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 389 can be null; however, DataObject::extendedCan() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
391
		if($extended !== null) return $extended;
392
393
		$hasAccess = $this->userHasAccess($member);
394
		/*
395
		 * If the next action is AssignUsersToWorkflowAction, execute() resets all user+group relations.
396
		 * Therefore current user no-longer has permission to view this WorkflowInstance in PendingObjects Gridfield, even though;
397
		 * - She had permissions granted via the workflow definition to run the preceeding Action that took her here.
398
		 */
399
		if(!$hasAccess) {
400
			if($this->getMostRecentActionForUser($member)) {
401
				return true;
402
			}
403
		}
404
		return $hasAccess;
405
	}
406
407
	/**
408
	 *
409
	 * @param \Member $member
410
	 * @return boolean
411
	 */
412
	public function canEdit($member = null) {
413
		$extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 412 can be null; however, DataObject::extendedCan() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
414
		if($extended !== null) return $extended;
415
416
		return $this->userHasAccess($member);
417
	}
418
419
	/**
420
	 *
421
	 * @param \Member $member
422
	 * @return boolean
423
	 */
424
	public function canDelete($member = null) {
425
		$extended = $this->extendedCan(__FUNCTION__, $member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 424 can be null; however, DataObject::extendedCan() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
426
		if($extended !== null) return $extended;
427
428
		if(Permission::checkMember($member, "DELETE_WORKFLOW")) {
429
			return true;
430
		}
431
		return false;
432
	}
433
434
	/**
435
	 * Checks whether the given user is in the list of users assigned to this
436
	 * workflow
437
	 *
438
	 * @param $memberID
439
	 */
440
	protected function userHasAccess($member) {
441
		if (!$member) {
442
			if (!Member::currentUserID()) {
443
				return false;
444
			}
445
			$member = Member::currentUser();
446
		}
447
448
		if(Permission::checkMember($member, "ADMIN")) {
449
			return true;
450
		}
451
452
		// This method primarily "protects" access to a WorkflowInstance, but assumes access only to be granted to users assigned-to that WorkflowInstance.
453
		// However; lowly authors (users entering items into a workflow) are not assigned - but we still wish them to see their submitted content.
454
		$inWorkflowGroupOrUserTables = ($member->inGroups($this->Groups()) || $this->Users()->find('ID', $member->ID));
0 ignored issues
show
Documentation Bug introduced by
The method Groups does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
Documentation Bug introduced by
The method Users does not exist on object<WorkflowInstance>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
455
		// This method is used in more than just the ModelAdmin. Check for the current controller to determine where canView() expectations differ
456
		if($this->getTarget() && Controller::curr()->getAction() == 'index' && !$inWorkflowGroupOrUserTables) {
457
			if($this->getVersionedConnection($this->getTarget()->ID, $member->ID)) {
458
				return true;
459
			}
460
			return false;
461
		}
462
		return $inWorkflowGroupOrUserTables;
463
	}
464
465
	/**
466
	 * Can documents in the current workflow state be edited?
467
	 */
468
	public function canEditTarget() {
469
		if ($this->CurrentActionID && ($target = $this->getTarget())) {
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
470
			return $this->CurrentAction()->canEditTarget($target);
471
		}
472
	}
473
474
	/**
475
	 * Does this action restrict viewing of the document?
476
	 *
477
	 * @return boolean
478
	 */
479
	public function canViewTarget() {
480
		$action = $this->CurrentAction();
481
		if ($action) {
482
			return $action->canViewTarget($this->getTarget());
0 ignored issues
show
Bug introduced by
It seems like $this->getTarget() can be null; however, canViewTarget() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
483
		}
484
		return true;
485
	}
486
487
	/**
488
	 * Does this action restrict the publishing of a document?
489
	 *
490
	 * @return boolean
491
	 */
492
	public function canPublishTarget() {
493
		if ($this->CurrentActionID && ($target = $this->getTarget())) {
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
494
			return $this->CurrentAction()->canPublishTarget($target);
495
		}
496
	}
497
498
	/**
499
	 * Get the current set of transitions that are valid for the current workflow state,
500
	 * and are available to the current user.
501
	 *
502
	 * @return array
503
	 */
504
	public function validTransitions() {
505
		$action    = $this->CurrentAction();
506
		$transitions = $action->getValidTransitions();
507
508
		// Filter by execute permission
509
		$self = $this;
510
		return $transitions->filterByCallback(function($transition) use ($self) {
511
			return $transition->canExecute($self);
512
		});
513
	}
514
515
	/* UI RELATED METHODS */
516
517
	/**
518
	 * Gets fields for managing this workflow instance in its current step
519
	 *
520
	 * @return FieldList
521
	 */
522
	public function getWorkflowFields() {
523
		$action    = $this->CurrentAction();
524
		$options   = $this->validTransitions();
525
		$wfOptions = $options->map('ID', 'Title', ' ');
0 ignored issues
show
Unused Code introduced by
The call to ArrayList::map() has too many arguments starting with ' '.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Unused Code introduced by
$wfOptions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
526
		$fields    = new FieldList();
527
528
		$fields->push(new HeaderField('WorkflowHeader', $action->Title));
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<WorkflowActionInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
529
530
		$fields->push(HiddenField::create('TransitionID', ''));
531
		// Let the Active Action update the fields that the user can interact with so that data can be
532
		// stored for the workflow.
533
		$action->updateWorkflowFields($fields);
534
535
		return $fields;
536
	}
537
538
	/**
539
	 * Gets Front-End form fields from current Action
540
	 *
541
	 * @return FieldList
542
	 */
543
	public function getFrontEndWorkflowFields() {
544
		$action = $this->CurrentAction();
545
546
		$fields = new FieldList();
547
		$action->updateFrontEndWorkflowFields($fields);
548
549
		return $fields;
550
	}
551
552
	/**
553
	 * Gets Transitions for display as Front-End Form Actions
554
	 *
555
	 * @return FieldList
556
	 */
557
	public function getFrontEndWorkflowActions() {
558
		$action    = $this->CurrentAction();
559
		$options   = $action->getValidTransitions();
560
		$actions   = new FieldList();
561
562
		$hide_disabled_actions_on_frontend = $this->config()->hide_disabled_actions_on_frontend; 
563
564
		foreach ($options as $option) {
565
			$btn = new FormAction("transition_{$option->ID}", $option->Title);
566
567
			// add cancel class to passive actions, this prevents js validation (using jquery.validate)
568
			if($option->Type == 'Passive'){
569
				$btn->addExtraClass('cancel');
570
			}
571
572
			// disable the button if canExecute() returns false
573
			if(!$option->canExecute($this))
574
			{
575
				if ($hide_disabled_actions_on_frontend)
576
				{
577
					continue;
578
				}
579
580
				$btn = $btn->performReadonlyTransformation();
581
				$btn->addExtraClass('hide');
582
			}
583
584
			$actions->push($btn);
585
		}
586
587
		$action->updateFrontEndWorkflowActions($actions);
588
589
		return $actions;
590
	}
591
592
	/**
593
	 * Gets Front-End DataObject
594
	 *
595
	 * @return DataObject
596
	 */
597
	public function getFrontEndDataObject() {
598
		$action = $this->CurrentAction();
599
		$obj = $action->getFrontEndDataObject();
600
601
		return $obj;
602
	}
603
604
	/**
605
	 * Gets Front-End DataObject
606
	 *
607
	 * @return DataObject
608
	 */
609
	public function getFrontEndRequiredFields() {
610
		$action = $this->CurrentAction();
611
		$validator = $action->getRequiredFields();
612
613
		return $validator;
614
	}
615
616
	public function setFrontendFormRequirements() {
617
		$action = $this->CurrentAction();
618
		$action->setFrontendFormRequirements();
619
	}
620
621
	public function doFrontEndAction(array $data, Form $form, SS_HTTPRequest $request) {
622
		$action = $this->CurrentAction();
623
		$action->doFrontEndAction($data, $form, $request);
624
	}
625
626
	/*
627
	 * We need a way to "associate" an author with this WorkflowInstance and its Target() to see if she is "allowed" to view WorkflowInstances within GridFields
628
	 * @see {@link $this->userHasAccess()}
629
	 *
630
	 * @param number $recordID
631
	 * @param number $userID
632
	 * @param number $wasPublished
633
	 * @return boolean
634
	 */
635
	public function getVersionedConnection($recordID, $userID, $wasPublished = 0) {
636
		// Turn this into an array and run through implode()
637
		$filter = "RecordID = {$recordID} AND AuthorID = {$userID} AND WasPublished = {$wasPublished}";
638
		$query = new SQLQuery();
0 ignored issues
show
Deprecated Code introduced by
The class SQLQuery has been deprecated with message: since version 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
639
		$query->setFrom('"SiteTree_versions"')->setSelect('COUNT("ID")')->setWhere($filter);
640
		$query->firstRow();
641
		$hasAuthored = $query->execute();
642
		if($hasAuthored) {
643
			return true;
644
		}
645
		return false;
646
	}
647
648
	/*
649
	 * Simple method to retrieve the current action, on the current WorkflowInstance
650
	 */
651
	public function getCurrentAction() {
652
		$join = '"WorkflowAction"."ID" = "WorkflowActionInstance"."BaseActionID"';
653
		$action = WorkflowAction::get()
654
					->leftJoin('WorkflowActionInstance',$join)
655
					->where('"WorkflowActionInstance"."ID" = '.$this->CurrentActionID)
0 ignored issues
show
Documentation introduced by
The property CurrentActionID does not exist on object<WorkflowInstance>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
656
					->first();
657
		if(!$action) {
658
			return 'N/A';
659
		}
660
		return $action->getField('Title');
661
	}
662
663
	/**
664
	 * Tells us if $member has had permissions over some part of the current WorkflowInstance.
665
	 *
666
	 * @param $member
667
	 * @return \WorkflowAction | boolean
668
	 */
669
	public function getMostRecentActionForUser($member = null) {
670
		if(!$member) {
671
			if (!Member::currentUserID()) {
672
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by WorkflowInstance::getMostRecentActionForUser of type WorkflowAction.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
673
			}
674
			$member = Member::currentUser();
675
		}
676
677
		// WorkflowActionInstances in reverse creation-order so we get the most recent one's first
678
		$history = $this->Actions()->filter(array(
0 ignored issues
show
Bug introduced by
The method Actions() does not exist on WorkflowInstance. Did you maybe mean getFrontEndWorkflowActions()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
679
			'Finished' =>1,
680
			'BaseAction.ClassName' => 'AssignUsersToWorkflowAction'
681
		))->Sort('Created', 'DESC');
682
683
		$i=0;
684
		foreach($history as $inst) {
685
			/*
686
			 * This iteration represents the 1st instance in the list - the most recent AssignUsersToWorkflowAction in $history.
687
			 * If there's no match for $member here or on the _previous_ AssignUsersToWorkflowAction, then bail out:
688
			 */
689
			$assignedMembers = $inst->BaseAction()->getAssignedMembers();
690
			if($i<=1 && $assignedMembers->count()>0 && $assignedMembers->find('ID', $member->ID)) {
691
				return $inst;
692
			}
693
			++$i;
694
		}
695
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by WorkflowInstance::getMostRecentActionForUser of type WorkflowAction.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
696
	}
697
}