I have 1 Employee entity which holds a value object of ContactInformation :
Employee.php
class Employee {
private $contactInformation;
public function __construct(ContactInformation $contactInformation) {
$this->contactInformation = $contactInformation;
}
public function getContactInformation() {
return $this->contactInformation;
}
}
ContactInformation.php
class ContactInformation {
private $email; // Required
private $cellPhone; // Required
private $phone; // Optional
private $address; // Optional
public function __construct($email, $cellPhone) {
$this->email = $email;
$this->cellphone = $cellphone;
}
/** Here goes a bunch of setter getter for those properties **/
}
If an Employee's phone is changed, is it not better to do just
$employee->getContactInformation()->setPhone($newPhone)
Rather than forcing immutability such as
$employee->setContactInformation(new ContactInformation(/** copy paste
here **/));
Going by Eric Evans' book, I understand that when you change a part of a value object, then it is a whole different value object then it was before.
I just cannot get the same conclusion about a little bit more complex object like this ContactInformation, is it supposed to be an Entity?
Need advice, thanks !
For me, Employee to have dependency on ContactInformation means that you expect to have ContactInformation already set with phones, etc. and then passed to employee, i.e.:
$contanctInfo = new ContactInformation();
$contanctInfo->setPhone(820);
$employee1 = new Employee($contanctInfo);
echo $employee1->getContanctInformation()->getPhone(); // 820
However, that means you would need to make as much ContactInformation objects, as Employee objects, before instantiating each Employee, because:
$contanctInfo = new ContactInformation();
$contanctInfo->setPhone(820);
$employee1 = new Employee($contanctInfo);
$employee2 = new Employee($contanctInfo);
$employee1->getContactInformation()->setPhone(123);
$employee2->getContactInformation()->setPhone(666);
echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();
Turns into:
666
666
Because you are changing one and the same object.
In your case, you are instantiating ContactInformation at the same time, when you are instantiating Employee when makes the dependency a bit useless:
$employee1 = new Employee(new ContactInformation());
$employee2 = new Employee(new ContactInformation());
$employee1->getContactInformation()->setPhone(123);
$employee2->getContactInformation()->setPhone(666);
echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();
Results into:
123
666
So, if you don't want to make changes on the dependent object before inject it to another object, you don't need a dependency at all. Even more, in real world ContanctInformation is bind to an Employee, not the opposite.
I would make it this way:
class Employee {
private $_contactInformation;
public function __construct() {
$this->_contactInformation = new ContactInformation($this);
}
/**
* #return ContactInformation
*/
public function getContactInformation() {
return $this->_contactInformation;
}
}
class ContactInformation {
private $_employee;
private $email; // Required
private $cellPhone; // Required
private $phone; // Optional
private $address; // Optional
public function __construct(Employee $employee) {
$this->_employee = $employee;
}
public function setPhone($phone) {
$this->phone = $phone;
}
public function getPhone() {
return $this->phone;
}
}
$employee1 = new Employee();
$employee2 = new Employee();
$employee1->getContactInformation()->setPhone(123);
$employee2->getContanctInformation()->setPhone(666);
echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();
On the other hand, if Employee has some identificator, so the ContanctInfo retrieves info based on the ID, you can make the injection the way you want it.
Value objects should be immutable, but ContactInformation should not be a value object.
Consider two people who share a house, and therefore have the same address and phone number (it simplifies matters if you assume they don't have a cellular phone or email).
In that situation, they'd have identical ContactInformation objects, but the contact information is still distinctly one person's or another's. If one of them moves out, or buys a cell phone then their contact information will have to change.
As you mentioned in your comment, it's better to consider ContactInformation as a mutable Entity, and not a Value Object.
Interesting discussion. What I do is to have getContactInformation return a clone.
class Employee {
public function getContactInformation() {
return clone $this->contactInformation;
}
...
$contactInformation = $employee->getContactInformation();
$contactInformation->setPhone('');
$employee->setContactInformation($contactInformation);
By doing so, the value object actually stored inside of Employee is immutable. You always pull a copy, which since it no longer linked to employee, is no longer a value object(one could argue with lots of hand waving) and thus can be modified without upsetting the DDD police.
Works for me and let's me use a form system which expects setters.
Related
I want to have an object for a student that contains their name among other properties – one of these properties being their assignment. And for the assignment I want to create another object containing its details.
class Student {
private $id; // used for sql queries
private $name; // result from such query
public function __construct($id) {
$this->id = $id;
// get properties from db using the $id
}
public function getName() {
return $this->name;
}
}
class Assignment {
private $id; // used for sql queries
private $student; // result from query
private $details; // result from query
public function __construct($id) {
$this->id = $id;
// get properties from db using the $id
$this->student = new Student($row["studentId"]);
}
public function getDetails() {
return "{$this->student->getName()} needs to do {$this->details}";
}
}
$assignment = new Assignment($someId);
$assignment->getDetails();
But for some reason I'm not allowed to access the assignment when it's protected. The only way to make it work is to make it public, but isn't that bad practice?
Speaking of bad practice, what about the whole approach of $student->assignment->getDetails(); would it be better practice to just make a getter for the assignment (i.e. $student->getAssignment()->getDetails()) ?
Thanks in advance
EDIT: Basically, I just want to know whether I can make $this->student->getName() work somehow, or whether it's better to use $this->getStudent()->getName().
The reason I'm confused about protected access is because I'm already in the assignment, and therefore I should have access to all properties, but I don't. I don't have access to $this->student, nor its methods, even though it's a property.
What's going on here?
I've a class, Proposal, which has a $status of type ProposalStatus. Now, for the most part the ProposalStatus's identity does not change... but it does (sort of). It's $id is fixed, but the $display_name and $definition (just strings) can change over a long period of time as the master data is updated, but it will NOT change within the lifetime of an HTTP Request-Response.
Question #1 - Entity or Value Object?
Is a value object something is never supposed to change or only never supposed to change over the lifetime of a specific execution of the application? If the display name or definition are changed then really I expect / want it to be changed for everyone. However, since they can be defined outside of the proposal I'm think that just straight up makes them entities instead of value objects.
At no time does the Proposal change the ProposalStatus's attributes, it only changes which ProposalStatus is has.
Question #2 - How to set the status correctly for a domain-driven design?
My proposal object has the ability to manage it's statuses, but in order to do that I need to have a specific ProposalStatus object. Only, where is the list of statuses that allows it to returns the right expected to be?
I could get it from a the ProposalRepository... but everything is accessed via the aggregate root which the Proposal so that doesn't make sense.
I could have constants that match the $id of the ProposalStatus, but that seems wrong.
I could make a ProposalStatusRepository... but should I be accessing another repository from within the Proposal?
I could make a array of all possible statuses with the $id as the key and add to the proposal, but that isn't much different from a repository...
Example:
class ProposalStatus {
protected $id; // E.g., pending_customer_approval
protected $display_name; // E.g., Pending Customer Approval
protected $definition; // E.g., The proposal needs to be approved by the customer
}
class Proposal {
/**
* The current status of the proposal
* #var ProposalStatus
*/
protected $proposal_status;
public function withdraw() {
// verify status is not closed or canceled
// change status to draft
}
public function submit() {
// verify status is draft
// change status to pending customer approval
}
public function approve() {
// verify status is pending customer approval
// change status to approved
}
public function reject() {
// verify status is pending customer approval
// change status to rejected
}
public function close() {
// verify status is not canceled
// change status to closed
}
public function cancel() {
// verify status is not closed
// change status to canceled
}
}
From what I understand from your domain, ProposalStatus should be a Value object. So, it should be made immutable and contain specific behavior. In your case, the behavior is testing for a specific value and initializing only to permitted range of values. You could use a PHP class, with a private constructor and static factory methods.
/**
* ProposalStatus is a Value Object
*/
class ProposalStatus
{
private const DRAFT = 1;
private const PENDING_CUSTOMER_APPROVAL = 2;
private const CANCELLED = 3;
private const CLOSED = 4;
/** #var int */
private $primitiveStatus;
private function __construct(int $primitiveStatus)
{
$this->primitiveStatus = $primitiveStatus;
}
private function equals(self $another): bool
{
return $this->primitiveStatus === $another->primitiveStatus;
}
public static function draft(): self
{
return new static(self::DRAFT);
}
public function isDraft(): bool
{
return $this->equals(static::draft());
}
public static function pendingCustomerApproval(): self
{
return new static(self::PENDING_CUSTOMER_APPROVAL);
}
public function isPendingCustomerApproval(): bool
{
return $this->equals(static::pendingCustomerApproval());
}
public static function cancelled(): self
{
return new static(static::CANCELLED);
}
public function isCancelled(): bool
{
return $this->equals(static::cancelled());
}
public static function closed(): self
{
return new static(static::CLOSED);
}
public function isClosed(): bool
{
return $this->equals(static::closed());
}
}
class Proposal
{
/** #var ProposalStatus */
private $status;
public function __construct()
{
$this->status = ProposalStatus::draft();
}
public function withdraw()
{
if (!$this->status->isClosed() && !$this->status->isCancelled()) {
$this->status = ProposalStatus::draft();
}
}
// and so on...
}
Note that immutability is an important characteristic of a Value object.
In case that your ProposalStatus is a fixed list of values just go for the enumeration approach.
Otherwise you need to treat ProposalStatus as an AggregateRoot that users can create, update and delete (I guess). When assigning a ProposalStatus to a Proposal you just need the ID. If you want to check that the given ID exists you just need to satisfy the invariant with a specialized query. Specification pattern fits well here.
class ProposalStatusExistsSpecification
{
public function isSatisfiedBy(string $proposalSatusId): bool
{
//database query to see if the given ID exists
}
}
You can find here the Interfaces to implement your specification.
Is list of all possible proposal statuses static? I think it is. So ProposalStatus looks like a simple enumeration. Attributes like DisplayName and Definition are not related to business code.
Just define ProposalStatus as enumeration (static class with read-only fields or any other structure supported by your language). It shuld be defined in business layer. Bussiness code should be able to distinguish enumeration values (e.g. if (proposal.Status == ProposalStatus.Pending) { poposal.Status = ProposalStatus.Approved; }).
In application or even presentation layer define a dictionary that contains DisplayName and Definition mapped to ProposalStatus. It will be used only when displaying data to users.
This is not a question about a particular problem, but rather a question asking for advice and / or assistance.
Im a 2nd year student, Im really struggling to get the hang of OOP programming, the fact that my textbook is not very clear, in its explanation is not helping either. I know there are 100's of examples on the web of this probably but I would like to focus specifically on the example used in my textbook.
The introduction to OOP in php starts with this:
EXAMPLE 1
class Person{
var $name;
function set_name($data){
$this->name=$data;
}
function get_name(){
return $this->name;
}
}
$ralph = new Person;
$ralph->set_name('Ralph');
echo 'friend name is '.$ralph->get_name();
Fine I get that no problem However it then goes on giving, what is in my view a very brief explanation on constructors, saying:
Wouldn't it be nice if you could both create and initialize an object at the same time, PHP allows you to do that with constructors.
And then proceeds to give the following example
EXAMPLE 2(UNCLEAR...?)
class Person{
var $name;
function __construct($data)
{
$this->name=$data
}
function set_name($data){
$this->name=$data;
}
function get_name(){
return $this->name;
}
}
$dan = new Person;
echo"Friend name is ", $dan->get_name(), ".";
My Question
I would really like to know what is the difference between the two examples above? Also if a beginner practical example could be included it will be enormously appreciated!
It's no wonder you are confused, that is a very bad example (looks like they forgot to include passing the name to the constructor)! There are also some outdated styles here as well, typically people do not use var anymore for property declarations but declare their scope (public, private, protected, etc.), also some syntax errors (missing semicolon), but that is for another day.
What would have made this a lot more clear is if they actually used the new power of the constructor, which for some reason, they did not. Here is an updated example where the constructor is actually being used:
class Person{
private $name;
function __construct($data)
{
$this->name=$data;
}
function set_name($data){
$this->name=$data;
}
function get_name(){
return $this->name;
}
}
// pass the name to the constructor and you can bypass the set_name call
$dan = new Person("Timothy");
echo "Friend name is ", $dan->get_name(), ".";
This definitely gets much more useful than passing scalars, but hopefully it illustrates what constructors can do a little bit better. Constructors are called automatically whenever you instantiate a new object from a class. Let me know if you have any questions.
The difference between two class is first one you can not initialize data at a time but in second class you can initialize data at a time.
In First Class
class Person{
private $name;
function set_name($data){
$this->name=$data;
}
function get_name(){
return $this->name;
}
}
$ralph = new Person; // you can not initialize value here
$ralph->set_name('Ralph');
echo 'friend name is '.$ralph->get_name(); //friend name is Ralph
In second Class
class Person{
private $name;
function __construct($data)
{
$this->name=$data
}
function set_name($data){
$this->name=$data;
}
function get_name(){
return $this->name;
}
}
$dan = new Person('Sahadat'); //you can initialize value here
echo"Friend name is ", $dan->get_name(), "."; //Friend name is Sahadat
$dan->set_name('John');
echo"Friend name is ", $dan->get_name(), "."; //Friend name is John
The second way is the best way.
Here is a simple answer. First you seem to use javascript var in the above code, so I omit this below. As you can see, the Person is the Object of concern. In the above example, the author is adding only a name to a Person object - but a person could have many other characteristics such as age, date of birth etc... So when a person object is initialized, the Person object will have a place to store its $name in memory. Since the the $name property is private, the above code uses setters and getters to set and get the $name property.
class Person{
private $name;
function setName($name){
$this->name = $name;
}
function getName(){
return $this->name;
}
function setName($name){
$this->name = $name;
}
}
$ralph = new Person;
$ralph->set_name('Ralph');
echo 'friend name is '.$ralph->get_name();
// Friend name is Ralph
The second example combines two steps ( btw you seem to have copied the wrong snippet ) the object will accept one parameter $data .
class Person{
private $name;
function __construct($data)
{
$this->name=$data
}
function setName($name){
$this->name=$name;
}
function getName(){
return $this->name;
}
}
$dan = new Person('Dan');
echo"Friend name is ", $dan->get_name(), ".";
//Friend name is Dan
A true object Oriented example should look like this:
class Person{
private $name;
function __construct($data)
{
$this->name=$data
}
function setName($name){
$this->name=$name;
return $this; // return $this to allow chaining of method calls
}
function getName(){
return $this->name;
}
}
Here is the extra, In real world applications, you find stuff like in this example
public function billTo(array $user){
// Create the Bill To info
$billto = new AnetAPI\CustomerAddressType();
$billto->setFirstName($user['firstname']);
$billto->setLastName($user['lastname']);
$billto->setCompany($user['company_name']);
$billto->setAddress($user['address']);
$billto->setCity($user['city']);
$billto->setState($user['state']);
$billto->setZip($user['zipcode']);
$billto->setCountry($user['country']);
$billto->setEmail($user['email']);
$billto->setPhoneNumber($user['phone']);
return $billto;
}
The above functions creates an instance of the class CustomerAddressType() and stores data from the User array ( passed as a parameter ) in that CustomerAddressType instance. Same as the first example given by the author.
I'm new to DI ,using Pimple. Using: php 5.3.5 (wamp), namespaces as well.
I'm refactoring code, using it, but came to a problem (s):
I have my Container that extends from Pimple, lets call it PContainer.php:
class ReuseableContainer extends Pimple{
private function initOutterClass(){
$this['special_location_class'] = '\SpecialLocation';
$this['special_location'] = function($c){return new $c['special_location_class']($c['location_details'],$c['location']);};
}
private function initGlobalFunctions(){
$this['getGeneralDataFromArray'] = function($c){
// returning a function
return function($arr){
foreach ($arr as $key => $value){
// do something
$new_data = $c['general_data_type'];
$new_data->id = $value['id'];
$new_data->name = $value['name'];
}
}
}
public function __construct(){
$this['location_class'] = '\Location';
$this['location_details_class'] = '\LocationDetails';
$this['general_data_type_class'] = '\GeneralDataType';
// define some objects
$this['location'] = function ($c) {
return new $c['location_class']();
};
$this['location_details'] = function ($c) {
return new $c['location_details_class']();
};
$this['general_data_type'] = function ($c) {
return new $c['general_data_type_class']();
};
$this->initOutterClass();
$this->initGlobalFunctions();
}
}
global $container ;
$container = new Pimple();
// embed the SomeContainer container
$container['embed'] = $container->share(function () { return new ReuseableContainer(); });
Ok. So i got a SpecialHelper.php which holds:
final class SpecialLocation{
public $name;
public $location;
public $picture;
public function __construct($location){
$this->location; // dependent on class: Location
}
}
final class SpecialUser{
private $id;
private $location;
public function __construct(\Location $location,$id=''){
$this->id = $id;
$this->location = $location; // $container['embed']['location'];
}
and we got our GeneralHelper.php which holds:
final class Location{
public $lat;
public $lng;
public function __construct($lat='',$lng=''){ $this->lat = $lat; $this->lng = $lng;}
}
final class LocationDetails{
public $id;
public $addresss;
public function __construct($id='',$address=''){$this->id = $id; $this->address = $address;}
}
class GeneralDataType{
public $id;
public $name;
public function getName(){ return $this->name;}
public function getId(){ return $this->id;}
}
and we have our "Special Class" controller, which looks something like this:
final class SpecialController{
public function foor($some_array){
$this->doSomething($some_array);
}
private function doSomething($ret_value){
// do something
$arr = array();
foreach($ret_value as $key => $value){
$something = $container['embed']['getGeneralDataFromArray']($value);
$special_location = $container['embed']['special_location'];
$arr[] = special_location;
}
return $arr;
}
}
Finally we have our main "driver", main.php
require('PContainer.php');
....
...
$some_array = array(....);
$special_controller = new SpecialController();
$special_controller->foor($some_array);
Problems:
1) I had to add initOutterClass function inside ReuseableContainer to decouple the "Special" classes, how could have i decoupled them in a better way? creating a new "special" 9container or something? as EVERYTHING now sitts inside the container.. same goes to the initGlobalFunctions()
2) regarding SpecialHelper.php: i have there SpecialLocation, which one of its properties is a \Location class, i've put it in the constructor , but if i have 20 object properties that are dependent, i must put them all as INPUT params for the constructor?? same goes to the SpecialUser class, it has a $location which if i could i would have made $this->location = $container['embed']['location']; instead of $this->location = $location; resulting in a dependent on the DI! :/
3) I've had to create SpecialHelper.php in a different file, despite wanting to put it in the "special class controller", just so there won't be any unknowns (due to require statement order)
4) MOST importantly: about the "Special class" controller, how do i solve the doSomething method? i must create "Special Location" object inside the loop but i get that $container is unrecognized (despite being global, as of scope probably) but more over it's really dependent! and it's a private function, i don't wish to pass the container to EVERY class i'll use from now on, it isn't IoC right?
Any help is appriciated... i'm trying to understand the best practices..
Thank you
4)Most important: IoC is correct. That an implementation is not correctly working does not reflect the principle of IoC itself.
If you want to use the global $container within a function, then should you use the global keyword within that function. That is how PHP works. Making it static is solving the problem of reference, but does not make a real difference.
An IoC container resolves the dependencies for the caller. The caller does not have to know anything about the internals of the callee - and he doesn't care either. So, there should be some kind of contract by which the exchange of data is regulated. If you have that situation, then you have IoC.
3)That problem is too vague to answer, but imo also not relevant from a practical perspective. Does it work? Ok, good to know. :-)
2)The clue of IoC is the use of contracts. The IoC container is there to connect the caller to the proper contract. The contract resolves to a concrete callee. The callee will return information inline with the contract. The caller understands the answer. Therefor will you need that the input and output in this process is independent of a certain implementation at a certain time. So don't use 20 object properties as input, but use an array or general object instead.
1) I get the idea that you are mixing functional flow (data flow) with technical flow (relationships between classes). An IoC container serves the purpose of the technical flow, it optimizes the dependency in the relationships between classes. For instance, if you want to connect to a database, then might you reuse an existing connection instead of creating new connections all the time. Or if you want to use a special functionality on several moments in your flow, then might you use IoC for that.
I'm trying to cast an array of Objects for a week already in a PHP class and have had some real issues making it to work and mainly with the logic itself as I am new to classes. Looked and read a lot of resources but it does not seem to make any sense to me, any pointers would be greatly appreciated and I am open to suggestions.
The issue:
Create a PHP class as part of the project named Contact, and then a class called 'ContactList' that contains an array of these contact objects.
And next, an array of ContactList objects called 'ContactTabs'.
Then, in the program, populate one ContactList object (named 'Contacts') with the current contacts, and create a new ContactList object named 'Friends', and add some names and email addresses there for friends. It is most important that this be done in a nice, object-oriented fashion so it can allow to create other type of contacts in the future.
A 'ContactList' object, should contain not only an array that is the list of contacts, but it would also contain the text label to put on the tab. So, it is more appropriate that the ContactList be more than a simple array, but rather it should be an object that contains an array as well as a text label.
The business logic is the following:
Contact
name
bgcolor
lgcolor
email
ContactTabs
Employees
Friends
// class definition
class Contact{
// define properties
public $name;
public $bgcolor;
public $lgcolor;
public $email;
// constructor
public function __construct() {
}
//destructor
public function __destruct() {
}
}
class ContactList extends Contact {
// constructor
public function __construct($contactname,$contactbgcolor,$contactlgcolor,$contactemail) {
$this ->name = $contactname;
$this ->bgcolor = $contactbgcolor;
$this ->lgcolor = $contactlgcolor;
$this ->email = $contactemail;
parent::__construct();
}
}
$johndie = new ContactList('John Die','#FCEDC9','#FEF9ED','somecontact1#gmail.com','9');
$johndoe = new ContactList('John Doe ','#DEEDFE','#EDF5FE','somecontact2#hotmail.com,'6');
$Friends = new ExtendedArrayObject($jp);
$Employees = new ExtendedArrayObject($elvete);
$ContactTabs= new ExtendedArrayObject($Employees,$Friends);
print_r($ContactTabs);
You had the Contact class correct (although you may want to use private/protected properties for encapsulation, but you can change that later).
This is how I would do it:
class Contact{
public $name;
public $bgcolor;
public $lgcolor;
public $email;
public function __construct($name, $bgcolor, $lgcolor, $email) {
$this->name = $name;
$this->bgcolor = $bgcolor;
$this->lgcolor = $lgcolor;
$this->email = $email;
}
}
class ContactList implements Iterator, ArrayAccess {
protected $_label;
protected $_contacts = array();
public function __construct($label) {
$this->_label = $label;
}
public function getLabel() {
return $this->label;
}
public function addContact(Contact $contact) {
$this->_contacts[] = $contact;
}
public function current() {
return current($this->_contacts);
}
public function key() {
return key($this->_contacts);
}
public function next() {
return next($this->_contacts);
}
public function rewind() {
return reset($this->_contacts);
}
public function valid() {
return current($this->_contacts);
}
public function offsetGet($offset) {
return $this->_contacts[$offset];
}
public function offsetSet($offset, $data) {
if (!$data instanceof Contact)
throw new InvalidArgumentException('Only Contact objects allowed in a ContactList');
if ($offset == '') {
$this->_contacts[] = $data;
} else {
$this->_contacts[$offset] = $data;
}
}
public function offsetUnset($offset) {
unset($this->_contacts[$offset]);
}
public function offsetExists($offset) {
return isset($this->_contacts[$offset]);
}
}
And ContactTabs would be very similar to ContactList, but would accept ContactList objects instead of Contact.
How it would work is:
// create some contacts
$bob = new Contact('Bob', 'black', 'white', 'bob#bob.com');
$john = new Contact('John', 'black', 'white', 'john#john.com');
// create a contact list and add contacts to it
$contactlist = new ContactList('Contacts');
$contactlist->addContact($bob); // using a method
$contactlist[] = $john; // using array notation
// access the list by using foreach on it, since ContactList implements Iterator
foreach ($contactlist as $contact) {
echo $contact->email;
}
First step will be to throw out all that code and start fresh. I don't know what "ExtendArrayObject" is, but you don't need it for this - it only complicates things. The rest of the code is not really on the right track.
In OOP, a class is supposed to model "something." Some kind of entity, which is often, but not always, a real-world thing. The first step in OO development is to sketch out the entities involved and model them in classes. In this case, your assignment tells you exactly what the entities are:
Contact
ContactList
So, OK, what do we need to know about our contact - what properties do we need it to have? Let's say "name" and "email." Let's give it these properties then:
class Contact {
public $name;
public $email;
}
A ContactList sounds pretty simple, it's just a list of Contacts, with a title that can be displayed in a tab. I'll write it so that it stores its Contacts internally in an array:
class ContactList {
public $title;
public $contacts = array();
}
You may be wondering, why do I need a ContactList if all it does is hold a title and store Contacts in an array? The answer is, because your assignment says you need it. :) Like many aspects of OOP, their usefulness will only be revealed as your projects increase in complexity. Just go along with it for now.
Now, put these classes in 2 separate files: Contact.php and ContactList.php. (This is not strictly necessary but is generally considered best practice.) Create a 3rd file called whatever, and in it, add the following code to tie it all together:
include("Contact.php");
include("ContactList.php");
// create a Contact object
$contact = new Contact();
$contact->name = "Bill";
$contact->email = "bill#gmail.com";
// create a ContactList object
$contact_list = new ContactList();
// set its title
$contact_list->title = "My Great Contacts";
// add our contact to it
$contact_list->contacts[] = $contact;
print_r($contact_list);
With this code, you are 80% of the way there - I didn't want to do exactly what your assignment specified because that would leave nothing left for you! I encourage you to play around with this until you feel like you really "get it." It often takes a while for OOP to "click" in one's head. But it's really crucial.
Exercises:
Add some different properties to the Contact class, like "phone" and "address"
Create some more contacts and add them to the list
Create a second ContactList object, containing a different list, with different contacts in it
Extra credit: Write a method in ContactList that adds a Contact, but only if another one with the same email address doesn't already exist in the list.