Tracker_Title_Model::getID()   C
last analyzed

Complexity

Conditions 12
Paths 48

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
cc 12
nc 48
nop 4
dl 0
loc 37
rs 6.9666
c 0
b 0
f 0
ccs 0
cts 22
cp 0
crap 156

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
const TITLEDATA_COLUMNS = ['title', 'latest_chapter', 'status', 'followed', 'mal_id', 'last_updated'];
4
5
class Tracker_Title_Model extends Tracker_Base_Model {
6 96
	public function __construct() {
7 96
		parent::__construct();
8 96
	}
9
10
	/**
11
	 * @param string $titleURL
12
	 * @param int    $siteID
13
	 * @param bool   $create
14
	 * @param bool   $returnData
15
	 *
16
	 * @return array|int
17
	 */
18
	public function getID(string $titleURL, int $siteID, bool $create = TRUE, bool $returnData = FALSE) {
19
		$query = $this->db->select('tracker_titles.id, tracker_titles.title, tracker_titles.title_url, tracker_titles.latest_chapter, tracker_titles.status, tracker_sites.site_class, (tracker_titles.last_checked > DATE_SUB(NOW(), INTERVAL 3 DAY)) AS active', FALSE)
20
		                  ->from('tracker_titles')
21
		                  ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
22
		                  ->where('tracker_titles.title_url', $titleURL)
23
		                  ->where('tracker_titles.site_id', $siteID)
24
		                  ->get();
25
26
		if($query->num_rows() > 0) {
27
			$id = (int) $query->row('id');
28
29
			//This updates inactive series if they are newly added, as noted in https://github.com/DakuTree/manga-tracker/issues/5#issuecomment-247480804
30
			if(((int) $query->row('active')) === 0 && $query->row('status') === 0) {
31
				$siteClass = $query->row('site_class');
32
				$titleData = $this->sites->{$siteClass}->getTitleData($query->row('title_url'));
33
				if(!is_null($titleData['latest_chapter']) || $this->sites->{$siteClass}->canHaveNoChapters) {
34
					if($this->updateByID((int) $id, $titleData['latest_chapter'])) {
35
						//Make sure last_checked is always updated on successful run.
36
						//CHECK: Is there a reason we aren't just doing this in updateTitleById?
37
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
38
						         ->where('id', $id)
39
						         ->update('tracker_titles');
40
					}
41
				} else {
42
					log_message('error', "{$siteClass} | {$query->row('title')} ({$query->row('title_url')}) | Failed to update.");
0 ignored issues
show
Unused Code introduced by
The call to the function log_message() seems unnecessary as the function has no side-effects.
Loading history...
43
				}
44
			}
45
46
			$titleID = $id;
47
		} else {
48
			//TODO: Check if title is valid URL!
49
			if($create) $titleID = $this->addTitle($titleURL, $siteID);
50
		}
51
		if(!isset($titleID) || !$titleID) $titleID = 0;
52
53
		return ($returnData && $titleID !== 0 ? $query->row_array() : $titleID);
54
	}
55
56
	/**
57
	 * @param string $title
58
	 * @param string $siteURL
59
	 *
60
	 * @return array|int
61
	 * @throws Exception
62
	 */
63
	public function getIDFromData(string $title, string $siteURL) {
64
		if(!($siteData = $this->getSiteDataFromURL($siteURL))) {
65
			throw new Exception("Site URL is invalid: {$siteURL}");
66
		}
67
68
		return $this->getID($title, $siteData->id);
69
	}
70
71
	/**
72
	 * @param string $titleURL
73
	 * @param int    $siteID
74
	 *
75
	 * @return int
76
	 */
77
	private function addTitle(string $titleURL, int $siteID) : int {
78
		$query = $this->db->select('site, site_class')
79
		                  ->from('tracker_sites')
80
		                  ->where('id', $siteID)
81
		                  ->get();
82
83
		$titleData = $this->sites->{$query->row()->site_class}->getTitleData($titleURL, TRUE);
84
85
		//FIXME: getTitleData can fail, which will in turn cause the below to fail aswell, we should try and account for that
86
		if($titleData && array_key_exists('title', $titleData)) {
87
			$this->db->insert('tracker_titles', array_merge($titleData, ['title_url' => $titleURL, 'site_id' => $siteID]));
88
			$titleID = $this->db->insert_id();
0 ignored issues
show
Bug introduced by
The method insert_id() does not exist on CI_DB_query_builder. Did you maybe mean insert()?

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...
89
90
			$this->History->updateTitleHistory((int) $titleID, NULL, $titleData['latest_chapter'] ?? NULL, $titleData['last_updated'] ?? date("Y-m-d H:i:s", now()));
91
		} else {
92
			log_message('error', "getTitleData failed for: {$query->row()->site_class} | {$titleURL}");
0 ignored issues
show
Unused Code introduced by
The call to the function log_message() seems unnecessary as the function has no side-effects.
Loading history...
93
		}
94
		return $titleID ?? 0;
95
	}
96
97
98
	/**
99
	 * @param int    $titleID
100
	 * @param string? $latestChapter
0 ignored issues
show
Documentation introduced by
The doc-type string? could not be parsed: Unknown type name "string?" at position 0. (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...
101
	 *
102
	 * @return bool
103
	 */
104
	public function updateByID(int $titleID, ?string $latestChapter) : bool {
105
		//FIXME: Really not too happy with how we're doing history stuff here, it just feels messy.
106
		$query = $this->db->select('latest_chapter AS current_chapter')
107
		                  ->from('tracker_titles')
108
		                  ->where('id', $titleID)
109
		                  ->get();
110
		$row = $query->row();
111
112
		//TODO (CHECK): If failed_checks changes won't that trigger affected_rows?
113
		$success = $this->db->set(['latest_chapter' => $latestChapter, 'failed_checks' => 0]) //last_updated gets updated via a trigger if something changes
114
		                    ->where('id', $titleID)
115
		                    ->update('tracker_titles');
116
117
		if($this->db->affected_rows() > 0) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_DB_query_builder as the method affected_rows() does only exist in the following sub-classes of CI_DB_query_builder: CI_DB_cubrid_driver, CI_DB_ibase_driver, CI_DB_mssql_driver, CI_DB_mysql_driver, CI_DB_mysqli_driver, CI_DB_oci8_driver, CI_DB_pdo_4d_driver, CI_DB_pdo_cubrid_driver, CI_DB_pdo_dblib_driver, CI_DB_pdo_driver, CI_DB_pdo_firebird_driver, CI_DB_pdo_ibm_driver, CI_DB_pdo_informix_driver, CI_DB_pdo_mysql_driver, CI_DB_pdo_oci_driver, CI_DB_pdo_odbc_driver, CI_DB_pdo_pgsql_driver, CI_DB_pdo_sqlite_driver, CI_DB_pdo_sqlsrv_driver, CI_DB_postgre_driver, CI_DB_sqlite3_driver, CI_DB_sqlite_driver, CI_DB_sqlsrv_driver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
118
			//Clear hidden latest chapter
119
			$this->db->set(['ignore_chapter' => 'NULL', 'last_updated' => 'last_updated'], NULL, FALSE)
120
			         ->where('title_id', $titleID)
121
			         ->update('tracker_chapters');
122
		}
123
124
		//Update History
125
		//NOTE: To avoid doing another query to grab the last_updated time, we just use time() which <should> get the same thing.
126
		//FIXME: The <preferable> solution here is we'd just check against the last_updated time, but that can have a few issues.
127
		$this->History->updateTitleHistory($titleID, $row->current_chapter, $latestChapter, date('Y-m-d H:i:s'));
128
129
		return (bool) $success;
130
	}
