Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add standalone jw player adapter and api logic #10

Merged
merged 23 commits into from
Oct 9, 2024

Conversation

manewc
Copy link
Contributor

@manewc manewc commented Sep 20, 2024

src/api/class-jw-player-api.php: This class is to handle the API logic for performing a request to the JWPlayer API and handle the responses.

Note

I had to update the function get_last_modified_date() because $this->last_modified_date was throwing a PHP error (Typed property must not be accessed before initialization) which I believe was due to the option of the last modified date not existing. I thought I would mention as I wasn't sure why this is different than what is in the class-jw-player-7-for-wp.php file.

src/adapters/class-jw-player.php: This is the Adapter used for syncing the posts. This class is instantiated with the JW_Player_API class - this could be changed to be accepting a class that is adhering to an Interface, but it doesn't seem necessary at this beginning stage.

WP_VIDEO_SYNC_VERSION: This constant was added so it may be output as the user agent in the header.

*/
public function __construct( JW_Player_API $api, string $origin_modified_date = '' ) {
$this->jw_player_api = $api;
$this->origin_modified_date = ! empty( $origin_modified_date ) ? $origin_modified_date : date( 'Y-m-d' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin keeps track of the last modified date via an option, so it shouldn't also need to be kept track of by another property here. If what you want to do is to set a minimum date to override the plugin default of 1/1/1970, then you should update the option with the user-provided value if it isn't already set (see https://github.com/alleyinteractive/wp-video-sync/blob/develop/src/class-sync-manager.php#L97).

The purpose of the get_last_modified_date() function is to return the value of the last modified date after a batch of videos has been imported, because where that value comes from is going to be different from adapter to adapter - it shouldn't need to be read until after it's set by the get_videos function.

Copy link
Contributor Author

@manewc manewc Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this update was to correct this value from not being initialized because it was typed. It seems like it it is coming from a case where the wp_video_sync_last_sync option doesn't exist, possibly coupled with the API not returning any videos. I believe what is happening is that when the option does not exist, this https://github.com/alleyinteractive/wp-video-sync/blob/develop/src/class-sync-manager.php#L99 is not true, and this method https://github.com/alleyinteractive/wp-video-sync/blob/develop/src/class-sync-manager.php#L116 is throwing the fatal:

Fatal error: Uncaught Error: Typed property Alley\WP\WP_Video_Sync\Adapters\JW_Player_7_For_WP::$last_modified_date must not be accessed before initialization in /var/www/nyp/wp-content/plugins/wp-video-sync/src/adapters/class-jw-player-7-for-wp.php:31

(I was testing the JW_Player_7_For_WP adapter to rule out if there was an issue with my adapter, but that didn't seem to be the case here)

I like the idea of setting the date, so I'll look further in to the best solution fit for this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else is going on here - the get_option call uses the second parameter to provide a default, which is a string, so if the option doesn't exist, it will default to the string for the Unix epoch in ISO-8601 format. If that's failing, it likely means that there is an option saved to the options table with that key, and its value is not a string.

For line 116, it will only ever be called after the get_videos method is called, which sets that property value, so there should never be a case where it gets to that part of the code and fails unless the get_videos method is failing to set that value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to track. I believe get_videos() wasn't returning any results, resulting in the the option not being set. So then when get_last_modified_date() was called, the error was flagged due to $this->last_modified_date not being assigned.

I am thinking this is a unique case due to my local testing and just uploading one batch of videos. If it makes sense to wrap https://github.com/alleyinteractive/wp-video-sync/blob/develop/src/class-sync-manager.php#L115-L119 within an! empty( $videos ) condition then I can handle that. Although I did default assign the $last_modified_date to null, which also works, and this portion may be moved "up" to a parent class shared amongst the JWP adapters.

* @return ?DateTimeImmutable
*/
public function get_last_modified_date(): ?DateTimeImmutable {
return $this->last_modified_date ?? DateTimeImmutable::createFromFormat('Y-m-d', $this->origin_modified_date );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With advice above this becomes a simple return of the class property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this - is this a case where these jwp classes should extend a parent class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could, yeah, especially if your API class and the JW Player 7 class are both handling data in the same format (which they should be - the JW Player 7 class is using the JW Player 7 plugin's API helper methods and credentials and otherwise returning what it gets)

Copy link
Contributor Author

@manewc manewc Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did rewrite this which may be seen in this commit: 5b66892. I do wonder now if this is an anti-pattern since the get_last_modified_date() function is removed from adapter.php . I just wonder as I see the chaining in this line: https://github.com/alleyinteractive/wp-video-sync/blob/develop/src/class-sync-manager.php#L116.

Let me know what you think on this and I'll move forward with either approach.

Comment on lines 34 to 39
/**
* The start date if the `last_modified_date` does not exist.
*
* @var string
*/
public string $origin_modified_date;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove (see comment below)

*
* @var ?DateTimeImmutable
*/
private ?DateTimeImmutable $last_modified_date;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't figure out how this value would be read before it's initialized, as you indicated in your comment, based on my read of the code, but you could initialize it to null so it has a value which is compatible with the declared type to avoid this if it's still a problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try null here, the reason was due to #10 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my reply to the other comment

],
];

return 'vip' === $type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having type be a string that could be vip either allow the user to specify the timeout value directly or let them specify an array of args for the remote get function


return 'vip' === $type
? $default_args
: array_merge( $default_args, [ 'timeout' => 3 ] );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp_parse_args instead of array_merge

*
* @return array
*/
public function request_latest_videos( string $updated_after, int $batch_size ): array {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I write API adapters I typically establish a few layers of abstraction to make it easier to work with - I have a basic request method that accepts an HTTP verb with a URL and parameters, then helper functions for get and post etc, then specific functions that do specific things, so that way you can have your logic for "how to fetch a response from the API" be generic and reusable - see, for instance, https://github.com/alleyinteractive/apple-news/blob/develop/includes/apple-push-api/request/class-request.php

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about doing that initially so an API request may be made from other platforms than JW Player. I opted not to because this was just one basic call, but I can look in to breaking this out and mimic what we have in our theme's custom video player plugin.

@@ -21,4 +21,7 @@
exit;
}

// Define the plugin version.
define( 'WP_VIDEO_SYNC_VERSION', '1.7.2' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong - it's 0.1.0 currently, this would be an 0.2.0 release

@manewc
Copy link
Contributor Author

manewc commented Sep 25, 2024

Hi @kevinfodness , I've done some refactoring and addressed your previous comments. Please have a subsequent look when you get a chance. Much appreciated!

NOTE: this is coupled with our theme changes located: https://github.com/alleyinteractive/nypost-go/pull/23678

Morgan

Comment on lines 17 to 31
/**
* The API requester.
*
* @var API_Requester
*/
public API_Requester $api_requester;

/**
* Constructor.
*
* @param API_Requester $api_requester The API requester.
*/
public function __construct( API_Requester $api_requester ) {
$this->api_requester = $api_requester;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* The API requester.
*
* @var API_Requester
*/
public API_Requester $api_requester;
/**
* Constructor.
*
* @param API_Requester $api_requester The API requester.
*/
public function __construct( API_Requester $api_requester ) {
$this->api_requester = $api_requester;
}
/**
* Constructor.
*
* @param API_Requester $api_requester The API requester.
*/
public function __construct( public API_Requester $api_requester ) {}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@manewc
Copy link
Contributor Author

manewc commented Sep 27, 2024

@kevinfodness FYI, I have some more changes I'd like to make, so I requested a review earlier in haste. I'll let you know when it's all complete from my perspective.

Thanks!

@manewc
Copy link
Contributor Author

manewc commented Oct 1, 2024

@kevinfodness if you would have another look over the latest changes that would be much appreciated. Thanks!

Comment on lines 21 to 35
/**
* The JW Player API.
*
* @var JW_Player_API
*/
public JW_Player_API $jw_player_api;

/**
* Constructor.
*
* @param JW_Player_API $api Instance of the JW Player API object.
*/
public function __construct( JW_Player_API $api ) {
$this->jw_player_api = $api;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a modern PHP constructor:

Suggested change
/**
* The JW Player API.
*
* @var JW_Player_API
*/
public JW_Player_API $jw_player_api;
/**
* Constructor.
*
* @param JW_Player_API $api Instance of the JW Player API object.
*/
public function __construct( JW_Player_API $api ) {
$this->jw_player_api = $api;
}
/**
* Constructor.
*
* @param JW_Player_API $jw_player_api Instance of the JW Player API object.
*/
public function __construct( public readonly JW_Player_API $jw_player_api ) {}

Comment on lines 17 to 31
/**
* The API requester.
*
* @var API_Requester
*/
public API_Requester $api_requester;

/**
* Constructor.
*
* @param API_Requester $api_requester The API requester.
*/
public function __construct( API_Requester $api_requester ) {
$this->api_requester = $api_requester;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

* @param string $last_modified_date The date of the last modified video in the batch.
* @return void
*/
public function set_last_modified_date( string $last_modified_date = '' ): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel pretty strongly that this should accept a DateTimeImmutable rather than a string. The date formats returned by different APIs won't necessarily be the same, so it's best to trade in a common format, which is DateTimeImmutable. This way, the JW Player API response (which returns an ISO-8601 date compatible with the DATE_W3C format of DateTime) can be converted to a DateTimeImmutable object in the adapter and passed here where it will be set. If another adapter gets dates in a different format (e.g., Y-m-d H:i:s or just Y-m-d or a Unix timestamp) that adapter would be responsible for standardizing on DateTimeImmtuable, etc.

*
* @return ?DateTimeImmutable
*/
public function get_last_modified_date(): ?DateTimeImmutable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't remove this from the interface. Being able to keep a cursor and fetch updated videos since the last run is core to the mission of this library. By removing this from the interface, it grants permission to adapters to not keep a cursor and do a full sync of all videos on every run, which we don't want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, good point.

/**
* Perform an API request.
*/
class Request {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting - I've always implemented API adapters as self-contained, rather than passing them as dependencies using DI into a generic request class. Is this a pattern that we're using elsewhere? I can see how it would cut down on repeated code, but I can also see how it would introduce some confusion into utilization (you have to set the properties of the API object including request URL, then pass it to the request class for execution, versus calling a method on the API class to get the data). A way to make this a bit more user friendly would be to encapsulate this functionality in the JW Player API class like so:

public function get_videos_after( DateTimeImmutable $updated_after, int $batch_size ): array {
	$this->set_request_url(
		$updated_after->format( 'Y-m-d' ),
		$batch_size
	);

	return ( new Request( $this ) )->get();
}

That code could be moved out of the adapter and into the API wrapper, and then the adapter would call $this->jw_player_api->get_videos_after( $updated_after, $batch_size ) and would otherwise proceed as normal. That makes the way the API class makes its requests an implementation detail, rather than something that the adapter class needs to worry about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been updated in the latest commit: 7397082

Copy link
Member

@srtfisher srtfisher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫡

->with_response_code( 200 )
->with_body( file_get_contents( __DIR__ . '/../Fixtures/jw-player-api-v2-media.json' ) );
->with_file( __DIR__ . '/../Fixtures/jw-player-api-v2-media.json' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh sweet!

@manewc manewc merged commit c3481d5 into develop Oct 9, 2024
2 checks passed
@manewc manewc deleted the feature/NYP-21439/jwp-adapter branch October 9, 2024 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants