1<?php
2
3/**
4 * OAuth service factory.
5 *
6 * PHP version 5.4
7 *
8 * @category   OAuth
9 * @author     David Desberg <david@daviddesberg.com>
10 * @author     Pieter Hordijk <info@pieterhordijk.com>
11 * @copyright  Copyright (c) 2013 The authors
12 * @license    http://www.opensource.org/licenses/mit-license.html  MIT License
13 */
14
15namespace OAuth;
16
17use OAuth\Common\Service\ServiceInterface;
18use OAuth\Common\Consumer\CredentialsInterface;
19use OAuth\Common\Storage\TokenStorageInterface;
20use OAuth\Common\Http\Client\ClientInterface;
21use OAuth\Common\Http\Client\StreamClient;
22use OAuth\Common\Http\Uri\UriInterface;
23use OAuth\Common\Exception\Exception;
24use OAuth\OAuth1\Signature\Signature;
25
26class ServiceFactory
27{
28    /**
29     *@var ClientInterface
30     */
31    protected $httpClient;
32
33    /**
34     * @var array
35     */
36    protected $serviceClassMap = array(
37        'OAuth1' => array(),
38        'OAuth2' => array()
39    );
40
41    /**
42     * @var array
43     */
44    protected $serviceBuilders = array(
45        'OAuth2' => 'buildV2Service',
46        'OAuth1' => 'buildV1Service',
47    );
48
49    /**
50     * @param ClientInterface $httpClient
51     *
52     * @return ServiceFactory
53     */
54    public function setHttpClient(ClientInterface $httpClient)
55    {
56        $this->httpClient = $httpClient;
57
58        return $this;
59    }
60
61    /**
62     * Register a custom service to classname mapping.
63     *
64     * @param string $serviceName Name of the service
65     * @param string $className   Class to instantiate
66     *
67     * @return ServiceFactory
68     *
69     * @throws Exception If the class is nonexistent or does not implement a valid ServiceInterface
70     */
71    public function registerService($serviceName, $className)
72    {
73        if (!class_exists($className)) {
74            throw new Exception(sprintf('Service class %s does not exist.', $className));
75        }
76
77        $reflClass = new \ReflectionClass($className);
78
79        foreach (array('OAuth2', 'OAuth1') as $version) {
80            if ($reflClass->implementsInterface('OAuth\\' . $version . '\\Service\\ServiceInterface')) {
81                $this->serviceClassMap[$version][ucfirst($serviceName)] = $className;
82
83                return $this;
84            }
85        }
86
87        throw new Exception(sprintf('Service class %s must implement ServiceInterface.', $className));
88    }
89
90    /**
91     * Builds and returns oauth services
92     *
93     * It will first try to build an OAuth2 service and if none found it will try to build an OAuth1 service
94     *
95     * @param string                $serviceName Name of service to create
96     * @param CredentialsInterface  $credentials
97     * @param TokenStorageInterface $storage
98     * @param array|null            $scopes      If creating an oauth2 service, array of scopes
99     * @param UriInterface|null     $baseApiUri
100     * @param string                $apiVersion version of the api call
101     *
102     * @return ServiceInterface
103     */
104    public function createService(
105        $serviceName,
106        CredentialsInterface $credentials,
107        TokenStorageInterface $storage,
108        $scopes = array(),
109        UriInterface $baseApiUri = null,
110        $apiVersion = ""
111    ) {
112        if (!$this->httpClient) {
113            // for backwards compatibility.
114            $this->httpClient = new StreamClient();
115        }
116
117        foreach ($this->serviceBuilders as $version => $buildMethod) {
118            $fullyQualifiedServiceName = $this->getFullyQualifiedServiceName($serviceName, $version);
119
120            if (class_exists($fullyQualifiedServiceName)) {
121                return $this->$buildMethod(
122                    $fullyQualifiedServiceName,
123                    $credentials,
124                    $storage,
125                    $scopes,
126                    $baseApiUri,
127                    $apiVersion
128                );
129            }
130        }
131
132        return null;
133    }
134
135    /**
136     * Gets the fully qualified name of the service
137     *
138     * @param string $serviceName The name of the service of which to get the fully qualified name
139     * @param string $type        The type of the service to get (either OAuth1 or OAuth2)
140     *
141     * @return string The fully qualified name of the service
142     */
143    private function getFullyQualifiedServiceName($serviceName, $type)
144    {
145        $serviceName = ucfirst($serviceName);
146
147        if (isset($this->serviceClassMap[$type][$serviceName])) {
148            return $this->serviceClassMap[$type][$serviceName];
149        }
150
151        return '\\OAuth\\' . $type . '\\Service\\' . $serviceName;
152    }
153
154    /**
155     * Builds v2 services
156     *
157     * @param string                $serviceName The fully qualified service name
158     * @param CredentialsInterface  $credentials
159     * @param TokenStorageInterface $storage
160     * @param array|null            $scopes      Array of scopes for the service
161     * @param UriInterface|null     $baseApiUri
162     *
163     * @return ServiceInterface
164     *
165     * @throws Exception
166     */
167    private function buildV2Service(
168        $serviceName,
169        CredentialsInterface $credentials,
170        TokenStorageInterface $storage,
171        array $scopes,
172        UriInterface $baseApiUri = null,
173        $apiVersion = ""
174    ) {
175        return new $serviceName(
176            $credentials,
177            $this->httpClient,
178            $storage,
179            $this->resolveScopes($serviceName, $scopes),
180            $baseApiUri,
181            $apiVersion
182        );
183    }
184
185    /**
186     * Resolves scopes for v2 services
187     *
188     * @param string  $serviceName The fully qualified service name
189     * @param array   $scopes      List of scopes for the service
190     *
191     * @return array List of resolved scopes
192     */
193    private function resolveScopes($serviceName, array $scopes)
194    {
195        $reflClass = new \ReflectionClass($serviceName);
196        $constants = $reflClass->getConstants();
197
198        $resolvedScopes = array();
199        foreach ($scopes as $scope) {
200            $key = strtoupper('SCOPE_' . $scope);
201
202            if (array_key_exists($key, $constants)) {
203                $resolvedScopes[] = $constants[$key];
204            } else {
205                $resolvedScopes[] = $scope;
206            }
207        }
208
209        return $resolvedScopes;
210    }
211
212    /**
213     * Builds v1 services
214     *
215     * @param string                $serviceName The fully qualified service name
216     * @param CredentialsInterface  $credentials
217     * @param TokenStorageInterface $storage
218     * @param array                 $scopes
219     * @param UriInterface          $baseApiUri
220     *
221     * @return ServiceInterface
222     *
223     * @throws Exception
224     */
225    private function buildV1Service(
226        $serviceName,
227        CredentialsInterface $credentials,
228        TokenStorageInterface $storage,
229        $scopes,
230        UriInterface $baseApiUri = null
231    ) {
232        if (!empty($scopes)) {
233            throw new Exception(
234                'Scopes passed to ServiceFactory::createService but an OAuth1 service was requested.'
235            );
236        }
237
238        return new $serviceName($credentials, $this->httpClient, $storage, new Signature($credentials), $baseApiUri);
239    }
240}
241