131
132
	public function updateTitleDataByID(int $titleID, array $data) : bool {
133
		$success = FALSE;
134
135
		unset($data['last_updated']); //NOTE: We don't want to overwrite last_updated
136
		if(!array_diff(array_keys($data), TITLEDATA_COLUMNS)) {
137
			$newData = array_merge($data, ['failed_checks' => 0]);
138
			$oldData = $this->db->select('latest_chapter AS current_chapter')
139
			                    ->from('tracker_titles')
140
			                    ->where('id', $titleID)
141
			                    ->get()->row_array();
142
143
144
			$success = $this->db->set($newData) //last_updated gets updated via a trigger if something changes
145
			                    ->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
146
			                    ->where('id', $titleID)
147
			                    ->update('tracker_titles');
148
			if(in_array('latest_chapter', $newData, TRUE) && $newData['latest_chapter'] !== $oldData['current_chapter'] && $this->db->affected_rows() > 0) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_DB_query_builder as the method affected_rows() does only exist in the following sub-classes of CI_DB_query_builder: CI_DB_cubrid_driver, CI_DB_ibase_driver, CI_DB_mssql_driver, CI_DB_mysql_driver, CI_DB_mysqli_driver, CI_DB_oci8_driver, CI_DB_pdo_4d_driver, CI_DB_pdo_cubrid_driver, CI_DB_pdo_dblib_driver, CI_DB_pdo_driver, CI_DB_pdo_firebird_driver, CI_DB_pdo_ibm_driver, CI_DB_pdo_informix_driver, CI_DB_pdo_mysql_driver, CI_DB_pdo_oci_driver, CI_DB_pdo_odbc_driver, CI_DB_pdo_pgsql_driver, CI_DB_pdo_sqlite_driver, CI_DB_pdo_sqlsrv_driver, CI_DB_postgre_driver, CI_DB_sqlite3_driver, CI_DB_sqlite_driver, CI_DB_sqlsrv_driver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
149
				//Clear hidden latest chapter
150
				$this->db->set(['ignore_chapter' => 'NULL', 'last_updated' => 'last_updated'], NULL, FALSE)
151
				         ->where('title_id', $titleID)
152
				         ->update('tracker_chapters');
153
			}
154
155
			//Update History
156
			//NOTE: To avoid doing another query to grab the last_updated time, we just use time() which <should> get the same thing.
157
			//FIXME: The <preferable> solution here is we'd just check against the last_updated time, but that can have a few issues.
158
			$this->History->updateTitleHistory($titleID, $oldData['current_chapter'], $newData['latest_chapter'] ?? NULL, date('Y-m-d H:i:s'));
159
		} else {
160
			log_message('error', "ID {$titleID} is attempting to access an invalid column?");
0 ignored issues
show
Unused Code introduced by
The call to the function log_message() seems unnecessary as the function has no side-effects.
Loading history...
161
		}
162
163
		return (bool) $success;
164
	}
165
166
	public function updateFailedChecksByID(int $titleID) : bool {
167
		$success = $this->db->set('failed_checks', 'failed_checks + 1', FALSE)
168
		                    ->where('id', $titleID)
169
		                    ->update('tracker_titles');
170
171
		return $success;
172
	}
173
174
	/**
175
	 * @param string $site_url
176
	 *
177
	 * @return stdClass|object|null
178
	 */
179 2
	public function getSiteDataFromURL(string $site_url) {
180 2
		$query = $this->db->select('*')
181 2
		                  ->from('tracker_sites')
182 2
		                  ->where('site', $site_url)
183 2
		                  ->get();
184
185 2
		return $query->row();
186
	}
187
}
188