Garethp / php-ews

PHP Exchange Web Services
BSD 3-Clause "New" or "Revised" License
112 stars 45 forks source link

Upgrade from jamesiarmes -> garethp php-ews #241

Open mcisar opened 1 year ago

mcisar commented 1 year ago

Am faced with making some maintenance changes to a legacy app which used jamesiarmes/php-ews The site has had to make an unexpected move from a hosted Exchange to M365 which means no basic-auth. It seems that moving to garethp/php-ews at least gives me the option of doing oauth based on some quick research.

Before I get neck-deep I'm hoping to beg some insight on what I'm going to be looking at when doing the upgrade? Not counting the OAUTH which I know I'll have to deal with... but is switching from one to the other implementation of php-ews going to require a lot of code changes elsewhere in the application, or should it be a relatively transparent transition.

Just hoping that anyone that's done the jamesiarmes->garethp transition (or the basic->oauth for that matter) might chime in with any "gotchas" that have been encountered. The maintenance changes to this app are in the "fix this app you didn't write and it needs to be working again yesterday" category so I haven't had the luxury of really doing a deep-dive on php-ews let alone the differences between versions.

Thanks in advance!

Garethp commented 1 year ago

Hey there! While this is technically a fork of jamesiarmes/php-ews, it's not exactly close in usage, but there is a way to migrate without it being too different. I've outlined it in the README, but there's two distinct ways of using this library: Some classes to give simple interfaces to the most common use cases and constructing API requests directly. jamesiarmes/php-ews works much closer to the second than the first. Let's take a look the example for creating an event. I can't say my code below will work perfectly if you try to run it, it's been quite a few years since I've written PHP, but it should give you a rough idea.

jamesiarmes/php-ews

$client = new Client($host, $username, $password, $version);

// Build the request,
$request = new CreateItemType();
$request->SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType::SEND_ONLY_TO_ALL;
$request->Items = new NonEmptyArrayOfAllItemsType();

// Build the event to be added.
$event = new CalendarItemType();
$event->RequiredAttendees = new NonEmptyArrayOfAttendeesType();
$event->Start = $start->format('c');
$event->End = $end->format('c');
$event->Subject = 'EWS Test Event';

// Set the event body.
$event->Body = new BodyType();
$event->Body->_ = 'This is the event body';
$event->Body->BodyType = BodyTypeType::TEXT;

// Iterate over the guests, adding each as an attendee to the request.
foreach ($guests as $guest) {
    $attendee = new AttendeeType();
    $attendee->Mailbox = new EmailAddressType();
    $attendee->Mailbox->EmailAddress = $guest['email'];
    $attendee->Mailbox->Name = $guest['name'];
    $attendee->Mailbox->RoutingType = RoutingType::SMTP;
    $event->RequiredAttendees->Attendee[] = $attendee;
}

// Add the event to the request. You could add multiple events to create more
// than one in a single request.
$request->Items->CalendarItem[] = $event;

$response = $client->CreateItem($request);

Can be (roughly) converted to:

$api = API::withUsernameAndPassword($server, $username, $password);

$request = array(
    'Items' => array(
        'CalendarItem' => array(
            'Start' => $start->format('c'),
            'End' => $end->format('c'),
            'Subject' => 'EWS Test Event',
            'Body' => array(
                'BodyType' => Enumeration\BodyTypeType::TEXT,
                '_value' => 'This is the event body'
            ),
            'RequiredAttendees' => array(
                'Attendee' => array_map(
                    function ($guest) {
                        return array(
                            'Mailbox' => array(
                                'EmailAddress' => $guest['email'],
                                'Name' => $guest['name'],
                                'RoutingType' => RoutingType::SMTP
                            )
                        );
                    },
                    $guests
                )
            )
    ),
    'SendMeetingInvitations' => Enumeration\CalendarItemCreateOrDeleteOperationType::SEND_ONLY_TO_ALL
);

$request = Type::buildFromArray($request);
$response = $api->getClient()->CreateItem($request);
));

So you can see the basic idea is still the same: You create a request in the shape of EWS' XML and send it. The difference is that in james' version, you create it by creating objects and then assigning to their properties while in mine you can just define it as an object. You can also create it as classes and just pass the classes in, but I find that to be more cumbersome.

Even with that though, you might want to consider just moving to the slightly nicer API's, like this:

$api = API::withUsernameAndPassword('server', 'username', 'password'); $calendar = $api->getCalendar();

$start = new DateTime('8:00 AM'); $end = new DateTime('9:00 AM');

$createdItemIds = $calendar->createCalendarItems(array(
    'Subject' => 'EWS Test Event',
    'Start' => $start->format('c'),
    'End' => $end->format('c')
    ...
));

Where you basically still have the same thing in so far as you're passing in an array of the message that gets converted to the same object, but it means you can skip some of the boilerplate. The responses also cut down some of the boilerplate for fetching the response data, here's a comparison of handling the response from that example

// Iterate over the results, printing any error messages or event ids.
$response_messages = $response->ResponseMessages->CreateItemResponseMessage;
foreach ($response_messages as $response_message) {
    // Make sure the request succeeded.
    if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
        $code = $response_message->ResponseCode;
        $message = $response_message->MessageText;
        fwrite(STDERR, "Event failed to create with \"$code: $message\"\n");
        continue;
    }

    // Iterate over the created events, printing the id for each.
    foreach ($response_message->Items->CalendarItem as $item) {
        $id = $item->ItemId->Id;
        fwrite(STDOUT, "Created event $id\n");
    }
}

Versus

foreach ($createdItemIds as $createdId) {
    echo $createdId->getId();
}

So as you can see, my library isn't a drop in replacement for jamesiarmes/php-ews, but they both revolve around accepting and returning the same structure of data at the end, the main difference is how you construct the data. The difference in responses comes down to the fact that in jamesiarmes/php-ews you receive anonymous classes that have no defined types, whereas with mine the responses are converted automatically into typed classes that can be found in src/API/Type, which were generated from the WSDL file. You can also use these types to create your requests too if you so wish, but I find the array approach to be cleaner.

Do you have any more specific questions on migrating over?